mirror of https://github.com/nodejs/node.git
timers: add experimental scheduler api
Adds experimental implementations of the yield and wait APIs being explored at https://github.com/WICG/scheduling-apis. When I asked the WHATWG folks about the possibility of standardizing the [awaitable versions of setTimeout/setImmediate](https://github.com/whatwg/html/issues/7340) that we have implemented in `timers/promises`, they pointed at the work in progress scheduling APIs draft as they direction they'll be going. While there is definitely a few thing in that draft that have questionable utility to Node.js, the yield and wait APIs map cleanly to the setImmediate and setTimeout we already have. Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/40909 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Darshan Sen <raisinten@gmail.com>
This commit is contained in:
parent
6ef6bdf1fd
commit
8bce46aff1
|
@ -472,7 +472,52 @@ const interval = 100;
|
|||
})();
|
||||
```
|
||||
|
||||
### `timersPromises.scheduler.wait(delay[, options])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1 - Experimental
|
||||
|
||||
* `delay` {number} The number of milliseconds to wait before resolving the
|
||||
promise.
|
||||
* `options` {Object}
|
||||
* `signal` {AbortSignal} An optional `AbortSignal` that can be used to
|
||||
cancel waiting.
|
||||
* Returns: {Promise}
|
||||
|
||||
An experimental API defined by the [Scheduling APIs][] draft specification
|
||||
being developed as a standard Web Platform API.
|
||||
|
||||
Calling `timersPromises.scheduler.wait(delay, options)` is roughly equivalent
|
||||
to calling `timersPromises.setTimeout(delay, undefined, options)` except that
|
||||
the `ref` option is not supported.
|
||||
|
||||
```mjs
|
||||
import { scheduler } from 'timers/promises';
|
||||
|
||||
await scheduler.wait(1000); // Wait one second before continuing
|
||||
```
|
||||
|
||||
### `timersPromises.scheduler.yield()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1 - Experimental
|
||||
|
||||
* Returns: {Promise}
|
||||
|
||||
An experimental API defined by the [Scheduling APIs][] draft specification
|
||||
being developed as a standard Web Platform API.
|
||||
|
||||
Calling `timersPromises.scheduler.yield()` is equivalent to calling
|
||||
`timersPromises.setImmediate()` with no arguments.
|
||||
|
||||
[Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout
|
||||
[Scheduling APIs]: https://github.com/WICG/scheduling-apis
|
||||
[`AbortController`]: globals.md#class-abortcontroller
|
||||
[`TypeError`]: errors.md#class-typeerror
|
||||
[`clearImmediate()`]: #clearimmediateimmediate
|
||||
|
|
|
@ -4,7 +4,9 @@ const {
|
|||
FunctionPrototypeBind,
|
||||
Promise,
|
||||
PromiseReject,
|
||||
ReflectConstruct,
|
||||
SafePromisePrototypeFinally,
|
||||
Symbol,
|
||||
} = primordials;
|
||||
|
||||
const {
|
||||
|
@ -15,7 +17,11 @@ const {
|
|||
|
||||
const {
|
||||
AbortError,
|
||||
codes: { ERR_INVALID_ARG_TYPE }
|
||||
codes: {
|
||||
ERR_ILLEGAL_CONSTRUCTOR,
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_INVALID_THIS,
|
||||
}
|
||||
} = require('internal/errors');
|
||||
|
||||
const {
|
||||
|
@ -24,6 +30,8 @@ const {
|
|||
validateObject,
|
||||
} = require('internal/validators');
|
||||
|
||||
const kScheduler = Symbol('kScheduler');
|
||||
|
||||
function cancelListenerHandler(clear, reject, signal) {
|
||||
if (!this._destroyed) {
|
||||
clear(this);
|
||||
|
@ -173,8 +181,45 @@ async function* setInterval(after, value, options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(@jasnell): Scheduler is an API currently being discussed by WICG
|
||||
// for Web Platform standardization: https://github.com/WICG/scheduling-apis
|
||||
// The scheduler.yield() and scheduler.wait() methods correspond roughly to
|
||||
// the awaitable setTimeout and setImmediate implementations here. This api
|
||||
// should be considered to be experimental until the spec for these are
|
||||
// finalized. Note, also, that Scheduler is expected to be defined as a global,
|
||||
// but while the API is experimental we shouldn't expose it as such.
|
||||
class Scheduler {
|
||||
constructor() {
|
||||
throw new ERR_ILLEGAL_CONSTRUCTOR();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
yield() {
|
||||
if (!this[kScheduler])
|
||||
throw new ERR_INVALID_THIS('Scheduler');
|
||||
return setImmediate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('../internal/abort_controller').AbortSignal} AbortSignal
|
||||
* @param {number} delay
|
||||
* @param {{ signal?: AbortSignal }} [options]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
wait(delay, options) {
|
||||
if (!this[kScheduler])
|
||||
throw new ERR_INVALID_THIS('Scheduler');
|
||||
return setTimeout(delay, undefined, { signal: options?.signal });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setTimeout,
|
||||
setImmediate,
|
||||
setInterval,
|
||||
scheduler: ReflectConstruct(function() {
|
||||
this[kScheduler] = true;
|
||||
}, [], Scheduler),
|
||||
};
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
const { scheduler } = require('timers/promises');
|
||||
const { setTimeout } = require('timers');
|
||||
const {
|
||||
strictEqual,
|
||||
rejects,
|
||||
} = require('assert');
|
||||
|
||||
async function testYield() {
|
||||
await scheduler.yield();
|
||||
process.emit('foo');
|
||||
}
|
||||
testYield().then(common.mustCall());
|
||||
queueMicrotask(common.mustCall(() => {
|
||||
process.addListener('foo', common.mustCall());
|
||||
}));
|
||||
|
||||
async function testWait() {
|
||||
let value = 0;
|
||||
setTimeout(() => value++, 10);
|
||||
await scheduler.wait(15);
|
||||
strictEqual(value, 1);
|
||||
}
|
||||
|
||||
testWait().then(common.mustCall());
|
||||
|
||||
async function testCancelableWait1() {
|
||||
const ac = new AbortController();
|
||||
const wait = scheduler.wait(1e6, { signal: ac.signal });
|
||||
ac.abort();
|
||||
await rejects(wait, {
|
||||
code: 'ABORT_ERR',
|
||||
message: 'The operation was aborted',
|
||||
});
|
||||
}
|
||||
|
||||
testCancelableWait1().then(common.mustCall());
|
||||
|
||||
async function testCancelableWait2() {
|
||||
const wait = scheduler.wait(10000, { signal: AbortSignal.abort() });
|
||||
await rejects(wait, {
|
||||
code: 'ABORT_ERR',
|
||||
message: 'The operation was aborted',
|
||||
});
|
||||
}
|
||||
|
||||
testCancelableWait2().then(common.mustCall());
|
Loading…
Reference in New Issue