161 lines
3.4 KiB
JavaScript
161 lines
3.4 KiB
JavaScript
|
/**
|
|||
|
* @typedef {(error?: Error|null|undefined, ...output: Array<any>) => void} Callback
|
|||
|
* @typedef {(...input: Array<any>) => any} Middleware
|
|||
|
*
|
|||
|
* @typedef {(...input: Array<any>) => void} Run
|
|||
|
* Call all middleware.
|
|||
|
* @typedef {(fn: Middleware) => Pipeline} Use
|
|||
|
* Add `fn` (middleware) to the list.
|
|||
|
* @typedef {{run: Run, use: Use}} Pipeline
|
|||
|
* Middleware.
|
|||
|
*/
|
|||
|
|
|||
|
/**
|
|||
|
* Create new middleware.
|
|||
|
*
|
|||
|
* @returns {Pipeline}
|
|||
|
*/
|
|||
|
export function trough() {
|
|||
|
/** @type {Array<Middleware>} */
|
|||
|
const fns = []
|
|||
|
/** @type {Pipeline} */
|
|||
|
const pipeline = {run, use}
|
|||
|
|
|||
|
return pipeline
|
|||
|
|
|||
|
/** @type {Run} */
|
|||
|
function run(...values) {
|
|||
|
let middlewareIndex = -1
|
|||
|
/** @type {Callback} */
|
|||
|
const callback = values.pop()
|
|||
|
|
|||
|
if (typeof callback !== 'function') {
|
|||
|
throw new TypeError('Expected function as last argument, not ' + callback)
|
|||
|
}
|
|||
|
|
|||
|
next(null, ...values)
|
|||
|
|
|||
|
/**
|
|||
|
* Run the next `fn`, or we’re done.
|
|||
|
*
|
|||
|
* @param {Error|null|undefined} error
|
|||
|
* @param {Array<any>} output
|
|||
|
*/
|
|||
|
function next(error, ...output) {
|
|||
|
const fn = fns[++middlewareIndex]
|
|||
|
let index = -1
|
|||
|
|
|||
|
if (error) {
|
|||
|
callback(error)
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// Copy non-nullish input into values.
|
|||
|
while (++index < values.length) {
|
|||
|
if (output[index] === null || output[index] === undefined) {
|
|||
|
output[index] = values[index]
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Save the newly created `output` for the next call.
|
|||
|
values = output
|
|||
|
|
|||
|
// Next or done.
|
|||
|
if (fn) {
|
|||
|
wrap(fn, next)(...output)
|
|||
|
} else {
|
|||
|
callback(null, ...output)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/** @type {Use} */
|
|||
|
function use(middelware) {
|
|||
|
if (typeof middelware !== 'function') {
|
|||
|
throw new TypeError(
|
|||
|
'Expected `middelware` to be a function, not ' + middelware
|
|||
|
)
|
|||
|
}
|
|||
|
|
|||
|
fns.push(middelware)
|
|||
|
return pipeline
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Wrap `middleware`.
|
|||
|
* Can be sync or async; return a promise, receive a callback, or return new
|
|||
|
* values and errors.
|
|||
|
*
|
|||
|
* @param {Middleware} middleware
|
|||
|
* @param {Callback} callback
|
|||
|
*/
|
|||
|
export function wrap(middleware, callback) {
|
|||
|
/** @type {boolean} */
|
|||
|
let called
|
|||
|
|
|||
|
return wrapped
|
|||
|
|
|||
|
/**
|
|||
|
* Call `middleware`.
|
|||
|
* @this {any}
|
|||
|
* @param {Array<any>} parameters
|
|||
|
* @returns {void}
|
|||
|
*/
|
|||
|
function wrapped(...parameters) {
|
|||
|
const fnExpectsCallback = middleware.length > parameters.length
|
|||
|
/** @type {any} */
|
|||
|
let result
|
|||
|
|
|||
|
if (fnExpectsCallback) {
|
|||
|
parameters.push(done)
|
|||
|
}
|
|||
|
|
|||
|
try {
|
|||
|
result = middleware.apply(this, parameters)
|
|||
|
} catch (error) {
|
|||
|
const exception = /** @type {Error} */ (error)
|
|||
|
|
|||
|
// Well, this is quite the pickle.
|
|||
|
// `middleware` received a callback and called it synchronously, but that
|
|||
|
// threw an error.
|
|||
|
// The only thing left to do is to throw the thing instead.
|
|||
|
if (fnExpectsCallback && called) {
|
|||
|
throw exception
|
|||
|
}
|
|||
|
|
|||
|
return done(exception)
|
|||
|
}
|
|||
|
|
|||
|
if (!fnExpectsCallback) {
|
|||
|
if (result instanceof Promise) {
|
|||
|
result.then(then, done)
|
|||
|
} else if (result instanceof Error) {
|
|||
|
done(result)
|
|||
|
} else {
|
|||
|
then(result)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Call `callback`, only once.
|
|||
|
* @type {Callback}
|
|||
|
*/
|
|||
|
function done(error, ...output) {
|
|||
|
if (!called) {
|
|||
|
called = true
|
|||
|
callback(error, ...output)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Call `done` with one value.
|
|||
|
*
|
|||
|
* @param {any} [value]
|
|||
|
*/
|
|||
|
function then(value) {
|
|||
|
done(null, value)
|
|||
|
}
|
|||
|
}
|