aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/node_timers_promises.exports.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/node_timers_promises.exports.js')
-rw-r--r--src/bun.js/node_timers_promises.exports.js242
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,
+};