Skip to content

Commit e7c5145

Browse files
committed
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 <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Myles Borins <[email protected]> Reviewed-By: Evan Lucas <[email protected]> Reviewed-By: William Kapke <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Teddy Katz <[email protected]>
1 parent e965ed1 commit e7c5145

File tree

3 files changed

+102
-2
lines changed

3 files changed

+102
-2
lines changed

doc/api/timers.md

+38
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,27 @@ next event loop iteration.
8585

8686
If `callback` is not a function, a [`TypeError`][] will be thrown.
8787

88+
*Note*: This method has a custom variant for promises that is available using
89+
[`util.promisify()`][]:
90+
91+
```js
92+
const util = require('util');
93+
const setImmediatePromise = util.promisify(setImmediate);
94+
95+
setImmediatePromise('foobar').then((value) => {
96+
// value === 'foobar' (passing values is optional)
97+
// This is executed after all I/O callbacks.
98+
});
99+
100+
// or with async function
101+
async function timerExample() {
102+
console.log('Before I/O callbacks');
103+
await setImmediatePromise();
104+
console.log('After I/O callbacks');
105+
}
106+
timerExample();
107+
```
108+
88109
### setInterval(callback, delay[, ...args])
89110
<!-- YAML
90111
added: v0.0.1
@@ -126,12 +147,28 @@ will be set to `1`.
126147

127148
If `callback` is not a function, a [`TypeError`][] will be thrown.
128149

150+
*Note*: This method has a custom variant for promises that is available using
151+
[`util.promisify()`][]:
152+
153+
```js
154+
const util = require('util');
155+
const setTimeoutPromise = util.promisify(setTimeout);
156+
157+
setTimeoutPromise(40, 'foobar').then((value) => {
158+
// value === 'foobar' (passing values is optional)
159+
// This is executed after about 40 milliseconds.
160+
});
161+
```
162+
129163
## Cancelling Timers
130164

131165
The [`setImmediate()`][], [`setInterval()`][], and [`setTimeout()`][] methods
132166
each return objects that represent the scheduled timers. These can be used to
133167
cancel the timer and prevent it from triggering.
134168

169+
It is not possible to cancel timers that were created using the promisified
170+
variants of [`setImmediate()`][], [`setTimeout()`][].
171+
135172
### clearImmediate(immediate)
136173
<!-- YAML
137174
added: v0.9.1
@@ -168,4 +205,5 @@ Cancels a `Timeout` object created by [`setTimeout()`][].
168205
[`setImmediate()`]: timers.html#timers_setimmediate_callback_args
169206
[`setInterval()`]: timers.html#timers_setinterval_callback_delay_args
170207
[`setTimeout()`]: timers.html#timers_settimeout_callback_delay_args
208+
[`util.promisify()`]: util.html#util_util_promisify_original
171209
[the Node.js Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick

lib/timers.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
const TimerWrap = process.binding('timer_wrap').Timer;
2525
const L = require('internal/linkedlist');
26+
const internalUtil = require('internal/util');
27+
const { createPromise, promiseResolve } = process.binding('util');
2628
const assert = require('assert');
2729
const util = require('util');
2830
const debug = util.debuglog('timer');
@@ -364,7 +366,7 @@ exports.enroll = function(item, msecs) {
364366
*/
365367

366368

367-
exports.setTimeout = function(callback, after, arg1, arg2, arg3) {
369+
function setTimeout(callback, after, arg1, arg2, arg3) {
368370
if (typeof callback !== 'function') {
369371
throw new TypeError('"callback" argument must be a function');
370372
}
@@ -383,8 +385,16 @@ exports.setTimeout = function(callback, after, arg1, arg2, arg3) {
383385
}
384386

385387
return createSingleTimeout(callback, after, args);
388+
}
389+
390+
setTimeout[internalUtil.promisify.custom] = function(after, value) {
391+
const promise = createPromise();
392+
createSingleTimeout(promise, after, [value]);
393+
return promise;
386394
};
387395

396+
exports.setTimeout = setTimeout;
397+
388398
function createSingleTimeout(callback, after, args) {
389399
after *= 1; // coalesce to number or NaN
390400
if (!(after >= 1 && after <= TIMEOUT_MAX))
@@ -403,6 +413,8 @@ function createSingleTimeout(callback, after, args) {
403413
function ontimeout(timer) {
404414
var args = timer._timerArgs;
405415
var callback = timer._onTimeout;
416+
if (typeof callback !== 'function')
417+
return promiseResolve(callback, args[0]);
406418
if (!args)
407419
callback.call(timer);
408420
else {
@@ -687,6 +699,8 @@ function tryOnImmediate(immediate, oldTail) {
687699
function runCallback(timer) {
688700
const argv = timer._argv;
689701
const argc = argv ? argv.length : 0;
702+
if (typeof timer._callback !== 'function')
703+
return promiseResolve(timer._callback, argv[0]);
690704
switch (argc) {
691705
// fast-path callbacks with 0-3 arguments
692706
case 0:
@@ -715,7 +729,7 @@ function Immediate() {
715729
this.domain = process.domain;
716730
}
717731

718-
exports.setImmediate = function(callback, arg1, arg2, arg3) {
732+
function setImmediate(callback, arg1, arg2, arg3) {
719733
if (typeof callback !== 'function') {
720734
throw new TypeError('"callback" argument must be a function');
721735
}
@@ -740,8 +754,16 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) {
740754
break;
741755
}
742756
return createImmediate(args, callback);
757+
}
758+
759+
setImmediate[internalUtil.promisify.custom] = function(value) {
760+
const promise = createPromise();
761+
createImmediate([value], promise);
762+
return promise;
743763
};
744764

765+
exports.setImmediate = setImmediate;
766+
745767
function createImmediate(args, callback) {
746768
// declaring it `const immediate` causes v6.0.0 to deoptimize this function
747769
var immediate = new Immediate();
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const timers = require('timers');
5+
const { promisify } = require('util');
6+
7+
/* eslint-disable no-restricted-syntax */
8+
9+
common.crashOnUnhandledRejection();
10+
11+
const setTimeout = promisify(timers.setTimeout);
12+
const setImmediate = promisify(timers.setImmediate);
13+
14+
{
15+
const promise = setTimeout(1);
16+
promise.then(common.mustCall((value) => {
17+
assert.strictEqual(value, undefined);
18+
}));
19+
}
20+
21+
{
22+
const promise = setTimeout(1, 'foobar');
23+
promise.then(common.mustCall((value) => {
24+
assert.strictEqual(value, 'foobar');
25+
}));
26+
}
27+
28+
{
29+
const promise = setImmediate();
30+
promise.then(common.mustCall((value) => {
31+
assert.strictEqual(value, undefined);
32+
}));
33+
}
34+
35+
{
36+
const promise = setImmediate('foobar');
37+
promise.then(common.mustCall((value) => {
38+
assert.strictEqual(value, 'foobar');
39+
}));
40+
}

0 commit comments

Comments
 (0)