diff options
Diffstat (limited to 'src/bun.js/node_timers_promises.exports.js')
-rw-r--r-- | src/bun.js/node_timers_promises.exports.js | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/src/bun.js/node_timers_promises.exports.js b/src/bun.js/node_timers_promises.exports.js new file mode 100644 index 000000000..a2c6d4558 --- /dev/null +++ b/src/bun.js/node_timers_promises.exports.js @@ -0,0 +1,242 @@ +// https://github.com/niksy/isomorphic-timers-promises/blob/master/index.js + +const symbolAsyncIterator = Symbol.asyncIterator; + +class ERR_INVALID_ARG_TYPE extends Error { + constructor(name, expected, actual) { + super(`${name} must be ${expected}, ${typeof actual} given`); + this.code = "ERR_INVALID_ARG_TYPE"; + } +} + +class AbortError extends Error { + constructor() { + super("The operation was aborted"); + this.code = "ABORT_ERR"; + } +} + +function validateObject(object, name) { + if (object === null || typeof object !== "object") { + throw new ERR_INVALID_ARG_TYPE(name, "Object", object); + } +} + +function validateBoolean(value, name) { + if (typeof value !== "boolean") { + throw new ERR_INVALID_ARG_TYPE(name, "boolean", value); + } +} + +function validateAbortSignal(signal, name) { + if ( + typeof signal !== "undefined" && + (signal === null || typeof signal !== "object" || !("aborted" in signal)) + ) { + throw new ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal); + } +} + +function asyncIterator({ next: nextFunction, return: returnFunction }) { + const result = {}; + if (typeof nextFunction === "function") { + result.next = nextFunction; + } + if (typeof returnFunction === "function") { + result.return = returnFunction; + } + result[symbolAsyncIterator] = function () { + return this; + }; + + return result; +} + +function setTimeoutPromise(after = 1, value, options = {}) { + const arguments_ = [].concat(value ?? []); + try { + validateObject(options, "options"); + } catch (error) { + return Promise.reject(error); + } + const { signal, ref: reference = true } = options; + try { + validateAbortSignal(signal, "options.signal"); + } catch (error) { + return Promise.reject(error); + } + try { + validateBoolean(reference, "options.ref"); + } catch (error) { + return Promise.reject(error); + } + if (signal?.aborted) { + return Promise.reject(new AbortError()); + } + let onCancel; + const returnValue = new Promise((resolve, reject) => { + const timeout = setTimeout(() => resolve(value), after, ...arguments_); + if (!reference) { + timeout?.unref?.(); + } + if (signal) { + onCancel = () => { + clearTimeout(timeout); + reject(new AbortError()); + }; + signal.addEventListener("abort", onCancel); + } + }); + if (typeof onCancel !== "undefined") { + returnValue.finally(() => signal.removeEventListener("abort", onCancel)); + } + return returnValue; +} + +function setImmediatePromise(value, options = {}) { + try { + validateObject(options, "options"); + } catch (error) { + return Promise.reject(error); + } + const { signal, ref: reference = true } = options; + try { + validateAbortSignal(signal, "options.signal"); + } catch (error) { + return Promise.reject(error); + } + try { + validateBoolean(reference, "options.ref"); + } catch (error) { + return Promise.reject(error); + } + if (signal?.aborted) { + return Promise.reject(new AbortError()); + } + let onCancel; + const returnValue = new Promise((resolve, reject) => { + const immediate = setImmediate(() => resolve(value)); + if (!reference) { + immediate?.unref?.(); + } + if (signal) { + onCancel = () => { + clearImmediate(immediate); + reject(new AbortError()); + }; + signal.addEventListener("abort", onCancel); + } + }); + if (typeof onCancel !== "undefined") { + returnValue.finally(() => signal.removeEventListener("abort", onCancel)); + } + return returnValue; +} + +function setIntervalPromise(after = 1, value, options = {}) { + /* eslint-disable no-undefined, no-unreachable-loop, no-loop-func */ + try { + validateObject(options, "options"); + } catch (error) { + return asyncIterator({ + next: function () { + return Promise.reject(error); + }, + }); + } + const { signal, ref: reference = true } = options; + try { + validateAbortSignal(signal, "options.signal"); + } catch (error) { + return asyncIterator({ + next: function () { + return Promise.reject(error); + }, + }); + } + try { + validateBoolean(reference, "options.ref"); + } catch (error) { + return asyncIterator({ + next: function () { + return Promise.reject(error); + }, + }); + } + if (signal?.aborted) { + return asyncIterator({ + next: function () { + return Promise.reject(new AbortError()); + }, + }); + } + + let onCancel, interval; + + try { + let notYielded = 0; + let callback; + interval = setInterval(() => { + notYielded++; + if (callback) { + callback(); + callback = undefined; + } + }, after); + if (!reference) { + interval?.unref?.(); + } + if (signal) { + onCancel = () => { + clearInterval(interval); + if (callback) { + callback(); + callback = undefined; + } + }; + signal.addEventListener("abort", onCancel); + } + + return asyncIterator({ + next: function () { + return new Promise((resolve, reject) => { + if (!signal?.aborted) { + if (notYielded === 0) { + callback = resolve; + } else { + resolve(); + } + } else if (notYielded === 0) { + reject(new AbortError()); + } else { + resolve(); + } + }).then(() => { + if (notYielded > 0) { + notYielded = notYielded - 1; + return { done: false, value: value }; + } + return { done: true }; + }); + }, + return: function () { + clearInterval(interval); + signal?.removeEventListener("abort", onCancel); + return Promise.resolve({}); + }, + }); + } catch (error) { + return asyncIterator({ + next: function () { + clearInterval(interval); + signal?.removeEventListener("abort", onCancel); + }, + }); + } +} + +export { + setTimeoutPromise as setTimeout, + setImmediatePromise as setImmediate, + setIntervalPromise as setInterval, +}; |