Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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 May 9, 2017
commit e7c51454b0de0a6b33c15a4d77006a17ced4eeff
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.
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An async function example would be cool as well.


// 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a note here that it is not yet possible to cancel a timer promise given that there is no standard way of canceling a promise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, but I didn’t mention the reason for why that doesn’t exist – I don’t think we do that in our docs

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');
}));
}