Permalink
Browse files

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: #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>
  • Loading branch information...
addaleax committed Apr 14, 2017
1 parent e965ed1 commit e7c51454b0de0a6b33c15a4d77006a17ced4eeff
Showing with 102 additions and 2 deletions.
  1. +38 −0 doc/api/timers.md
  2. +24 −2 lib/timers.js
  3. +40 −0 test/parallel/test-timers-promisified.js
View
@@ -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
View
@@ -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();
@@ -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');
}));
}

0 comments on commit e7c5145

Please sign in to comment.