|
var Promise = require('any-promise');
|
var co = require('co');
|
|
module.exports = compose;
|
|
compose.Wrap = Wrap;
|
|
function compose(middleware) {
|
return function (next) {
|
next = next || new Wrap(noop);
|
var i = middleware.length;
|
while (i--) next = new Wrap(middleware[i], this, next);
|
return next
|
}
|
}
|
|
/**
|
* Wrap a function, then lazily call it,
|
* always returning both a promise and a generator.
|
*
|
* @param {Function} fn
|
* @param {Object} ctx
|
* @param {Wrap} next
|
*/
|
|
function Wrap(fn, ctx, next) {
|
if (typeof fn !== 'function') throw TypeError('Not a function!');
|
this._fn = fn;
|
this._ctx = ctx;
|
this._next = next;
|
this._called = false;
|
this._value = undefined;
|
this._promise = undefined;
|
this._generator = undefined;
|
}
|
|
/**
|
* Lazily call the function.
|
* Note that if it's not an async or generator function,
|
*
|
* @returns {Mixed}
|
*/
|
|
Wrap.prototype._getValue = function () {
|
if (!this._called) {
|
this._called = true;
|
try {
|
this._value = this._fn.call(this._ctx, this._next);
|
} catch (e) {
|
this._value = Promise.reject(e);
|
}
|
}
|
return this._value
|
};
|
|
/**
|
* Lazily create a promise from the return value.
|
*
|
* @returns {Promise}
|
*/
|
|
Wrap.prototype._getPromise = function () {
|
if (this._promise === undefined) {
|
var value = this._getValue();
|
this._promise = isGenerator(value)
|
? co.call(this._ctx, value)
|
: Promise.resolve(value);
|
}
|
return this._promise
|
}
|
|
/**
|
* Lazily create a generator from the return value.
|
*
|
* @returns {Iterator}
|
*/
|
|
Wrap.prototype._getGenerator = function () {
|
if (this._generator === undefined) {
|
var value = this._getValue();
|
this._generator = isGenerator(value)
|
? value
|
: promiseToGenerator.call(this._ctx, value);
|
}
|
return this._generator
|
}
|
|
/**
|
* In later version of v8,
|
* `yield*` works on the `[@@iterator]` method.
|
*
|
* @returns {Iterator}
|
*/
|
|
if (typeof Symbol !== 'undefined') {
|
Wrap.prototype[Symbol.iterator] = function () {
|
return this._getGenerator();
|
}
|
}
|
|
/**
|
* This creates a generator from a promise or a value.
|
*
|
* TODO: try to avoid using a generator function for this.
|
*
|
* @returns {Iterator}
|
*/
|
|
var loggedDeprecationMessage = false;
|
function* promiseToGenerator(promise) {
|
if (!loggedDeprecationMessage) {
|
console.error('A promise was converted into a generator, which is an anti-pattern. Please avoid using `yield* next`!')
|
loggedDeprecationMessage = true;
|
}
|
return yield Promise.resolve(promise);
|
}
|
|
/**
|
* Proxy generator and promise methods.
|
*/
|
|
Wrap.prototype.then = function (resolve, reject) {
|
return this._getPromise().then(resolve, reject);
|
}
|
|
Wrap.prototype.catch = function (reject) {
|
return this._getPromise().catch(reject);
|
}
|
|
Wrap.prototype.next = function (val) {
|
return this._getGenerator().next(val);
|
}
|
|
Wrap.prototype.throw = function (err) {
|
return this._getGenerator().throw(err);
|
}
|
|
/**
|
* Check if `obj` is a generator.
|
*
|
* @param {Mixed} obj
|
* @return {Boolean}
|
*/
|
|
function isGenerator(obj) {
|
return obj
|
&& typeof obj.next === 'function'
|
&& typeof obj.throw === 'function';
|
}
|
|
function noop() {}
|