Control passed to handler before completing middleware chain
theycallmeswift opened this issue · comments
Hey, folks --
I feel like I must be doing something silly here, but I'm experiencing an issue where the middleware chain is not completely resolved before my code passes control to the final handler. Let me know if this is something I'm doing or an actual bug, thanks!
Setup:
- Middleware #1 - immmediately calls next
- Middleware #2 - immmediately calls next
- Middleware #3 - Waits 2 seconds, then calls next
- Handler - Print Finished when done
Expected Output:
Middleware #1: Triggered
Middleware #1: Next
Middleware #2: Triggered
Middleware #2: Next
Middleware #3: Triggered
Middleware #3: Next
Finished
Actual Output:
Middleware #1: Triggered
Middleware #1: Next
Middleware #2: Triggered
Middleware #2: Next
Middleware #3: Triggered
Finished
Middleware #3: Next
Code:
const compose = require('koa-compose');
/**
* Simple App Structure.
*/
class App {
constructor () {
this.middleware = [];
}
use (fn) {
if (typeof fn !== 'function') {
throw new TypeError('middleware must be a function!');
}
this.middleware.push(fn);
return this;
}
route (handler) {
let fn = compose(this.middleware);
return fn(this).then( () => this.handleRequest(handler) );
}
handleRequest (handler) {
return handler();
}
}
let app = new App();
/**
* Setup Middleware
*/
app.use( (ctx, next) => {
console.log("Middleware #1: Triggered");
console.log("Middleware #1: Next");
next();
});
app.use( (ctx, next) => {
console.log("Middleware #2: Triggered");
console.log("Middleware #2: Next");
next();
});
app.use( (ctx, next) => {
console.log("Middleware #3: Triggered");
setTimeout( () => {
console.log("Middleware #3: Next");
next();
}, 2000);
});
app.route(() => console.log("Finished") );
You need to return a promise if you do asynchronous activities, which includes calling next IIRC (it's been a while since I've worked with Koa).
Here's a working example: https://gist.github.com/1f0670e88911cac546dc80548bca1865
@PlasmaPower your example does work. I'm struggling to understand why though. This feels like Promise black magic haha.
Thoughts on this being an antipattern? My experience with callbacks is that you need to call next before moving on.
You're probably thinking of the express model. In Koa, you almost always return a promise.
Expanding on @PlasmaPower's answer: Promises need to be chain returned otherwise any of them will resolve and Koa flushes the response. All next calls return a promise guaranteed because of how compose works - meaning that even if you call next expecting it will be a normal function it's actually a promise.