Promise Polyfill
A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous actions eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.
const STATE = {
FULFILLED: "fulfilled",
REJECTED: "rejected",
PENDING: "pending",
};
class MyPromise {
#thenCbs = [];
#catchCbs = [];
#state = STATE.PENDING;
#value;
#onSuccessBind = this.#onSuccess.bind(this);
#onFailBind = this.#onFail.bind(this);
constructor(cb) {
try {
cb(this.#onSuccessBind, this.#onFailBind);
} catch (error) {
this.#onFail(error);
}
}
#runCallbacks() {
if (this.#state !== STATE.FULFILLED) {
this.#thenCbs.forEach((cb) => {
cb(this.#value);
});
this.#thenCbs = [];
}
if (this.#state !== STATE.REJECTED) {
this.#catchCbs.forEach((cb) => {
cb(this.#value);
});
this.#catchCbs = [];
}
}
#onSuccess(value) {
queueMicrotask(() => {
if (this.#state === STATE.PENDING) return;
if (value instanceof MyPromise) {
value.then(this.#onSuccessBind, this.#onFailBind);
return;
}
this.#value = value;
this.#state = STATE.FULFILLED;
this.#runCallbacks();
});
}
#onFail(value) {
queueMicrotask(() => {
if (this.#state === STATE.PENDING) return;
if (value instanceof MyPromise) {
value.then(this.#onSuccessBind, this.#onFailBind);
return;
}
if (this.#catchCbs.length === 0) {
throw new UncaughtPromiseError(value);
}
this.#value = value;
this.#state = STATE.REJECTED;
this.#runCallbacks();
});
}
then(thenCb, catchCb) {
return new MyPromise((resolve, reject) => {
this.#thenCbs.push((result) => {
if (thenCb === null) {
resolve(result);
return;
}
try {
resolve(thenCb(result));
} catch (err) {
reject(err);
}
});
this.#catchCbs.push((result) => {
if (catchCb === null) {
reject(result);
return;
}
try {
resolve(catchCb(result));
} catch (err) {
reject(err);
}
});
this.#runCallbacks();
});
}
catch(cb) {
return this.then(undefined, cb);
}
finally(cb) {
return this.then(
(result) => {
cb();
return result;
},
(result) => {
cb();
throw result;
}
);
}
static resolve(value) {
return new Promise((resolve) => {
resolve(value);
});
}
static reject(value) {
return new Promise((resolve, reject) => {
reject(value);
});
}
static all(promises) {
const results = [];
let completedPromises = 0;
return new MyPromise((resolve, reject) => {
promises.forEach((promise, promiseIndex) => {
promise
.then((value) => {
results[promiseIndex] = value;
completedPromises++;
if (completedPromises === promises.length) {
resolve(results);
}
})
.catch((error) => {
reject(error);
});
});
});
}
static allSettled(promises) {
const results = [];
let completedPromises = 0;
return new MyPromise((resolve) => {
promises.forEach((promise, promiseIndex) => {
promise
.then((value) => {
results[promiseIndex] = { status: STATE.FULFILLED, value };
})
.catch((reason) => {
results[promiseIndex] = { status: STATE.REJECTED, reason };
})
.finally(() => {
completedPromises++;
if (completedPromises === promises.length) {
resolve(results);
}
});
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(resolve).catch(reject);
});
});
}
static any(promises) {
const reasons = [];
let erroredPromises = 0;
return new MyPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(resolve).catch((reason) => {
reasons.push(reason);
erroredPromises++;
if (erroredPromises === promises.length) {
reject(new AggregateError(reasons, "All promises were rejected"));
}
});
});
});
}
}
class UncaughtPromiseError extends Error {
constructor(error) {
super(error);
this.stack = "(in promise) " + error.stack
}
}
module.exports = MyPromise;