Skip to content

Commit

Permalink
timers: add promisify support
Browse files Browse the repository at this point in the history
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: nodejs#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 authored and Olivier Martin committed May 19, 2017
1 parent 5c74efe commit 2c904cc
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 2 deletions.
38 changes: 38 additions & 0 deletions doc/api/timers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 changes: 24 additions & 2 deletions lib/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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');
}
Expand All @@ -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))
Expand All @@ -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 {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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');
}
Expand All @@ -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();
Expand Down
40 changes: 40 additions & 0 deletions test/parallel/test-timers-promisified.js
Original file line number Diff line number Diff line change
@@ -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 2c904cc

Please sign in to comment.