GeezerVison is showing github.com — timers: add experimental scheduler api · nodejs/node@9c213cf
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/timers.md +45Lines changed: 45 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -472,7 +472,52 @@ const interval = 100; | 472 | 472 | })(); | 473 | 473 | ``` | 474 | 474 |
| | 475 | +### `timersPromises.scheduler.wait(delay[, options])` | | 476 | + | | 477 | +<!-- YAML | | 478 | +added: REPLACEME | | 479 | +--> | | 480 | + | | 481 | +> Stability: 1 - Experimental | | 482 | +
| | 483 | +* `delay` {number} The number of milliseconds to wait before resolving the | | 484 | + promise. | | 485 | +* `options` {Object} | | 486 | + * `signal` {AbortSignal} An optional `AbortSignal` that can be used to | | 487 | + cancel waiting. | | 488 | +* Returns: {Promise} | | 489 | + | | 490 | +An experimental API defined by the [Scheduling APIs][] draft specification | | 491 | +being developed as a standard Web Platform API. | | 492 | + | | 493 | +Calling `timersPromises.scheduler.wait(delay, options)` is roughly equivalent | | 494 | +to calling `timersPromises.setTimeout(delay, undefined, options)` except that | | 495 | +the `ref` option is not supported. | | 496 | + | | 497 | +```mjs | | 498 | +import { scheduler } from 'timers/promises'; | | 499 | + | | 500 | +await scheduler.wait(1000); // Wait one second before continuing | | 501 | +``` | | 502 | + | | 503 | +### `timersPromises.scheduler.yield()` | | 504 | + | | 505 | +<!-- YAML | | 506 | +added: REPLACEME | | 507 | +--> | | 508 | + | | 509 | +> Stability: 1 - Experimental | | 510 | +
| | 511 | +* Returns: {Promise} | | 512 | + | | 513 | +An experimental API defined by the [Scheduling APIs][] draft specification | | 514 | +being developed as a standard Web Platform API. | | 515 | + | | 516 | +Calling `timersPromises.scheduler.yield()` is equivalent to calling | | 517 | +`timersPromises.setImmediate()` with no arguments. | | 518 | + | 475 | 519 | [Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout | | 520 | +[Scheduling APIs]: https://github.com/WICG/scheduling-apis | 476 | 521 | [`AbortController`]: globals.md#class-abortcontroller | 477 | 522 | [`TypeError`]: errors.md#class-typeerror | 478 | 523 | [`clearImmediate()`]: #clearimmediateimmediate | |
Collapse file Copy file name to clipboardExpand all lines: lib/timers/promises.js +46-1Lines changed: 46 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 4 | 4 | FunctionPrototypeBind, | 5 | 5 | Promise, | 6 | 6 | PromiseReject, | | 7 | + ReflectConstruct, | 7 | 8 | SafePromisePrototypeFinally, | | 9 | + Symbol, | 8 | 10 | } = primordials; | 9 | 11 |
| 10 | 12 | const { | @@ -15,7 +17,11 @@ const { | 15 | 17 |
| 16 | 18 | const { | 17 | 19 | AbortError, | 18 | | - codes: { ERR_INVALID_ARG_TYPE } | | 20 | + codes: { | | 21 | + ERR_ILLEGAL_CONSTRUCTOR, | | 22 | + ERR_INVALID_ARG_TYPE, | | 23 | + ERR_INVALID_THIS, | | 24 | + } | 19 | 25 | } = require('internal/errors'); | 20 | 26 |
| 21 | 27 | const { | @@ -24,6 +30,8 @@ const { | 24 | 30 | validateObject, | 25 | 31 | } = require('internal/validators'); | 26 | 32 |
| | 33 | +const kScheduler = Symbol('kScheduler'); | | 34 | + | 27 | 35 | function cancelListenerHandler(clear, reject, signal) { | 28 | 36 | if (!this._destroyed) { | 29 | 37 | clear(this); | @@ -173,8 +181,45 @@ async function* setInterval(after, value, options = {}) { | 173 | 181 | } | 174 | 182 | } | 175 | 183 |
| | 184 | +// TODO(@jasnell): Scheduler is an API currently being discussed by WICG | | 185 | +// for Web Platform standardization: https://github.com/WICG/scheduling-apis | | 186 | +// The scheduler.yield() and scheduler.wait() methods correspond roughly to | | 187 | +// the awaitable setTimeout and setImmediate implementations here. This api | | 188 | +// should be considered to be experimental until the spec for these are | | 189 | +// finalized. Note, also, that Scheduler is expected to be defined as a global, | | 190 | +// but while the API is experimental we shouldn't expose it as such. | | 191 | +class Scheduler { | | 192 | + constructor() { | | 193 | + throw new ERR_ILLEGAL_CONSTRUCTOR(); | | 194 | + } | | 195 | + | | 196 | + /** | | 197 | + * @returns {Promise<void>} | | 198 | + */ | | 199 | + yield() { | | 200 | + if (!this[kScheduler]) | | 201 | + throw new ERR_INVALID_THIS('Scheduler'); | | 202 | + return setImmediate(); | | 203 | + } | | 204 | + | | 205 | + /** | | 206 | + * @typedef {import('../internal/abort_controller').AbortSignal} AbortSignal | | 207 | + * @param {number} delay | | 208 | + * @param {{ signal?: AbortSignal }} [options] | | 209 | + * @returns {Promise<void>} | | 210 | + */ | | 211 | + wait(delay, options) { | | 212 | + if (!this[kScheduler]) | | 213 | + throw new ERR_INVALID_THIS('Scheduler'); | | 214 | + return setTimeout(delay, undefined, { signal: options?.signal }); | | 215 | + } | | 216 | +} | | 217 | + | 176 | 218 | module.exports = { | 177 | 219 | setTimeout, | 178 | 220 | setImmediate, | 179 | 221 | setInterval, | | 222 | + scheduler: ReflectConstruct(function() { | | 223 | + this[kScheduler] = true; | | 224 | + }, [], Scheduler), | 180 | 225 | }; |
Collapse file Copy file name to clipboard +50Lines changed: 50 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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()); |
|