Skip to content

Commit

Permalink
lib: add Timer/Immediate to promisifyed setTimer/setImmediate
Browse files Browse the repository at this point in the history
The current implementation of the promisified setTimeout and
setImmediate does not make the Timeout or Immediate available, so they
cannot be canceled or unreffed.

The docs and code follow the pattern established by child_process
- lib/child_process.js#L150-L171
- doc/api/child_process.md#L217-L222
  • Loading branch information
Caleb ツ Everett committed Sep 10, 2019
1 parent f2e35ff commit c0367dc
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 8 deletions.
23 changes: 19 additions & 4 deletions doc/api/timers.md
Expand Up @@ -151,8 +151,9 @@ next event loop iteration.

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

This method has a custom variant for promises that is available using
[`util.promisify()`][]:
If this method is invoked as its [`util.promisify()`][]ed version, it returns a
Promise. The created `Immediate` is attached to the Promise as a `immediate`
property.

```js
const util = require('util');
Expand All @@ -170,6 +171,13 @@ async function timerExample() {
console.log('After I/O callbacks');
}
timerExample();

// You can access the immediate on promise.immediate
const promise = setImmediatePromise();
promise.then(() => {
// This callback is never called because the Immediate is cleared
});
clearImmediate(promise.immediate);
```

### setInterval(callback, delay[, ...args])
Expand Down Expand Up @@ -213,8 +221,9 @@ will be set to `1`. Non-integer delays are truncated to an integer.

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

This method has a custom variant for promises that is available using
[`util.promisify()`][]:
If this method is invoked as its [`util.promisify()`][]ed version, it returns a
Promise. The created `Timeout` is attached to the Promise as a `timeout`
property.

```js
const util = require('util');
Expand All @@ -224,6 +233,12 @@ setTimeoutPromise(40, 'foobar').then((value) => {
// value === 'foobar' (passing values is optional)
// This is executed after about 40 milliseconds.
});

const promise = setTimeoutPromise(40, 'foobar');
promise.then((value) => {
// This is never executed because the timeout is cleared
});
clearTimeout(promise.timeout);
```

## Cancelling Timers
Expand Down
13 changes: 9 additions & 4 deletions lib/timers.js
Expand Up @@ -147,9 +147,11 @@ function setTimeout(callback, after, arg1, arg2, arg3) {

setTimeout[customPromisify] = function(after, value) {
const args = value !== undefined ? [value] : value;
return new Promise((resolve) => {
active(new Timeout(resolve, after, args, false));
});
let resolve;
const promise = new Promise((res) => resolve = res);
promise.timeout = new Timeout(resolve, after, args, false);
active(promise.timeout);
return promise;
};

function clearTimeout(timer) {
Expand Down Expand Up @@ -272,7 +274,10 @@ function setImmediate(callback, arg1, arg2, arg3) {
}

setImmediate[customPromisify] = function(value) {
return new Promise((resolve) => new Immediate(resolve, [value]));
let resolve;
const promise = new Promise((res) => resolve = res);
promise.immediate = new Immediate(resolve, [value]);
return promise;
};

function clearImmediate(immediate) {
Expand Down
10 changes: 10 additions & 0 deletions test/parallel/test-timer-immediate-async-cleared.js
@@ -0,0 +1,10 @@
'use strict';
const { mustNotCall } = require('../common');
const { promisify } = require('util');

const setImmediateAsync = promisify(setImmediate);

const expected = ['foo', 'bar', 'baz'];
const promise = setImmediateAsync(...expected);
promise.then(() => mustNotCall('expected immediate to be cleared'));
clearImmediate(promise.immediate);
12 changes: 12 additions & 0 deletions test/parallel/test-timer-immediate-async.js
@@ -0,0 +1,12 @@
'use strict';
require('../common');
const { strictEqual } = require('assert');
const { promisify } = require('util');

const setImmediateAsync = promisify(setImmediate);

const expected = ['foo', 'bar', 'baz'];
// N.B. the promisified version of setImmediate will resolve with the _first_
// value, not an array of all values.
setImmediateAsync(...expected)
.then((actual) => strictEqual(actual, expected[0]));
10 changes: 10 additions & 0 deletions test/parallel/test-timer-timeout-async-cleared.js
@@ -0,0 +1,10 @@
'use strict';
const { mustNotCall } = require('../common');
const { promisify } = require('util');

const setTimeoutAsync = promisify(setTimeout);

const expected = ['foo', 'bar', 'baz'];
const promise = setTimeoutAsync(10, ...expected);
promise.then(() => mustNotCall('expected timeout to be cleared'));
clearTimeout(promise.timeout);
12 changes: 12 additions & 0 deletions test/parallel/test-timer-timeout-async.js
@@ -0,0 +1,12 @@
'use strict';
require('../common');
const { strictEqual } = require('assert');
const { promisify } = require('util');

const setTimeoutAsync = promisify(setTimeout);

const expected = ['foo', 'bar', 'baz'];
// N.B. the promisified version of setTimeout will resolve with the _first_
// value, not an array of all values.
setTimeoutAsync(10, ...expected)
.then((actual) => strictEqual(actual, expected[0]));

0 comments on commit c0367dc

Please sign in to comment.