In modern JavaScript, Promises are an important tool for handling asynchronous operations. By handwriting a simple Promise class, we can have a deeper understanding of the working principle of Promise and the mechanism of asynchronous programming. In this blog, we will gradually build a fully functional Promise class, including basic structure, then
methods, static methods such as resolve
, reject
, all
, allSettled
, race
and any
, as well as some auxiliary methods.
Basic structure of Promise
First, we define a MyPromise
class that contains common Promise states and basic structures:
class MyPromise {
static REJECTED = 'rejected';
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
value = undefined;
status = MyPromise.PENDING;
onFulfilledCallBacks = [];
onRejectedCallBacks = [];
constructor(execute) {
const resolve = (value) => {
if (this.status === MyPromise.PENDING) {
this.value = value;
this.status = MyPromise.FULFILLED;
this.onFulfilledCallBacks.forEach((func) => func(value));
}
};
const reject = (value) => {
if (this.status === MyPromise.PENDING) {
this.value = value;
this.status = MyPromise.REJECTED;
this.onRejectedCallBacks.forEach((func) => func(value));
}
};
try {
execute(resolve, reject);
} catch (error) {
reject(error);
}
}
// ...()
}
The basic structure of the class is defined here MyPromise
, including state constants, initial values, status and execution callback arrays, etc.
Implementationthen
then
Methods are the core of Promise and are used to register callback functions that are executed when the Promise is completed. Here is then
the basic implementation of the method:
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (onFulfilled) => onFulfilled;
onRejected = typeof onRejected === 'function' ? onRejected : (onRejected) => onRejected;
return new MyPromise((resolve, reject) => {
//
if (this.status === MyPromise.FULFILLED) {
try {
queueMicrotask(() => {
const result = onFulfilled(this.value);
this.handlePromiseResult(result, resolve, reject);
});
} catch (error) {
reject(error);
}
}
//
else if (this.status === MyPromise.REJECTED) {
try {
queueMicrotask(() => {
const result = onRejected(this.value);
this.handlePromiseResult(result, resolve, reject);
});
} catch (error) {
reject(error);
}
}
//
else {
this.onFulfilledCallBacks.push((value) => {
queueMicrotask(() => {
const result = onFulfilled(value);
this.handlePromiseResult(result, resolve, reject);
});
});
this.onRejectedCallBacks.push((value) => {
queueMicrotask(() => {
const result = onRejected(value);
this.handlePromiseResult(result, resolve, reject);
});
});
}
});
}
handlePromiseResult(result, resolve, reject) {
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
resolve(result);
}
}
In then
the method, we determine the current Promise state and execute the corresponding callback function according to the different states. If it is an asynchronous state, push the callback function into the corresponding callback array and wait for execution when the state changes.
Implementation resolve
and reject
static methods
resolve
and reject
are MyPromise
two static methods of the class that are used to create resolved or rejected Promises. Here are their implementations:
static resolve = (value) => {
return new MyPromise((resolve, reject) => {
resolve(value);
});
};
static reject = (value) => {
return new MyPromise((resolve, reject) => {
reject(value);
});
};
These two methods return a resolved or rejected Promise respectively.
Reality
Present all
method
all
The method receives an array of Promise and returns a new Promise. Only when all input Promises resolve, the new Promise will be resolved and an array containing the results of all Promises will be returned; otherwise, the new Promise will be rejected as long as one Promise rejects.
static all = (promises) => {
if (!this._hasIterator(promises)) {
throw new Error('Parameters are not iterable');
}
return new MyPromise((resolve, reject) => {
const resultArr = [];
promises.forEach((promise) => {
promise.then(
(res) => {
if (resultArr.length === promises.length) {
resolve(resultArr);
}
resultArr.push(res);
},
(err) => {
reject(err);
}
);
});
});
};
Implementation allSettled
, race
and any
methods
allSettled
Method returns a Promise that resolves after all given Promises have resolved or been rejected. It waits for all Promises to complete, whether successful or failed, and returns an array containing the results of each Promise.
static allSettled = (promises) => {
if (!this._hasIterator(promises)) {
throw new Error('Parameters are not iterable');
}
return new MyPromise((resolve) => {
const resultArr = [];
promises.forEach((promise, index) => {
promise.then(
(res) => {
resultArr.push({ status: 'fulfilled', value: res });
if (resultArr.length === promises.length) {
resolve(resultArr);
}
},
(res) => {
resultArr.push({ status: 'rejected', reason: res });
if (resultArr.length === promises.length) {
resolve(resultArr);
}
}
);
});
});
};
race
Method returns a Promise that resolves immediately after any given Promise resolves or rejects.
static race = (promises) => {
if (!this._hasIterator(promises)) {
throw new Error('Parameters are not iterable');
}
return new MyPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(
(res) => {
resolve(res);
},
(res) => {
reject(res);
}
);
});
});
};
any
Method returns a Promise that resolves immediately after any given Promise resolves. The difference race
is any
that only one Promise can be resolved, regardless of whether its status is success or failure.
static any = (promises) => {
if (!this._hasIterator(promises)) {
throw new Error('Parameters are not iterable');
}
return new MyPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then((res) => {
resolve(res);
});
});
});
};
Helper methods:deferred
To facilitate the creation of a Promise object that resolves lazily, we added deferred
the method:
static deferred = () => {
let dfd = {};
dfd.promise = new MyPromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
This method returns an object containing promise
, resolve
and reject
for subsequent use.
Summarize
Through the above implementation, we successfully created a simple but fully functional Promise class. This handwritten Promise not only has basic asynchronous operation capabilities, but also supports Promise chain calls and common Promise methods, such as then
, resolve
, reject
, all
, allSettled
, race
and any
.
Implementing Promise by hand is a challenging but rewarding exercise, which helps to gain a deeper understanding of how Promise works and the mechanism of asynchronous programming. I hope this blog will help you understand Promise.