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

/**
* @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);
}
}