diff --git a/HISTORY.md b/HISTORY.md index a85082d..78c4ee4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,6 +3,8 @@ This incorporates all changes after 1.3.5 up to 1.3.6. + * Add support for returned, rejected Promises to `router.param` + 2.0.0-beta.1 / 2020-03-29 ========================= diff --git a/README.md b/README.md index b105931..77cbddc 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,11 @@ Maps the specified path parameter `name` to a specialized param-capturing middle This function positions the middleware in the same stack as `.use`. +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. + Parameter mapping is used to provide pre-conditions to routes which use normalized placeholders. For example a _:user_id_ parameter could automatically load a user's information from the database without diff --git a/index.js b/index.js index 6dbb540..c9ee0fa 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,7 @@ */ var flatten = require('array-flatten').flatten +var isPromise = require('is-promise') var Layer = require('./lib/layer') var methods = require('methods') var mixin = require('utils-merge') @@ -642,7 +643,12 @@ function processParams (params, layer, called, req, res, done) { if (!fn) return param() try { - fn(req, res, paramCallback, paramVal, key.name) + var ret = fn(req, res, paramCallback, paramVal, key.name) + if (isPromise(ret)) { + ret.then(null, function (error) { + paramCallback(error || new Error('Rejected promise')) + }) + } } catch (e) { paramCallback(e) } diff --git a/lib/layer.js b/lib/layer.js index 39acfe2..4936ca2 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -12,6 +12,7 @@ * @private */ +var isPromise = require('is-promise') var pathRegexp = require('path-to-regexp') /** @@ -187,20 +188,6 @@ function decodeParam (val) { } } -/** - * 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' -} - /** * Loosens the given path for path-to-regexp matching. */ diff --git a/package.json b/package.json index fa25e86..e85a87a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "repository": "pillarjs/router", "dependencies": { "array-flatten": "3.0.0", + "is-promise": "4.0.0", "methods": "~1.1.2", "parseurl": "~1.3.3", "path-to-regexp": "3.2.0", diff --git a/test/param.js b/test/param.js index 43245fc..e2ca379 100644 --- a/test/param.js +++ b/test/param.js @@ -10,6 +10,8 @@ var shouldNotHitHandle = utils.shouldNotHitHandle var createServer = utils.createServer var request = utils.request +var describePromises = global.Promise ? describe : describe.skip + describe('Router', function () { describe('.param(name, fn)', function () { it('should reject missing name', function () { @@ -254,6 +256,48 @@ describe('Router', function () { .expect(500, /Error: boom/, done) }) + describePromises('promise support', function () { + it('should pass rejected promise value', function (done) { + var router = new Router() + var server = createServer(router) + + router.param('user', function parseUser (req, res, next, user) { + return Promise.reject(new Error('boom')) + }) + + router.get('/user/:user', function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('get user ' + req.params.id) + }) + + request(server) + .get('/user/bob') + .expect(500, /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() // eslint-disable-line prefer-promise-reject-errors + }) + + router.param('user', function parseUser (req, res, next, user) { + return Promise.reject() // eslint-disable-line prefer-promise-reject-errors + }) + + router.get('/user/:user', function (req, res) { + res.setHeader('Content-Type', 'text/plain') + res.end('get user ' + req.params.id) + }) + + request(server) + .get('/user/bob') + .expect(500, /Error: Rejected promise/, done) + }) + }) + describe('next("route")', function () { it('should cause route with param to be skipped', function (done) { var cb = after(3, done)