Skip to content

Commit 12be9e2

Browse files
committed
feat: async/await support
BREAKING CHANGE: adds async/await support to pre, use and handler chains
1 parent bd34988 commit 12be9e2

11 files changed

+483
-13
lines changed

.eslintrc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ if (!process.env.NO_LINT) {
127127
// stylistic.
128128
if (!process.env.NO_STYLE) {
129129
// Global
130-
config.rules['max-len'] = [ERROR, { code: 80 }];
130+
config.rules['max-len'] = [ERROR, { code: 80, ignoreComments: true }];
131131

132132
// Prettier
133133
config.extends.push('prettier');

.travis.yml

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
sudo: false
22
language: node_js
33
node_js:
4-
- '8'
54
- '10'
65
- "lts/*" # Active LTS release
76
- "node" # Latest stable release

docs/guides/8to9guide.md

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
title: restify 8.x to 9.x migration guide
3+
permalink: /docs/8to9/
4+
---
5+
6+
## Introduction
7+
8+
restify `9.x` comes with `async/await` support!
9+
10+
## Breaking Changes
11+
12+
### Drops support for Node.js `8.x`
13+
14+
Restify requires Node.js version `>=10.0.0`.
15+
16+
### Async/await support
17+
18+
`async/await` basic support for `.pre()`, `.use()` and route handlers.
19+
20+
#### Example
21+
22+
```js
23+
const restify = require('restify');
24+
25+
const server = restify.createServer({});
26+
27+
server.use(async (req, res) => {
28+
req.something = await doSomethingAsync();
29+
});
30+
31+
server.get('/params', async (req, res) => {
32+
const value = await asyncOperation(req.something);
33+
res.send(value);
34+
});
35+
```
36+
37+
#### Middleware API (`.pre()` and `.use()`)
38+
39+
```js
40+
server.use(async (req, res) => {
41+
req.something = await doSomethingAsync();
42+
});
43+
```
44+
- `fn.length === 2` (arity 2);
45+
- `fn instanceof AsyncFunction`;
46+
- if the async function resolves, it calls `next()`;
47+
- any value returned by the async function will be discarded;
48+
- if it rejects with an `Error` instance it calls `next(err)`;
49+
- if it rejects with anything else it wraps in a `AsyncError` and calls `next(err)`;
50+
51+
#### Route handler API
52+
53+
```js
54+
server.get('/something', async (req, res) => {
55+
const someData = await fetchSomeDataAsync();
56+
res.send({ data: someData });
57+
});
58+
```
59+
- `fn.length === 2` (arity 2);
60+
- `fn instanceof AsyncFunction`;
61+
- if the async function resolves without a value, it calls `next()`;
62+
- if the async function resolves with a string value, it calls `next(string)` (re-routes*);
63+
- if the async function resolves with a value other than string, it calls `next(any)`;
64+
- if it rejects with an `Error` instance it calls `next(err)`;
65+
- if it rejects with anything else it wraps in a `AsyncError` and calls `next(err)` (error-handing**);
66+
67+
##### (*) Note about re-routing:
68+
The `8.x` API allows re-routing when calling `next()` with a string value. If the string matches a valid route,
69+
it will re-route to the given handler. The same is valid for resolving a async function. If the value returned by
70+
the async function is a string, it will try to re-route to the given handler.
71+
72+
##### (**) Note about error handling:
73+
Although it is recommended to always reject with an instance of Error, in a async function it is possible to
74+
throw or reject without returning an `Error` instance or even anything at all. In such cases, the value rejected
75+
will be wrapped on a `AsyncError`.
76+
77+
### Handler arity check
78+
Handlers expecting 2 or fewer parameters added to a `.pre()`, `.use()` or route chain must be async functions, as:
79+
80+
```js
81+
server.use(async (req, res) => {
82+
req.something = await doSomethingAsync();
83+
});
84+
```
85+
86+
Handlers expecting more than 2 parameters shouldn't be async functions, as:
87+
88+
````js
89+
// This middleware will be rejected and restify will throw
90+
server.use(async (req, res, next) => {
91+
doSomethingAsync(function callback(val) {
92+
req.something = val;
93+
next();
94+
});
95+
});
96+
````

lib/chain.js

+41-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
var assert = require('assert-plus');
44
var once = require('once');
5+
var customErrorTypes = require('./errorTypes');
56

67
module.exports = Chain;
78

@@ -71,6 +72,15 @@ Chain.prototype.getHandlers = function getHandlers() {
7172
* @returns {undefined} no return value
7273
*/
7374
Chain.prototype.add = function add(handler) {
75+
assert.func(handler);
76+
if (handler.length <= 2) {
77+
// arity <= 2, must be AsyncFunction
78+
assert.equal(handler.constructor.name, 'AsyncFunction');
79+
} else {
80+
// otherwise shouldn't be AsyncFunction
81+
assert.notEqual(handler.constructor.name, 'AsyncFunction');
82+
}
83+
7484
// _name is assigned in the server and router
7585
handler._name = handler._name || handler.name;
7686

@@ -144,7 +154,6 @@ Chain.prototype.run = function run(req, res, done) {
144154
*/
145155
function call(handler, err, req, res, _next) {
146156
var arity = handler.length;
147-
var error = err;
148157
var hasError = err === false || Boolean(err);
149158

150159
// Meassure handler timings
@@ -157,19 +166,47 @@ function call(handler, err, req, res, _next) {
157166
_next(nextErr, req, res);
158167
}
159168

169+
function resolve(value) {
170+
if (value && req.log) {
171+
// logs resolved value
172+
req.log.debug({ value }, 'Async handler resolved with a value');
173+
}
174+
175+
return next(value);
176+
}
177+
178+
function reject(error) {
179+
if (!(error instanceof Error)) {
180+
error = new customErrorTypes.AsyncError(
181+
{
182+
info: {
183+
cause: error,
184+
handler: handler._name,
185+
method: req.method,
186+
path: req.path ? req.path() : undefined
187+
}
188+
},
189+
'Async middleware rejected without an error'
190+
);
191+
}
192+
return next(error);
193+
}
194+
160195
if (hasError && arity === 4) {
161196
// error-handling middleware
162197
handler(err, req, res, next);
163198
return;
164199
} else if (!hasError && arity < 4) {
165200
// request-handling middleware
166201
process.nextTick(function nextTick() {
167-
handler(req, res, next);
202+
const result = handler(req, res, next);
203+
if (result && typeof result.then === 'function') {
204+
result.then(resolve, reject);
205+
}
168206
});
169207
return;
170208
}
171209

172210
// continue
173-
next(error, req, res);
174-
return;
211+
next(err);
175212
}

lib/errorTypes.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ var errors = require('restify-errors');
55
// This allows Restify to work with restify-errors v6+
66
module.exports = {
77
RequestCloseError: errors.makeConstructor('RequestCloseError'),
8-
RouteMissingError: errors.makeConstructor('RouteMissingError')
8+
RouteMissingError: errors.makeConstructor('RouteMissingError'),
9+
AsyncError: errors.makeConstructor('AsyncError')
910
};

lib/server.js

+44-4
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,13 @@ Server.prototype.close = function close(callback) {
367367
* res.send({ hello: 'world' });
368368
* next();
369369
* });
370+
* @example
371+
* <caption>using with async/await</caption>
372+
* server.get('/', function (req, res) {
373+
* await somethingAsync();
374+
* res.send({ hello: 'world' });
375+
* next();
376+
* }
370377
*/
371378
Server.prototype.get = serverMethodFactory('GET');
372379

@@ -474,9 +481,16 @@ Server.prototype.opts = serverMethodFactory('OPTIONS');
474481
* return next();
475482
* });
476483
* @example
484+
* <caption>using with async/await</caption>
485+
* server.pre(async function(req, res) {
486+
* await somethingAsync();
487+
* somethingSync();
488+
* }
489+
* @example
477490
* <caption>For example, `pre()` can be used to deduplicate slashes in
478491
* URLs</caption>
479492
* server.pre(restify.pre.dedupeSlashes());
493+
* @see {@link http://restify.com/docs/plugins-api/#serverpre-plugins|Restify pre() plugins}
480494
*/
481495
Server.prototype.pre = function pre() {
482496
var self = this;
@@ -575,6 +589,22 @@ Server.prototype.first = function first() {
575589
* * and/or a
576590
* variable number of nested arrays of handler functions
577591
* @returns {Object} returns self
592+
* @example
593+
* server.use(function(req, res, next) {
594+
* // do something...
595+
* return next();
596+
* });
597+
* @example
598+
* <caption>using with async/await</caption>
599+
* server.use(async function(req, res) {
600+
* await somethingAsync();
601+
* somethingSync();
602+
* }
603+
* @example
604+
* <caption>For example, `use()` can be used to attach a request logger
605+
* </caption>
606+
* server.pre(restify.plugins.requestLogger());
607+
* @see {@link http://restify.com/docs/plugins-api/#serveruse-plugins|Restify use() plugins}
578608
*/
579609
Server.prototype.use = function use() {
580610
var self = this;
@@ -596,12 +626,22 @@ Server.prototype.use = function use() {
596626
* new middleware function that only fires if the specified parameter exists
597627
* in req.params
598628
*
599-
* Exposes an API:
600-
* server.param("user", function (req, res, next) {
601-
* // load the user's information here, always making sure to call next()
629+
* @example
630+
* server.param("user", function (req, res, next) {
631+
* // load the user's information here, always making sure to call next()
632+
* fetchUserInformation(req, function callback(user) {
633+
* req.user = user;
634+
* next();
602635
* });
636+
* });
637+
* @example
638+
* <caption>using with async/await</caption>
639+
* server.param("user", async function(req, res) {
640+
* req.user = await fetchUserInformation(req);
641+
* somethingSync();
642+
* }
603643
*
604-
* @see http://expressjs.com/guide.html#route-param%20pre-conditions
644+
* @see {@link http://expressjs.com/guide.html#route-param%20pre-conditions| Express route param pre-conditions}
605645
* @public
606646
* @memberof Server
607647
* @instance

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"report-latency": "./bin/report-latency"
9191
},
9292
"engines": {
93-
"node": ">=10.21.0"
93+
"node": ">=10.0.0"
9494
},
9595
"dependencies": {
9696
"assert-plus": "^1.0.0",

0 commit comments

Comments
 (0)