koa: ctx.throw() kills the server

This server

const app = new (require('koa'))()

// first middleware
app.use(async (ctx, next) => {
  next()
})

// second middleware
app.use(async ctx => {
  ctx.throw(400)
})

app.listen(3000)

process.on('unhandledRejection', reason => {
  throw reason
})

will blowup when request will get to the second middleware, and you will see it in the error

BadRequestError: Bad Request
    at Object.throw (...\node_modules\koa\lib\context.js:91:23)
    at .../server.js:10:3
    at Generator.next (<anonymous>)
    at step (...\server.js:3:191)
    at ...\server.js:3:437
    at ...\server.js:3:99
    at app.use (.../server.js:9:1)
    at dispatch (...\node_modules\koa-compose\index.js:44:32)
    at next (...\node_modules\koa-compose\index.js:45:18)
    at .../server.js:6:3

Error message blames request but the real problem is in the first middleware, where next() doesn’t have await before it. Lack of await is probably a bug. The issue points are:

  1. Should it blowup the server?
  2. Every thing would be ok if second middleware would just ctx.body = 'ok' and no error or warning would appears. Reaction on absents of await should not depend on consequent code.
  3. Reaction should be as soon as possible. Seen the crash on ctx.throw() in the other part of a project is very confusing.

Edit: Possible solution could be to throw if next has been called, middleware is resolved and next is still pending with message says that this is the problem

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 17 (12 by maintainers)

Most upvoted comments

// first middleware
app.use(async (ctx, next) => {
  next()
})

here is the problem, you need to await or return next(), when you dont do that, the promise chain is ended, just change to

app.use(async (ctx, next) => {
  await next()
})
// or a simple arrow function
app.use((ctx, next) => next())

lack of an await/return is a bug. awaiting or returning it should fix it. otherwise, it’s just a dangling promise

Would it be sane to, in koa-compose, make sure that the return is present (AFAIK async in ES@next always returns a Promise that resolves anyway)? Mini safeguard against newbie buggs that doesn’t affect run-time performance?