mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
3.5 KiB
176 lines
3.5 KiB
/**
|
|
* @author Titus Wormer
|
|
* @copyright 2016 Titus Wormer
|
|
* @license MIT
|
|
* @module trough
|
|
* @fileoverview Middleware. Inspired by `segmentio/ware`,
|
|
* but able to change the values from transformer to
|
|
* transformer.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
/* Expose. */
|
|
module.exports = trough;
|
|
|
|
/* Methods. */
|
|
var slice = [].slice;
|
|
|
|
/**
|
|
* Create new middleware.
|
|
*
|
|
* @return {Object} - Middlewre.
|
|
*/
|
|
function trough() {
|
|
var fns = [];
|
|
var middleware = {};
|
|
|
|
middleware.run = run;
|
|
middleware.use = use;
|
|
|
|
return middleware;
|
|
|
|
/**
|
|
* Run `fns`. Last argument must be
|
|
* a completion handler.
|
|
*
|
|
* @param {...*} input - Parameters
|
|
*/
|
|
function run() {
|
|
var index = -1;
|
|
var input = slice.call(arguments, 0, -1);
|
|
var done = arguments[arguments.length - 1];
|
|
|
|
if (typeof done !== 'function') {
|
|
throw new Error('Expected function as last argument, not ' + done);
|
|
}
|
|
|
|
next.apply(null, [null].concat(input));
|
|
|
|
return;
|
|
|
|
/**
|
|
* Run the next `fn`, if any.
|
|
*
|
|
* @param {Error?} err - Failure.
|
|
* @param {...*} values - Other input.
|
|
*/
|
|
function next(err) {
|
|
var fn = fns[++index];
|
|
var params = slice.call(arguments, 0);
|
|
var values = params.slice(1);
|
|
var length = input.length;
|
|
var pos = -1;
|
|
|
|
if (err) {
|
|
done(err);
|
|
return;
|
|
}
|
|
|
|
/* Copy non-nully input into values. */
|
|
while (++pos < length) {
|
|
if (values[pos] === null || values[pos] === undefined) {
|
|
values[pos] = input[pos];
|
|
}
|
|
}
|
|
|
|
input = values;
|
|
|
|
/* Next or done. */
|
|
if (fn) {
|
|
wrap(fn, next).apply(null, input);
|
|
} else {
|
|
done.apply(null, [null].concat(input));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add `fn` to the list.
|
|
*
|
|
* @param {Function} fn - Anything `wrap` accepts.
|
|
*/
|
|
function use(fn) {
|
|
if (typeof fn !== 'function') {
|
|
throw new Error('Expected `fn` to be a function, not ' + fn);
|
|
}
|
|
|
|
fns.push(fn);
|
|
|
|
return middleware;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrap `fn`. Can be sync or async; return a promise,
|
|
* receive a completion handler, return new values and
|
|
* errors.
|
|
*
|
|
* @param {Function} fn - Thing to wrap.
|
|
* @param {Function} next - Completion handler.
|
|
* @return {Function} - Wrapped `fn`.
|
|
*/
|
|
function wrap(fn, next) {
|
|
var invoked;
|
|
|
|
return wrapped;
|
|
|
|
function wrapped() {
|
|
var params = slice.call(arguments, 0);
|
|
var callback = fn.length > params.length;
|
|
var result;
|
|
|
|
if (callback) {
|
|
params.push(done);
|
|
}
|
|
|
|
try {
|
|
result = fn.apply(null, params);
|
|
} catch (err) {
|
|
/* Well, this is quite the pickle. `fn` received
|
|
* a callback and invoked it (thus continuing the
|
|
* pipeline), but later also threw an error.
|
|
* We’re not about to restart the pipeline again,
|
|
* so the only thing left to do is to throw the
|
|
* thing instea. */
|
|
if (callback && invoked) {
|
|
throw err;
|
|
}
|
|
|
|
return done(err);
|
|
}
|
|
|
|
if (!callback) {
|
|
if (result && typeof result.then === 'function') {
|
|
result.then(then, done);
|
|
} else if (result instanceof Error) {
|
|
done(result);
|
|
} else {
|
|
then(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invoke `next`, only once.
|
|
*
|
|
* @param {Error?} err - Optional error.
|
|
*/
|
|
function done() {
|
|
if (!invoked) {
|
|
invoked = true;
|
|
|
|
next.apply(null, arguments);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invoke `done` with one value.
|
|
* Tracks if an error is passed, too.
|
|
*
|
|
* @param {*} value - Optional value.
|
|
*/
|
|
function then(value) {
|
|
done(null, value);
|
|
}
|
|
}
|
|
|