nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
233 lines (216 loc) • 6.59 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/timers/promises.js
import { Timeout, Immediate, insert } from "nstdlib/lib/internal/timers";
import {
clearImmediate,
clearInterval,
clearTimeout,
} from "nstdlib/stub/timers";
import { AbortError, codes as __codes__ } from "nstdlib/lib/internal/errors";
import {
validateAbortSignal,
validateBoolean,
validateObject,
} from "nstdlib/lib/internal/validators";
import { kEmptyObject } from "nstdlib/lib/internal/util";
import * as __hoisted_internal_event_target__ from "nstdlib/lib/internal/event_target";
const { ERR_ILLEGAL_CONSTRUCTOR, ERR_INVALID_ARG_TYPE, ERR_INVALID_THIS } =
__codes__;
const kScheduler = Symbol("kScheduler");
let kResistStopPropagation;
function cancelListenerHandler(clear, reject, signal) {
if (!this._destroyed) {
clear(this);
reject(new AbortError(undefined, { cause: signal?.reason }));
}
}
function setTimeout(after, value, options = kEmptyObject) {
const args = value !== undefined ? [value] : value;
if (options == null || typeof options !== "object") {
return Promise.reject(
new ERR_INVALID_ARG_TYPE("options", "Object", options),
);
}
const { signal, ref = true } = options;
try {
validateAbortSignal(signal, "options.signal");
} catch (err) {
return Promise.reject(err);
}
if (typeof ref !== "boolean") {
return Promise.reject(
new ERR_INVALID_ARG_TYPE("options.ref", "boolean", ref),
);
}
if (signal?.aborted) {
return Promise.reject(new AbortError(undefined, { cause: signal.reason }));
}
let oncancel;
const ret = new Promise((resolve, reject) => {
const timeout = new Timeout(resolve, after, args, false, ref);
insert(timeout, timeout._idleTimeout);
if (signal) {
oncancel = Function.prototype.bind.call(
cancelListenerHandler,
timeout,
clearTimeout,
reject,
signal,
);
kResistStopPropagation ??=
__hoisted_internal_event_target__.kResistStopPropagation;
signal.addEventListener("abort", oncancel, {
__proto__: null,
[kResistStopPropagation]: true,
});
}
});
return oncancel !== undefined
? Promise.prototype.finally.call(ret, () =>
signal.removeEventListener("abort", oncancel),
)
: ret;
}
function setImmediate(value, options = kEmptyObject) {
if (options == null || typeof options !== "object") {
return Promise.reject(
new ERR_INVALID_ARG_TYPE("options", "Object", options),
);
}
const { signal, ref = true } = options;
try {
validateAbortSignal(signal, "options.signal");
} catch (err) {
return Promise.reject(err);
}
if (typeof ref !== "boolean") {
return Promise.reject(
new ERR_INVALID_ARG_TYPE("options.ref", "boolean", ref),
);
}
if (signal?.aborted) {
return Promise.reject(new AbortError(undefined, { cause: signal.reason }));
}
let oncancel;
const ret = new Promise((resolve, reject) => {
const immediate = new Immediate(resolve, [value]);
if (!ref) immediate.unref();
if (signal) {
oncancel = Function.prototype.bind.call(
cancelListenerHandler,
immediate,
clearImmediate,
reject,
signal,
);
kResistStopPropagation ??=
__hoisted_internal_event_target__.kResistStopPropagation;
signal.addEventListener("abort", oncancel, {
__proto__: null,
[kResistStopPropagation]: true,
});
}
});
return oncancel !== undefined
? Promise.prototype.finally.call(ret, () =>
signal.removeEventListener("abort", oncancel),
)
: ret;
}
async function* setInterval(after, value, options = kEmptyObject) {
validateObject(options, "options");
const { signal, ref = true } = options;
validateAbortSignal(signal, "options.signal");
validateBoolean(ref, "options.ref");
if (signal?.aborted)
throw new AbortError(undefined, { cause: signal?.reason });
let onCancel;
let interval;
try {
let notYielded = 0;
let callback;
interval = new Timeout(
() => {
notYielded++;
if (callback) {
callback();
callback = undefined;
}
},
after,
undefined,
true,
ref,
);
insert(interval, interval._idleTimeout);
if (signal) {
onCancel = () => {
clearInterval(interval);
if (callback) {
callback(
Promise.reject(new AbortError(undefined, { cause: signal.reason })),
);
callback = undefined;
}
};
kResistStopPropagation ??=
__hoisted_internal_event_target__.kResistStopPropagation;
signal.addEventListener("abort", onCancel, {
__proto__: null,
once: true,
[kResistStopPropagation]: true,
});
}
while (!signal?.aborted) {
if (notYielded === 0) {
await new Promise((resolve) => (callback = resolve));
}
for (; notYielded > 0; notYielded--) {
yield value;
}
}
throw new AbortError(undefined, { cause: signal?.reason });
} finally {
clearInterval(interval);
signal?.removeEventListener("abort", onCancel);
}
}
// 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 });
}
}
export { setTimeout };
export { setImmediate };
export { setInterval };
const _export_scheduler_ = Reflect.construct(
function () {
this[kScheduler] = true;
},
[],
Scheduler,
);
export { _export_scheduler_ as scheduler };