Sitelet https://web.archive.org/web/20211206035858/https://github.com/nodejs/node/commit/a8904e8eee
Skip to content
Permalink
Browse files
timers: introduce timers/promises
Move the promisified timers implementations into a new sub-module
to avoid the need to promisify. The promisified versions now return
the timers/promises versions.

Also adds `ref` option to the promisified versions

```js
const {
  setTimeout,
  setImmediate
} = require('timers/promises');

setTimeout(10, null, { ref: false })
  .then(console.log);

setImmediate(null, { ref: false })
  .then(console.log);

```

Signed-off-by: James M Snell <jasnell@gmail.com>

PR-URL: #33950
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
  • Loading branch information
jasnell committed Jun 22, 2020
1 parent bfc0e3f commit a8904e8eeea3ca513de588424bb99f6a7cdfc598
Showing with 250 additions and 127 deletions.
  1. +34 −0 doc/api/timers.md
  2. +43 −1 lib/internal/timers.js
  3. +19 −126 lib/timers.js
  4. +124 −0 lib/timers/promises.js
  5. +1 −0 node.gyp
  6. +29 −0 test/parallel/test-timers-promisified.js
@@ -302,6 +302,40 @@ added: v0.0.1

Cancels a `Timeout` object created by [`setTimeout()`][].

## Timers Promises API

> Stability: 1 - Experimental
The `timers/promises` API provides an alternative set of timer functions
that return `Promise` objects. The API is accessible via
`require('timers/promises')`.

```js
const timersPromises = require('timers/promises');
```

### `timersPromises.setTimeout(delay\[, value\[, options\]\])
* `delay` {number} The number of milliseconds to wait before resolving the
`Promise`.
* `value` {any} A value with which the `Promise` is resolved.
* `options` {Object}
* `ref` {boolean} Set to `false` to indicate that the scheduled `Timeout`
should not require the Node.js event loop to remain active.
**Default**: `true`.
* `signal` {AbortSignal} An optional `AbortSignal` that can be used to
cancel the scheduled `Timeout`.

### `timersPromises.setImmediate(\[value\[, options\]\])
* `value` {any} A value with which the `Promise` is resolved.
* `options` {Object}
* `ref` {boolean} Set to `false` to indicate that the scheduled `Immediate`
should not require the Node.js event loop to remain active.
**Default**: `true`.
* `signal` {AbortSignal} An optional `AbortSignal` that can be used to
cancel the scheduled `Immediate`.

[Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout
[`AbortController`]: globals.html#globals_class_abortcontroller
[`TypeError`]: errors.html#errors_class_typeerror
@@ -84,7 +84,8 @@ const {
scheduleTimer,
toggleTimerRef,
getLibuvNow,
immediateInfo
immediateInfo,
toggleImmediateRef
} = internalBinding('timers');

const {
@@ -590,12 +591,53 @@ function getTimerCallbacks(runNextTicks) {
};
}

class Immediate {
constructor(callback, args) {
this._idleNext = null;
this._idlePrev = null;
this._onImmediate = callback;
this._argv = args;
this._destroyed = false;
this[kRefed] = false;

initAsyncResource(this, 'Immediate');

this.ref();
immediateInfo[kCount]++;

immediateQueue.append(this);
}

ref() {
if (this[kRefed] === false) {
this[kRefed] = true;
if (immediateInfo[kRefCount]++ === 0)
toggleImmediateRef(true);
}
return this;
}

unref() {
if (this[kRefed] === true) {
this[kRefed] = false;
if (--immediateInfo[kRefCount] === 0)
toggleImmediateRef(false);
}
return this;
}

hasRef() {
return !!this[kRefed];
}
}

module.exports = {
TIMEOUT_MAX,
kTimeout: Symbol('timeout'), // For hiding Timeouts on other internals.
async_id_symbol,
trigger_async_id_symbol,
Timeout,
Immediate,
kRefed,
initAsyncResource,
setUnrefTimeout,
@@ -23,15 +23,9 @@

const {
MathTrunc,
Promise,
Object,
} = primordials;

const {
codes: { ERR_INVALID_ARG_TYPE }
} = require('internal/errors');

let DOMException;

const {
immediateInfo,
toggleImmediateRef
@@ -40,13 +34,13 @@ const L = require('internal/linkedlist');
const {
async_id_symbol,
Timeout,
Immediate,
decRefCount,
immediateInfoFields: {
kCount,
kRefCount
},
kRefed,
initAsyncResource,
getTimerDuration,
timerListMap,
timerListQueue,
@@ -64,6 +58,8 @@ let debug = require('internal/util/debuglog').debuglog('timer', (fn) => {
});
const { validateCallback } = require('internal/validators');

let timersPromises;

const {
destroyHooksExist,
// The needed emit*() functions.
@@ -124,12 +120,6 @@ function enroll(item, msecs) {
* DOM-style timers
*/

function lazyDOMException(message) {
if (DOMException === undefined)
DOMException = internalBinding('messaging').DOMException;
return new DOMException(message);
}

function setTimeout(callback, after, arg1, arg2, arg3) {
validateCallback(callback);

@@ -160,44 +150,14 @@ function setTimeout(callback, after, arg1, arg2, arg3) {
return timeout;
}

setTimeout[customPromisify] = function(after, value, options = {}) {
const args = value !== undefined ? [value] : value;
if (options == null || typeof options !== 'object') {
return Promise.reject(
new ERR_INVALID_ARG_TYPE(
'options',
'Object',
options));
Object.defineProperty(setTimeout, customPromisify, {
enumerable: true,
get() {
if (!timersPromises)
timersPromises = require('timers/promises');
return timersPromises.setTimeout;
}
const { signal } = options;
if (signal !== undefined &&
(signal === null ||
typeof signal !== 'object' ||
!('aborted' in signal))) {
return Promise.reject(
new ERR_INVALID_ARG_TYPE(
'options.signal',
'AbortSignal',
signal));
}
// TODO(@jasnell): If a decision is made that this cannot be backported
// to 12.x, then this can be converted to use optional chaining to
// simplify the check.
if (signal && signal.aborted)
return Promise.reject(lazyDOMException('AbortError'));
return new Promise((resolve, reject) => {
const timeout = new Timeout(resolve, after, args, false, true);
insert(timeout, timeout._idleTimeout);
if (signal) {
signal.addEventListener('abort', () => {
if (!timeout._destroyed) {
clearTimeout(timeout);
reject(lazyDOMException('AbortError'));
}
}, { once: true });
}
});
};
});

function clearTimeout(timer) {
if (timer && timer._onTimeout) {
@@ -248,46 +208,6 @@ Timeout.prototype.close = function() {
return this;
};

const Immediate = class Immediate {
constructor(callback, args) {
this._idleNext = null;
this._idlePrev = null;
this._onImmediate = callback;
this._argv = args;
this._destroyed = false;
this[kRefed] = false;

initAsyncResource(this, 'Immediate');

this.ref();
immediateInfo[kCount]++;

immediateQueue.append(this);
}

ref() {
if (this[kRefed] === false) {
this[kRefed] = true;
if (immediateInfo[kRefCount]++ === 0)
toggleImmediateRef(true);
}
return this;
}

unref() {
if (this[kRefed] === true) {
this[kRefed] = false;
if (--immediateInfo[kRefCount] === 0)
toggleImmediateRef(false);
}
return this;
}

hasRef() {
return !!this[kRefed];
}
};

function setImmediate(callback, arg1, arg2, arg3) {
validateCallback(callback);

@@ -314,42 +234,15 @@ function setImmediate(callback, arg1, arg2, arg3) {
return new Immediate(callback, args);
}

setImmediate[customPromisify] = function(value, options = {}) {
if (options == null || typeof options !== 'object') {
return Promise.reject(
new ERR_INVALID_ARG_TYPE(
'options',
'Object',
options));
Object.defineProperty(setImmediate, customPromisify, {
enumerable: true,
get() {
if (!timersPromises)
timersPromises = require('timers/promises');
return timersPromises.setImmediate;
}
const { signal } = options;
if (signal !== undefined &&
(signal === null ||
typeof signal !== 'object' ||
!('aborted' in signal))) {
return Promise.reject(
new ERR_INVALID_ARG_TYPE(
'options.signal',
'AbortSignal',
signal));
}
// TODO(@jasnell): If a decision is made that this cannot be backported
// to 12.x, then this can be converted to use optional chaining to
// simplify the check.
if (signal && signal.aborted)
return Promise.reject(lazyDOMException('AbortError'));
return new Promise((resolve, reject) => {
const immediate = new Immediate(resolve, [value]);
if (signal) {
signal.addEventListener('abort', () => {
if (!immediate._destroyed) {
clearImmediate(immediate);
reject(lazyDOMException('AbortError'));
}
}, { once: true });
}
});
};
});


function clearImmediate(immediate) {
if (!immediate || immediate._destroyed)
Loading

0 comments on commit a8904e8

Please sign in to comment.