koajs / compose

Middleware composition utility

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Control passed to handler before completing middleware chain

theycallmeswift opened this issue · comments

commented

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

commented

@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.