Implement compose(middlewares) in the style of Koa, a Node web framework. A middleware is a function that does one step of the work and then passes control on. compose takes an array of middleware functions. It returns one function, run(ctx, next?), that runs them as a pipeline and returns a promise.
Each middleware is called as middleware(ctx, next):
ctxis the same object for every middleware in one run.next()starts the next middleware. It returns a promise that settles when that middleware, and everything after it, has finished. So code afterawait next()runs on the way back up, after all later middleware.- A middleware that doesn't call
next()ends the chain there. - A middleware may be
asyncor a plain function.
Rules:
- After the last middleware,
next()calls the optionalnextgiven torun. Without one, it resolves straight away. With an empty array,runcalls thatnextif there is one, then resolves. runresolves with the value the first middleware returns. If that value is a promise,runresolves with what the promise resolves to.- Calling
next()twice in the same middleware returns a promise that rejects with anErrorwhose message is"next() called multiple times". - Errors never throw out of
runsynchronously. If a middleware throws or rejects, thenext()promise of the middleware before it rejects. So an earlier middleware can catch the error withtry { await next() } catch {}. If nobody catches it, the promise fromrunrejects. composethrows aTypeErrorright away ifmiddlewaresis not an array. It also throws if the array holds anything that isn't a function.- Each call of
runis independent.
Input: compose([
async (c, next) => { c.log.push('a in'); await next(); c.log.push('a out'); },
(c) => { c.log.push('b'); },
])(ctx)
Output: ctx.log is ['a in', 'b', 'a out'] once the promise resolves
Input: compose([async (c, next) => { await next(); await next(); }])(ctx)
Output: rejects with Error('next() called multiple times')