Skip to content

Commit

Permalink
Add basic support for returned, rejected Promises
Browse files Browse the repository at this point in the history
closes #47
  • Loading branch information
leebyron authored and dougwilson committed Jul 19, 2018
1 parent f0c4c70 commit d2bce0c
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 2 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
2.x
===

* Add basic support for returned, rejected Promises
- Rejected Promises from middleware functions `next(error)`
* Drop support for Node.js below 0.10
* deps: [email protected]
- Add `DEBUG_HIDE_DATE` environment variable
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ format is with three parameters - "req", "res" and "next".
- `res` - This is a [HTTP server response](https://nodejs.org/api/http.html#http_class_http_serverresponse) instance.
- `next` - Calling this function that tells `router` to proceed to the next matching middleware or method handler. It accepts an error as the first argument.

The function can optionally return a `Promise` object. If a `Promise` object
is returned from the function, the router will attach an `onRejected` callback
using `.then`. If the promise is rejected, `next` will be called with the
rejected value, or an error if the value is falsy.

Middleware and method handlers can also be defined with four arguments. When
the function has four parameters defined, the first argument is an error and
subsequent arguments remain, becoming - "err", "req", "res", "next". These
Expand Down
34 changes: 32 additions & 2 deletions lib/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,15 @@ Layer.prototype.handle_error = function handle_error(error, req, res, next) {
}

try {
fn(error, req, res, next)
// invoke function
var ret = fn(error, req, res, next)

// wait for returned promise
if (isPromise(ret)) {
ret.then(null, function (error) {
next(error || new Error('Rejected promise'))
})
}
} catch (err) {
next(err)
}
Expand All @@ -90,7 +98,15 @@ Layer.prototype.handle_request = function handle(req, res, next) {
}

try {
fn(req, res, next)
// invoke function
var ret = fn(req, res, next)

// wait for returned promise
if (isPromise(ret)) {
ret.then(null, function (error) {
next(error || new Error('Rejected promise'))
})
}
} catch (err) {
next(err)
}
Expand Down Expand Up @@ -178,3 +194,17 @@ function decode_param(val){
throw err
}
}

/**
* Returns true if the val is a Promise.
*
* @param {*} val
* @return {boolean}
* @private
*/

function isPromise (val) {
return val &&
typeof val === 'object' &&
typeof val.then === 'function'
}
137 changes: 137 additions & 0 deletions test/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ var request = utils.request
var shouldHitHandle = utils.shouldHitHandle
var shouldNotHitHandle = utils.shouldNotHitHandle

var describePromises = global.Promise ? describe : describe.skip

describe('Router', function () {
describe('.route(path)', function () {
it('should return a new route', function () {
Expand Down Expand Up @@ -467,6 +469,141 @@ describe('Router', function () {
})
})

describePromises('promise support', function () {
it('should pass rejected promise value', function (done) {
var router = new Router()
var route = router.route('/foo')
var server = createServer(router)

route.all(function createError (req, res, next) {
return Promise.reject(new Error('boom!'))
})

route.all(helloWorld)

route.all(function handleError (err, req, res, next) {
res.statusCode = 500
res.end('caught: ' + err.message)
})

request(server)
.get('/foo')
.expect(500, 'caught: boom!', done)
})

it('should pass rejected promise without value', function (done) {
var router = new Router()
var route = router.route('/foo')
var server = createServer(router)

route.all(function createError (req, res, next) {
return Promise.reject()
})

route.all(helloWorld)

route.all(function handleError (err, req, res, next) {
res.statusCode = 500
res.end('caught: ' + err.message)
})

request(server)
.get('/foo')
.expect(500, 'caught: Rejected promise', done)
})

it('should ignore resolved promise', function (done) {
var router = new Router()
var route = router.route('/foo')
var server = createServer(router)

route.all(function createError (req, res, next) {
saw(req, res)
return Promise.resolve('foo')
})

route.all(function () {
done(new Error('Unexpected route invoke'))
})

request(server)
.get('/foo')
.expect(200, 'saw GET /foo', done)
})

describe('error handling', function () {
it('should pass rejected promise value', function (done) {
var router = new Router()
var route = router.route('/foo')
var server = createServer(router)

route.all(function createError (req, res, next) {
return Promise.reject(new Error('boom!'))
})

route.all(function handleError (err, req, res, next) {
return Promise.reject(new Error('caught: ' + err.message))
})

route.all(function handleError (err, req, res, next) {
res.statusCode = 500
res.end('caught again: ' + err.message)
})

request(server)
.get('/foo')
.expect(500, 'caught again: caught: boom!', done)
})

it('should pass rejected promise without value', function (done) {
var router = new Router()
var route = router.route('/foo')
var server = createServer(router)

route.all(function createError (req, res, next) {
return Promise.reject(new Error('boom!'))
})

route.all(function handleError (err, req, res, next) {
return Promise.reject()
})

route.all(function handleError (err, req, res, next) {
res.statusCode = 500
res.end('caught again: ' + err.message)
})

request(server)
.get('/foo')
.expect(500, 'caught again: Rejected promise', done)
})

it('should ignore resolved promise', function (done) {
var router = new Router()
var route = router.route('/foo')
var server = createServer(router)

route.all(function createError (req, res, next) {
return Promise.reject(new Error('boom!'))
})

route.all(function handleError (err, req, res, next) {
res.statusCode = 500
res.end('caught: ' + err.message)
return Promise.resolve('foo')
})

route.all(function () {
done(new Error('Unexpected route invoke'))
})

request(server)
.get('/foo')
.expect(500, 'caught: boom!', done)
})
})
})

describe('path', function () {
describe('using ":name"', function () {
it('should name a capture group', function (done) {
Expand Down
114 changes: 114 additions & 0 deletions test/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var request = utils.request
var shouldHitHandle = utils.shouldHitHandle
var shouldNotHitHandle = utils.shouldNotHitHandle

var describePromises = global.Promise ? describe : describe.skip

describe('Router', function () {
it('should return a function', function () {
assert.equal(typeof Router(), 'function')
Expand Down Expand Up @@ -690,6 +692,118 @@ describe('Router', function () {
})
})

describePromises('promise support', function () {
it('should pass rejected promise value', function (done) {
var router = new Router()
var server = createServer(router)

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

router.use(sawError)

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

it('should pass rejected promise without value', function (done) {
var router = new Router()
var server = createServer(router)

router.use(function createError (req, res, next) {
return Promise.reject()
})

router.use(sawError)

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

it('should ignore resolved promise', function (done) {
var router = new Router()
var server = createServer(router)

router.use(function createError (req, res, next) {
saw(req, res)
return Promise.resolve('foo')
})

router.use(function () {
done(new Error('Unexpected middleware invoke'))
})

request(server)
.get('/foo')
.expect(200, 'saw GET /foo', done)
})

describe('error handling', function () {
it('should pass rejected promise value', function (done) {
var router = new Router()
var server = createServer(router)

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

router.use(function handleError (err, req, res, next) {
return Promise.reject(new Error('caught: ' + err.message))
})

router.use(sawError)

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

it('should pass rejected promise without value', function (done) {
var router = new Router()
var server = createServer(router)

router.use(function createError (req, res, next) {
return Promise.reject()
})

router.use(function handleError (err, req, res, next) {
return Promise.reject(new Error('caught: ' + err.message))
})

router.use(sawError)

request(server)
.get('/')
.expect(200, 'saw Error: caught: Rejected promise', done)
})

it('should ignore resolved promise', function (done) {
var router = new Router()
var server = createServer(router)

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

router.use(function handleError (err, req, res, next) {
sawError(err, req, res, next)
return Promise.resolve('foo')
})

router.use(function () {
done(new Error('Unexpected middleware invoke'))
})

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

describe('req.baseUrl', function () {
it('should be empty', function (done) {
var router = new Router()
Expand Down

0 comments on commit d2bce0c

Please sign in to comment.