CodePark/ problems / Middleware pipeline
START PRACTICINGPRACTICE
Coding problem · JavaScript utilities › Event emitters and observable state

Middleware pipeline

hardabout 35 minutes in an interview

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):

  • ctx is 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 after await 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 async or a plain function.

Rules:

  • After the last middleware, next() calls the optional next given to run. Without one, it resolves straight away. With an empty array, run calls that next if there is one, then resolves.
  • run resolves with the value the first middleware returns. If that value is a promise, run resolves with what the promise resolves to.
  • Calling next() twice in the same middleware returns a promise that rejects with an Error whose message is "next() called multiple times".
  • Errors never throw out of run synchronously. If a middleware throws or rejects, the next() promise of the middleware before it rejects. So an earlier middleware can catch the error with try { await next() } catch {}. If nobody catches it, the promise from run rejects.
  • compose throws a TypeError right away if middlewares is not an array. It also throws if the array holds anything that isn't a function.
  • Each call of run is 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')