Skip to content

Commit 55ebbfe

Browse files
committed
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](whatwg/html#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>
1 parent 129c12e commit 55ebbfe

File tree

3 files changed

+113
-0
lines changed

3 files changed

+113
-0
lines changed

doc/api/timers.md

+37
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,44 @@ const interval = 100;
472472
})();
473473
```
474474

475+
### `timersPromises.scheduler.wait(delay[, options])`
476+
477+
> Stability: 1 - Experimental
478+
479+
* `delay` {number} The number of milliseconds to wait before resolving the
480+
promise.
481+
* `options` {object}
482+
* `signal` {AbortSignal} An optional `AbortSignal` that can be used to
483+
cancel waiting.
484+
* Returns: {Promise}
485+
486+
An experimental API defined by the [Scheduling APIs][] draft specification
487+
being developed as a standard Web Platform API.
488+
489+
Calling `timersPromises.scheduler.wait(delay, options)` is roughly equivalent
490+
to calling `timersPromises.setTimeout(delay, undefined, options)` except that
491+
the `ref` option is not supported.
492+
493+
```mjs
494+
import { scheduler } from 'timers/promises';
495+
496+
await scheduler.wait(1000); // Wait one second before continuing
497+
```
498+
499+
### `timersPromises.scheduler.yield()`
500+
501+
> Stability: 1 - Experimental
502+
503+
* Returns: {Promise}
504+
505+
An experimental API defined by the [Scheduling APIs][] draft specification
506+
being developed as a standard Web Platform API.
507+
508+
Calling `timersPromises.scheduler.yield()` is equivalent to calling
509+
`timersPromises.setImmediate()` with no arguments.
510+
475511
[Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout
512+
[Scheduling APIs]: https://github.com/WICG/scheduling-apis
476513
[`AbortController`]: globals.md#class-abortcontroller
477514
[`TypeError`]: errors.md#class-typeerror
478515
[`clearImmediate()`]: #clearimmediateimmediate

lib/timers/promises.js

+26
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,34 @@ async function* setInterval(after, value, options = {}) {
170170
}
171171
}
172172

173+
// TODO(@jasnell): Scheduler is an API currently being discussed by WICG
174+
// for Web Platform standardization: https://github.com/WICG/scheduling-apis
175+
// The scheduler.yield() and scheduler.wait() methods correspond roughly to
176+
// the awaitable setTimeout and setImmediate implementations here. This api
177+
// should be considered to be experimental until the spec for these are
178+
// finalized. Note, also, that Scheduler is expected to be defined as a global,
179+
// but while the API is experimental we shouldn't expose it as such.
180+
class Scheduler {
181+
/**
182+
* @returns {Promise<void>}
183+
*/
184+
yield() { return setImmediate(); }
185+
186+
/**
187+
* @typedef {import('../internal/abort_controller').AbortSignal} AbortSignal
188+
* @param {number} delay
189+
* @param {{ signal? : AbortSignal }} [options]
190+
* @returns {Promise<void>}
191+
*/
192+
wait(delay, options) {
193+
194+
return setTimeout(delay, undefined, { signal: options?.signal });
195+
}
196+
}
197+
173198
module.exports = {
174199
setTimeout,
175200
setImmediate,
176201
setInterval,
202+
scheduler: new Scheduler(),
177203
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
const { scheduler } = require('timers/promises');
6+
const { setTimeout } = require('timers');
7+
const {
8+
strictEqual,
9+
rejects,
10+
} = require('assert');
11+
12+
async function testYield() {
13+
await scheduler.yield();
14+
process.emit('foo');
15+
}
16+
testYield().then(common.mustCall());
17+
queueMicrotask(common.mustCall(() => {
18+
process.addListener('foo', common.mustCall());
19+
}));
20+
21+
async function testWait() {
22+
let value = 0;
23+
setTimeout(() => value++, 10);
24+
await scheduler.wait(15);
25+
strictEqual(value, 1);
26+
}
27+
28+
testWait().then(common.mustCall());
29+
30+
async function testCancelableWait1() {
31+
const ac = new AbortController();
32+
const wait = scheduler.wait(1e6, { signal: ac.signal });
33+
ac.abort();
34+
await rejects(wait, {
35+
code: 'ABORT_ERR',
36+
message: 'The operation was aborted',
37+
});
38+
}
39+
40+
testCancelableWait1().then(common.mustCall());
41+
42+
async function testCancelableWait2() {
43+
const wait = scheduler.wait(10000, { signal: AbortSignal.abort() });
44+
await rejects(wait, {
45+
code: 'ABORT_ERR',
46+
message: 'The operation was aborted',
47+
});
48+
}
49+
50+
testCancelableWait2().then(common.mustCall());

0 commit comments

Comments
 (0)