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