2020-06-18 22:22:17 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const {
|
2020-11-15 18:12:43 +01:00
|
|
|
FunctionPrototypeBind,
|
2020-06-18 22:22:17 +02:00
|
|
|
Promise,
|
|
|
|
PromiseReject,
|
2024-11-06 18:17:31 +01:00
|
|
|
PromiseWithResolvers,
|
2021-11-21 17:04:21 +01:00
|
|
|
ReflectConstruct,
|
2021-05-12 12:16:43 +02:00
|
|
|
SafePromisePrototypeFinally,
|
2021-11-21 17:04:21 +01:00
|
|
|
Symbol,
|
2020-06-18 22:22:17 +02:00
|
|
|
} = primordials;
|
|
|
|
|
|
|
|
const {
|
|
|
|
Timeout,
|
|
|
|
Immediate,
|
2023-02-16 18:47:24 +01:00
|
|
|
insert,
|
2020-06-18 22:22:17 +02:00
|
|
|
} = require('internal/timers');
|
2022-02-18 12:37:36 +01:00
|
|
|
const {
|
|
|
|
clearImmediate,
|
|
|
|
clearInterval,
|
|
|
|
clearTimeout,
|
|
|
|
} = require('timers');
|
2020-06-18 22:22:17 +02:00
|
|
|
|
|
|
|
const {
|
2020-11-29 19:01:24 +01:00
|
|
|
AbortError,
|
2021-11-21 17:04:21 +01:00
|
|
|
codes: {
|
|
|
|
ERR_ILLEGAL_CONSTRUCTOR,
|
|
|
|
ERR_INVALID_THIS,
|
2023-02-16 18:47:24 +01:00
|
|
|
},
|
2020-06-18 22:22:17 +02:00
|
|
|
} = require('internal/errors');
|
|
|
|
|
2021-01-31 00:16:18 +01:00
|
|
|
const {
|
|
|
|
validateAbortSignal,
|
|
|
|
validateBoolean,
|
|
|
|
validateObject,
|
2024-08-25 09:30:10 +02:00
|
|
|
validateNumber,
|
2021-01-31 00:16:18 +01:00
|
|
|
} = require('internal/validators');
|
2020-12-22 16:23:23 +01:00
|
|
|
|
2022-05-21 11:56:32 +02:00
|
|
|
const {
|
|
|
|
kEmptyObject,
|
|
|
|
} = require('internal/util');
|
|
|
|
|
2021-11-21 17:04:21 +01:00
|
|
|
const kScheduler = Symbol('kScheduler');
|
2023-06-24 17:52:38 +02:00
|
|
|
let kResistStopPropagation;
|
2021-11-21 17:04:21 +01:00
|
|
|
|
2021-11-28 21:38:42 +01:00
|
|
|
function cancelListenerHandler(clear, reject, signal) {
|
2020-11-06 16:07:43 +01:00
|
|
|
if (!this._destroyed) {
|
|
|
|
clear(this);
|
2021-11-28 21:38:42 +01:00
|
|
|
reject(new AbortError(undefined, { cause: signal?.reason }));
|
2020-11-06 16:07:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-21 11:56:32 +02:00
|
|
|
function setTimeout(after, value, options = kEmptyObject) {
|
2020-12-22 16:23:23 +01:00
|
|
|
try {
|
2024-08-25 09:30:10 +02:00
|
|
|
if (typeof after !== 'undefined') {
|
|
|
|
validateNumber(after, 'delay');
|
|
|
|
}
|
|
|
|
|
|
|
|
validateObject(options, 'options');
|
|
|
|
|
|
|
|
if (typeof options?.signal !== 'undefined') {
|
|
|
|
validateAbortSignal(options.signal, 'options.signal');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof options?.ref !== 'undefined') {
|
|
|
|
validateBoolean(options.ref, 'options.ref');
|
|
|
|
}
|
2020-12-22 16:23:23 +01:00
|
|
|
} catch (err) {
|
|
|
|
return PromiseReject(err);
|
2020-06-18 22:22:17 +02:00
|
|
|
}
|
2024-08-25 09:30:10 +02:00
|
|
|
|
|
|
|
const { signal, ref = true } = options;
|
2023-02-05 11:12:16 +01:00
|
|
|
|
|
|
|
if (signal?.aborted) {
|
2021-11-28 21:38:42 +01:00
|
|
|
return PromiseReject(new AbortError(undefined, { cause: signal.reason }));
|
2020-08-13 21:00:55 +02:00
|
|
|
}
|
2024-08-25 09:30:10 +02:00
|
|
|
|
2020-11-06 16:07:43 +01:00
|
|
|
let oncancel;
|
2024-11-06 18:17:31 +01:00
|
|
|
const { promise, resolve, reject } = PromiseWithResolvers();
|
|
|
|
const timeout = new Timeout(resolve, after, [value], false, ref);
|
|
|
|
insert(timeout, timeout._idleTimeout);
|
|
|
|
if (signal) {
|
|
|
|
oncancel = FunctionPrototypeBind(cancelListenerHandler,
|
|
|
|
timeout, clearTimeout, reject, signal);
|
|
|
|
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
|
|
|
signal.addEventListener('abort', oncancel, { __proto__: null, [kResistStopPropagation]: true });
|
|
|
|
}
|
2020-11-06 16:07:43 +01:00
|
|
|
return oncancel !== undefined ?
|
2021-05-12 12:16:43 +02:00
|
|
|
SafePromisePrototypeFinally(
|
2024-11-06 18:17:31 +01:00
|
|
|
promise,
|
|
|
|
() => signal.removeEventListener('abort', oncancel)) : promise;
|
2020-06-18 22:22:17 +02:00
|
|
|
}
|
|
|
|
|
2022-05-21 11:56:32 +02:00
|
|
|
function setImmediate(value, options = kEmptyObject) {
|
2020-12-22 16:23:23 +01:00
|
|
|
try {
|
2024-08-25 09:30:10 +02:00
|
|
|
validateObject(options, 'options');
|
|
|
|
|
|
|
|
if (typeof options?.signal !== 'undefined') {
|
|
|
|
validateAbortSignal(options.signal, 'options.signal');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof options?.ref !== 'undefined') {
|
|
|
|
validateBoolean(options.ref, 'options.ref');
|
|
|
|
}
|
2020-12-22 16:23:23 +01:00
|
|
|
} catch (err) {
|
|
|
|
return PromiseReject(err);
|
2020-06-18 22:22:17 +02:00
|
|
|
}
|
2024-08-25 09:30:10 +02:00
|
|
|
|
|
|
|
const { signal, ref = true } = options;
|
2023-02-05 11:12:16 +01:00
|
|
|
|
|
|
|
if (signal?.aborted) {
|
2021-11-28 21:38:42 +01:00
|
|
|
return PromiseReject(new AbortError(undefined, { cause: signal.reason }));
|
2020-08-13 21:00:55 +02:00
|
|
|
}
|
2024-08-25 09:30:10 +02:00
|
|
|
|
2020-11-06 16:07:43 +01:00
|
|
|
let oncancel;
|
2024-11-06 18:17:31 +01:00
|
|
|
const { promise, resolve, reject } = PromiseWithResolvers();
|
|
|
|
const immediate = new Immediate(resolve, [value]);
|
|
|
|
if (!ref) immediate.unref();
|
|
|
|
if (signal) {
|
|
|
|
oncancel = FunctionPrototypeBind(cancelListenerHandler,
|
|
|
|
immediate, clearImmediate, reject,
|
|
|
|
signal);
|
|
|
|
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
|
|
|
signal.addEventListener('abort', oncancel, { __proto__: null, [kResistStopPropagation]: true });
|
|
|
|
}
|
2020-11-06 16:07:43 +01:00
|
|
|
return oncancel !== undefined ?
|
2021-05-12 12:16:43 +02:00
|
|
|
SafePromisePrototypeFinally(
|
2024-11-06 18:17:31 +01:00
|
|
|
promise,
|
|
|
|
() => signal.removeEventListener('abort', oncancel)) : promise;
|
2020-06-18 22:22:17 +02:00
|
|
|
}
|
|
|
|
|
2022-05-21 11:56:32 +02:00
|
|
|
async function* setInterval(after, value, options = kEmptyObject) {
|
2024-09-02 08:57:02 +02:00
|
|
|
if (typeof after !== 'undefined') {
|
|
|
|
validateNumber(after, 'delay');
|
|
|
|
}
|
2024-08-25 09:30:10 +02:00
|
|
|
|
2024-09-02 08:57:02 +02:00
|
|
|
validateObject(options, 'options');
|
2024-08-25 09:30:10 +02:00
|
|
|
|
2024-09-02 08:57:02 +02:00
|
|
|
if (typeof options?.signal !== 'undefined') {
|
|
|
|
validateAbortSignal(options.signal, 'options.signal');
|
|
|
|
}
|
2024-08-25 09:30:10 +02:00
|
|
|
|
2024-09-02 08:57:02 +02:00
|
|
|
if (typeof options?.ref !== 'undefined') {
|
|
|
|
validateBoolean(options.ref, 'options.ref');
|
2024-08-25 09:30:10 +02:00
|
|
|
}
|
|
|
|
|
2021-01-31 00:16:18 +01:00
|
|
|
const { signal, ref = true } = options;
|
|
|
|
|
2024-08-25 09:30:10 +02:00
|
|
|
if (signal?.aborted) {
|
2024-11-07 16:59:12 +01:00
|
|
|
throw new AbortError(undefined, { cause: signal.reason });
|
2024-08-25 09:30:10 +02:00
|
|
|
}
|
2021-01-31 00:16:18 +01:00
|
|
|
|
|
|
|
let onCancel;
|
|
|
|
let interval;
|
|
|
|
try {
|
|
|
|
let notYielded = 0;
|
|
|
|
let callback;
|
|
|
|
interval = new Timeout(() => {
|
|
|
|
notYielded++;
|
|
|
|
if (callback) {
|
|
|
|
callback();
|
|
|
|
callback = undefined;
|
|
|
|
}
|
2021-04-20 22:13:59 +02:00
|
|
|
}, after, undefined, true, ref);
|
2021-01-31 00:16:18 +01:00
|
|
|
insert(interval, interval._idleTimeout);
|
|
|
|
if (signal) {
|
|
|
|
onCancel = () => {
|
|
|
|
clearInterval(interval);
|
|
|
|
if (callback) {
|
2021-11-28 21:38:42 +01:00
|
|
|
callback(
|
|
|
|
PromiseReject(
|
|
|
|
new AbortError(undefined, { cause: signal.reason })));
|
2021-01-31 00:16:18 +01:00
|
|
|
callback = undefined;
|
|
|
|
}
|
|
|
|
};
|
2023-06-24 17:52:38 +02:00
|
|
|
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
|
|
|
signal.addEventListener('abort', onCancel, { __proto__: null, once: true, [kResistStopPropagation]: true });
|
2021-01-31 00:16:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
while (!signal?.aborted) {
|
|
|
|
if (notYielded === 0) {
|
|
|
|
await new Promise((resolve) => callback = resolve);
|
|
|
|
}
|
|
|
|
for (; notYielded > 0; notYielded--) {
|
|
|
|
yield value;
|
|
|
|
}
|
|
|
|
}
|
2021-11-28 21:38:42 +01:00
|
|
|
throw new AbortError(undefined, { cause: signal?.reason });
|
2021-01-31 00:16:18 +01:00
|
|
|
} finally {
|
|
|
|
clearInterval(interval);
|
|
|
|
signal?.removeEventListener('abort', onCancel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-21 17:04:21 +01:00
|
|
|
// 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');
|
2024-08-25 09:30:10 +02:00
|
|
|
return setTimeout(delay, undefined, options);
|
2021-11-21 17:04:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-18 22:22:17 +02:00
|
|
|
module.exports = {
|
|
|
|
setTimeout,
|
|
|
|
setImmediate,
|
2021-01-31 00:16:18 +01:00
|
|
|
setInterval,
|
2021-11-21 17:04:21 +01:00
|
|
|
scheduler: ReflectConstruct(function() {
|
|
|
|
this[kScheduler] = true;
|
|
|
|
}, [], Scheduler),
|
2020-06-18 22:22:17 +02:00
|
|
|
};
|