class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor) {
this.state = MyPromise.PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === MyPromise.PENDING) {
if (value instanceof MyPromise) {
value.then(resolve, reject);
return;
}
this.state = MyPromise.FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === MyPromise.PENDING) {
if (reason instanceof MyPromise) {
reason.then(resolve, reject);
return;
}
this.state = MyPromise.REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const newPromise = new MyPromise((resolve, reject) => {
const handleCallback = (callback, arg, resolve, reject) => {
queueMicrotask(() => {
try {
const result = callback(arg);
resolvePromise(newPromise, result, resolve, reject);
} catch (err) {
reject(err);
}
});
};
if (this.state === MyPromise.FULFILLED) {
handleCallback(onFulfilled, this.value, resolve, reject);
} else if (this.state === MyPromise.REJECTED) {
handleCallback(onRejected, this.reason, resolve, reject);
} else {
this.onFulfilledCallbacks.push(() =>
handleCallback(onFulfilled, this.value, resolve, reject)
);
this.onRejectedCallbacks.push(() =>
handleCallback(onRejected, this.reason, resolve, reject)
);
}
});
return newPromise;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason })
);
}
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('promises must be an array'));
}
const results = [];
let completed = 0;
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((p, index) => {
MyPromise.resolve(p).then(
value => {
results[index] = value;
completed++;
if (completed === promises.length) resolve(results);
},
reject
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('promises must be an array'));
}
promises.forEach(p => {
MyPromise.resolve(p).then(resolve, reject);
});
});
}
}
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
return;
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let then;
try {
then = x.then;
} catch (err) {
return reject(err);
}
if (typeof then === 'function') {
let called = false;
try {
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} catch (err) {
if (called) return;
reject(err);
}
}
} else {
resolve(x);
}
}