Browse Source

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>
v6
Anna Henningsen 8 years ago
parent
commit
e7c51454b0
No known key found for this signature in database GPG Key ID: D8B9F5AEAE84E4CF
  1. 38
      doc/api/timers.md
  2. 26
      lib/timers.js
  3. 40
      test/parallel/test-timers-promisified.js

38
doc/api/timers.md

@ -85,6 +85,27 @@ next event loop iteration.
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])
<!-- YAML
added: v0.0.1
@ -126,12 +147,28 @@ will be set to `1`.
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
The [`setImmediate()`][], [`setInterval()`][], and [`setTimeout()`][] methods
each return objects that represent the scheduled timers. These can be used to
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)
<!-- YAML
added: v0.9.1
@ -168,4 +205,5 @@ Cancels a `Timeout` object created by [`setTimeout()`][].
[`setImmediate()`]: timers.html#timers_setimmediate_callback_args
[`setInterval()`]: timers.html#timers_setinterval_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

26
lib/timers.js

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

40
test/parallel/test-timers-promisified.js

@ -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');
}));
}
Loading…
Cancel
Save