compose
compose copied to clipboard
support variadic usage (rather than requiring an array)
this is just my ocd, but imo the latter looks much nicer:
app.use(route.get('/foo', compose([ middleware1, middleware2, routeHandler ])))
vs
app.use(route.get('/foo', compose(middleware1, middleware2, routeHandler)))
would you take a pr implementing this in a backward compatible way?
/cc @jonathanong
i would personally like:
function compose(...args) {
const middleware = _.flatten(args)
// ...
}
@tj @juliangruber @dead-horse @fengmk2 thoughts?
SGTM
Awesome- I'll throw something together Monday if not sooner.
gonna hold on this for now... but i don't mind publishing this as v4 after we've understood the use-case for mutating middleware
Any updates? Can we just check if the first item is an array?
const _middlewares = Array.isArray(middlewares[0])
? middlewares[0]
: middlewares
IMO, it can be better if it supports both middleware and middlewares array
compose([middleware1, middleware2], middleware3, [middleware4, middleware5])
BTW, it is overweight to use lodash
@Runrioter I'm not sure if that's really useful, but you can always add a check that composes them
if (Array.isArray(my_middleware_taken_from_input_list)) { middleware = compose(middleware) }
With this you get better performance (see below) and support both compose([m1,m2]) and compose(m1,m2). It passes all the tests but many of them need to be rewritten if you keep the unwrapped case for compose(m1), it may still hide bugs too.
function compose(...middlewares) {
const _middlewares =
Array.isArray(middlewares[0]) && middlewares.length === 1
? middlewares[0]
: middlewares
_middlewares.map(m => {
if (typeof m !== "function")
throw new TypeError("Middleware must be composed of functions!")
})
const noop = () => {}
function _next(context, next, end) {
let called = 0
return Promise.resolve(
next === _middlewares.length
? end(context, noop)
: _middlewares[next](context, () => {
if (!called++) return _next(context, next + 1, end)
else throw new Error("next() called multiple times")
})
)
}
return function composed(context, next) {
try {
//Unwrap the case where there's a single middleware
//It can catch test cases so tests need to be updated to test
//with 2 or more middlewares as well as just 1.
if (_middlewares.length === 1) {
let called = 0
return Promise.resolve(
_middlewares[0](context, () => {
if (!called++) return (next || noop)(context, noop)
else throw new Error("next() called multiple times")
})
)
// END
} else return Promise.resolve(_next(context, 0, next || noop))
} catch (err) {
return Promise.reject(err)
}
}
}
Current implementation:
1,130,866 op/s » (fn * 1)
556,355 op/s » (fn * 2)
280,713 op/s » (fn * 4)
154,546 op/s » (fn * 8)
76,328 op/s » (fn * 16)
39,178 op/s » (fn * 32)
18,403 op/s » (fn * 64)
9,453 op/s » (fn * 128)
4,572 op/s » (fn * 256)
2,251 op/s » (fn * 512)
1,042 op/s » (fn * 1024)
This implementation:
1,308,892 op/s » (fn * 1) -- 15.7% (11.3% if you removed the optimization)
610,887 op/s » (fn * 2) -- 9.8%
303,005 op/s » (fn * 4) -- 7.9%
158,465 op/s » (fn * 8) -- 2.5% (Probably within margin of error from here on)
77,234 op/s » (fn * 16) -- 1.2%
38,029 op/s » (fn * 32) -- -3%
18,585 op/s » (fn * 64) -- irrelevant
9,410 op/s » (fn * 128)
4,578 op/s » (fn * 256)
2,232 op/s » (fn * 512)
1,072 op/s » (fn * 1024)
I forgot the PR that added this got reverted and wasted a bunch of time debugging... any chance we'd be up for trying this again?
Also, any news on "the use-case for mutating middleware"?