Skip to content

Commit

Permalink
Handle promise errors in router middleware
Browse files Browse the repository at this point in the history
* Add lots of `return` calls for proxying the function returns back to the previous middleware stack function
* Catch promise errors and pass to `next`
  • Loading branch information
blakeembrey committed Dec 7, 2015
1 parent 7ed8a4b commit dd8c57f
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 29 deletions.
34 changes: 13 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ var setPrototypeOf = require('setprototypeof')

var slice = Array.prototype.slice

/* istanbul ignore next */
var defer = typeof setImmediate === 'function'
? setImmediate
: function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }

/**
* Expose `Router`.
*/
Expand Down Expand Up @@ -61,7 +56,7 @@ function Router(options) {
var opts = options || {}

function router(req, res, next) {
router.handle(req, res, next)
return router.handle(req, res, next)
}

// inherit from the correct prototype
Expand Down Expand Up @@ -186,7 +181,7 @@ Router.prototype.handle = function handle(req, res, callback) {
req.baseUrl = parentUrl
req.originalUrl = req.originalUrl || req.url

next()
return next()

function next(err) {
var layerError = err === 'route'
Expand All @@ -208,8 +203,7 @@ Router.prototype.handle = function handle(req, res, callback) {

// no more matching layers
if (idx >= stack.length) {
defer(done, layerError)
return
return done(layerError)
}

// get pathname of request
Expand Down Expand Up @@ -281,7 +275,7 @@ Router.prototype.handle = function handle(req, res, callback) {
var layerPath = layer.path

// this should be done for the layer
self.process_params(layer, paramcalled, req, res, function (err) {
return self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err)
}
Expand All @@ -290,16 +284,15 @@ Router.prototype.handle = function handle(req, res, callback) {
return layer.handle_request(req, res, next)
}

trim_prefix(layer, layerError, layerPath, path)
return trim_prefix(layer, layerError, layerPath, path)
})
}

function trim_prefix(layer, layerError, layerPath, path) {
var c = path[layerPath.length]

if (c && c !== '/') {
next(layerError)
return
return next(layerError)
}

// Trim off the part of the url that matches the route
Expand All @@ -324,9 +317,9 @@ Router.prototype.handle = function handle(req, res, callback) {
debug('%s %s : %s', layer.name, layerPath, req.originalUrl)

if (layerError) {
layer.handle_error(layerError, req, res, next)
return layer.handle_error(layerError, req, res, next)
} else {
layer.handle_request(req, res, next)
return layer.handle_request(req, res, next)
}
}
}
Expand Down Expand Up @@ -399,7 +392,7 @@ Router.prototype.process_params = function process_params(layer, called, req, re
value: paramVal
}

paramCallback()
return paramCallback()
}

// single param callbacks
Expand All @@ -412,20 +405,19 @@ Router.prototype.process_params = function process_params(layer, called, req, re
if (err) {
// store error
paramCalled.error = err
param(err)
return
return param(err)
}

if (!fn) return param()

try {
fn(req, res, paramCallback, paramVal, key.name)
return fn(req, res, paramCallback, paramVal, key.name)
} catch (e) {
paramCallback(e)
return paramCallback(e)
}
}

param()
return param()
}

/**
Expand Down
22 changes: 18 additions & 4 deletions lib/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@
var pathRegexp = require('path-to-regexp')
var debug = require('debug')('router:layer')

/**
* Normalize the middleware result with promise error handling.
*/

function normalize (result, next) {
if (result && typeof result.then === 'function') {
return result.then(null, function (err) {
return next(err || new Error('Promise was rejected with a falsy value'))
})
}

return result
}

/**
* Module variables.
* @private
Expand Down Expand Up @@ -66,9 +80,9 @@ Layer.prototype.handle_error = function handle_error(error, req, res, next) {
}

try {
fn(error, req, res, next)
return normalize(fn(error, req, res, next), next)
} catch (err) {
next(err)
return next(err)
}
}

Expand All @@ -90,9 +104,9 @@ Layer.prototype.handle_request = function handle(req, res, next) {
}

try {
fn(req, res, next)
return normalize(fn(req, res, next), next)
} catch (err) {
next(err)
return next(err)
}
}

Expand Down
6 changes: 3 additions & 3 deletions lib/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Route.prototype.dispatch = function dispatch(req, res, done) {

req.route = this

next()
return next()

function next(err) {
if (err && err === 'route') {
Expand All @@ -133,9 +133,9 @@ Route.prototype.dispatch = function dispatch(req, res, done) {
}

if (err) {
layer.handle_error(err, req, res, next)
return layer.handle_error(err, req, res, next)
} else {
layer.handle_request(req, res, next)
return layer.handle_request(req, res, next)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"devDependencies": {
"after": "0.8.1",
"bluebird": "3.0.6",
"finalhandler": "0.4.0",
"istanbul": "0.4.0",
"mocha": "2.3.3",
Expand Down
34 changes: 34 additions & 0 deletions test/router.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

var after = require('after')
var methods = require('methods')
var Bluebird = require('bluebird')
var Router = require('..')
var utils = require('./support/utils')

Expand Down Expand Up @@ -539,6 +540,24 @@ describe('Router', function () {
.expect(404, done)
})

it('should read downstream results', function (done) {
var router = new Router()
var server = createServer(router)
var message = 'hello world'

router.use(function (req, res, next) {
return next().then(res.end.bind(res))
})

router.use(function (req, res, next) {
return Bluebird.resolve(message)
})

request(server)
.get('/')
.expect(200, message, done)
})

describe('error handling', function () {
it('should invoke error function after next(err)', function (done) {
var router = new Router()
Expand Down Expand Up @@ -570,6 +589,21 @@ describe('Router', function () {
.expect(200, 'saw Error: boom!', done)
})

it('should invoke error function after rejected promise', function (done) {
var router = new Router()
var server = createServer(router)

router.use(function handle(req, res, next) {
return Bluebird.reject(new Error('boom!'))
})

router.use(sawError)

request(server)
.get('/')
.expect(200, 'saw Error: boom!', done)
})

it('should not invoke error functions above function', function (done) {
var router = new Router()
var server = createServer(router)
Expand Down
2 changes: 1 addition & 1 deletion test/support/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function createHitHandle(num) {
var name = 'x-fn-' + String(num)
return function hit(req, res, next) {
res.setHeader(name, 'hit')
next()
return next()
}
}

Expand Down

0 comments on commit dd8c57f

Please sign in to comment.