timers: add promisify support

Add support for `util.promisify(setTimeout)` and
`util.promisify(setImmediate)` as a proof-of-concept implementation.
`clearTimeout()` and `clearImmediate()` do not work on those Promises.
Includes documentation and tests.

PR-URL: https://github.com/nodejs/node/pull/12442
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: William Kapke <william.kapke@gmail.com>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Teddy Katz <teddy.katz@gmail.com>
This commit is contained in:
Anna Henningsen 2017-04-14 21:42:47 +02:00
parent e965ed16c1
commit e7c51454b0
No known key found for this signature in database
GPG Key ID: D8B9F5AEAE84E4CF
3 changed files with 102 additions and 2 deletions

View File

@ -85,6 +85,27 @@ next event loop iteration.
If `callback` is not a function, a [`TypeError`][] will be thrown. If `callback` is not a function, a [`TypeError`][] will be thrown.
*Note*: This method has a custom variant for promises that is available using
[`util.promisify()`][]:
```js
const util = require('util');
const setImmediatePromise = util.promisify(setImmediate);
setImmediatePromise('foobar').then((value) => {
// value === 'foobar' (passing values is optional)
// This is executed after all I/O callbacks.
});
// or with async function
async function timerExample() {
console.log('Before I/O callbacks');
await setImmediatePromise();
console.log('After I/O callbacks');
}
timerExample();
```
### setInterval(callback, delay[, ...args]) ### setInterval(callback, delay[, ...args])
<!-- YAML <!-- YAML
added: v0.0.1 added: v0.0.1
@ -126,12 +147,28 @@ will be set to `1`.
If `callback` is not a function, a [`TypeError`][] will be thrown. If `callback` is not a function, a [`TypeError`][] will be thrown.
*Note*: This method has a custom variant for promises that is available using
[`util.promisify()`][]:
```js
const util = require('util');
const setTimeoutPromise = util.promisify(setTimeout);
setTimeoutPromise(40, 'foobar').then((value) => {
// value === 'foobar' (passing values is optional)
// This is executed after about 40 milliseconds.
});
```
## Cancelling Timers ## Cancelling Timers
The [`setImmediate()`][], [`setInterval()`][], and [`setTimeout()`][] methods The [`setImmediate()`][], [`setInterval()`][], and [`setTimeout()`][] methods
each return objects that represent the scheduled timers. These can be used to each return objects that represent the scheduled timers. These can be used to
cancel the timer and prevent it from triggering. cancel the timer and prevent it from triggering.
It is not possible to cancel timers that were created using the promisified
variants of [`setImmediate()`][], [`setTimeout()`][].
### clearImmediate(immediate) ### clearImmediate(immediate)
<!-- YAML <!-- YAML
added: v0.9.1 added: v0.9.1
@ -168,4 +205,5 @@ Cancels a `Timeout` object created by [`setTimeout()`][].
[`setImmediate()`]: timers.html#timers_setimmediate_callback_args [`setImmediate()`]: timers.html#timers_setimmediate_callback_args
[`setInterval()`]: timers.html#timers_setinterval_callback_delay_args [`setInterval()`]: timers.html#timers_setinterval_callback_delay_args
[`setTimeout()`]: timers.html#timers_settimeout_callback_delay_args [`setTimeout()`]: timers.html#timers_settimeout_callback_delay_args
[`util.promisify()`]: util.html#util_util_promisify_original
[the Node.js Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick [the Node.js Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick

View File

@ -23,6 +23,8 @@
const TimerWrap = process.binding('timer_wrap').Timer; const TimerWrap = process.binding('timer_wrap').Timer;
const L = require('internal/linkedlist'); const L = require('internal/linkedlist');
const internalUtil = require('internal/util');
const { createPromise, promiseResolve } = process.binding('util');
const assert = require('assert'); const assert = require('assert');
const util = require('util'); const util = require('util');
const debug = util.debuglog('timer'); const debug = util.debuglog('timer');
@ -364,7 +366,7 @@ exports.enroll = function(item, msecs) {
*/ */
exports.setTimeout = function(callback, after, arg1, arg2, arg3) { function setTimeout(callback, after, arg1, arg2, arg3) {
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function'); throw new TypeError('"callback" argument must be a function');
} }
@ -383,8 +385,16 @@ exports.setTimeout = function(callback, after, arg1, arg2, arg3) {
} }
return createSingleTimeout(callback, after, args); return createSingleTimeout(callback, after, args);
}
setTimeout[internalUtil.promisify.custom] = function(after, value) {
const promise = createPromise();
createSingleTimeout(promise, after, [value]);
return promise;
}; };
exports.setTimeout = setTimeout;
function createSingleTimeout(callback, after, args) { function createSingleTimeout(callback, after, args) {
after *= 1; // coalesce to number or NaN after *= 1; // coalesce to number or NaN
if (!(after >= 1 && after <= TIMEOUT_MAX)) if (!(after >= 1 && after <= TIMEOUT_MAX))
@ -403,6 +413,8 @@ function createSingleTimeout(callback, after, args) {
function ontimeout(timer) { function ontimeout(timer) {
var args = timer._timerArgs; var args = timer._timerArgs;
var callback = timer._onTimeout; var callback = timer._onTimeout;
if (typeof callback !== 'function')
return promiseResolve(callback, args[0]);
if (!args) if (!args)
callback.call(timer); callback.call(timer);
else { else {
@ -687,6 +699,8 @@ function tryOnImmediate(immediate, oldTail) {
function runCallback(timer) { function runCallback(timer) {
const argv = timer._argv; const argv = timer._argv;
const argc = argv ? argv.length : 0; const argc = argv ? argv.length : 0;
if (typeof timer._callback !== 'function')
return promiseResolve(timer._callback, argv[0]);
switch (argc) { switch (argc) {
// fast-path callbacks with 0-3 arguments // fast-path callbacks with 0-3 arguments
case 0: case 0:
@ -715,7 +729,7 @@ function Immediate() {
this.domain = process.domain; this.domain = process.domain;
} }
exports.setImmediate = function(callback, arg1, arg2, arg3) { function setImmediate(callback, arg1, arg2, arg3) {
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function'); throw new TypeError('"callback" argument must be a function');
} }
@ -740,8 +754,16 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) {
break; break;
} }
return createImmediate(args, callback); return createImmediate(args, callback);
}
setImmediate[internalUtil.promisify.custom] = function(value) {
const promise = createPromise();
createImmediate([value], promise);
return promise;
}; };
exports.setImmediate = setImmediate;
function createImmediate(args, callback) { function createImmediate(args, callback) {
// declaring it `const immediate` causes v6.0.0 to deoptimize this function // declaring it `const immediate` causes v6.0.0 to deoptimize this function
var immediate = new Immediate(); var immediate = new Immediate();

View File

@ -0,0 +1,40 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const timers = require('timers');
const { promisify } = require('util');
/* eslint-disable no-restricted-syntax */
common.crashOnUnhandledRejection();
const setTimeout = promisify(timers.setTimeout);
const setImmediate = promisify(timers.setImmediate);
{
const promise = setTimeout(1);
promise.then(common.mustCall((value) => {
assert.strictEqual(value, undefined);
}));
}
{
const promise = setTimeout(1, 'foobar');
promise.then(common.mustCall((value) => {
assert.strictEqual(value, 'foobar');
}));
}
{
const promise = setImmediate();
promise.then(common.mustCall((value) => {
assert.strictEqual(value, undefined);
}));
}
{
const promise = setImmediate('foobar');
promise.then(common.mustCall((value) => {
assert.strictEqual(value, 'foobar');
}));
}