Use JavaScript to implement a simple Promise step by step, supporting asynchronous and then chain calls.
Translated and organized from Medium: Implementing a simple Promise in Javascript-by Zhi Sun
Preface
In front-end interviews and daily development, Promise is often exposed. And in many interviews nowadays, you are often asked to write Promises by hand.
Next, we will use JavaScript to implement a simple Promise step by step, supporting asynchronous and then chain calls.
Analyze Promise
The Promise object is used to represent the final completion (or failure) of an asynchronous operation and its result value. It is commonly used to implement asynchronous operations.
Promise status
Promise has three states:
pending
Initial state
fulfilled
Status after successful execution
rejected
Status after execution failure
The Promise state can only be changed pending
fulfilled
or from pending
to rejected
. The process of Promise state change is called settled
, and once the state changes, it will not be changed again in the future.
Parameters in the Promise constructor
The Promise constructor receives a function parameter executor
, and the function receives two parameters:
- resolve
- reject
Executing resolve
will change the Promise status from pending
to fulfilled
, and trigger the success callback function onFulfilled
then
method,
Executing reject
will change the Promise status from pending
to rejected
, and trigger the failure callback function onRejected
then
method.
Callback function parameters in the then method
then
method receives two parameters:
onFulfilled
Success callback function, receiving a parameter, that is, the value passed in the
resolve
onRejected
Failure callback function, receiving a parameter, that is, the value passed in the
reject
If the Promise status changes to fulfilled
, the success callback function onFulfilled
will be executed; if the Promise status changes to rejected
, the failure callback function onRejected
will be executed.
Realize Promise
Basic Promise
First, constructor
receives a function executor
, and the function receives two parameters, which are functions resolve
and reject
Therefore, you need to create the resolve
and reject
constructor
and pass them to the executor
function.
class MyPromise {
constructor(executor) {
const resolve = (value) => {};
const reject = (value) => {};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
}
Secondly, Promise will execute the corresponding callback function according to the state. The initial state is pending
, when resolve
, the state changes pending
fulfilled
; when reject
, the state changes pending
rejected
.
class MyPromise {
constructor(executor) {
this.state = 'pending';
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
}
};
const reject = (value) => {
if (this.state === 'pending') {
this.state = 'rejected';
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
}
After the Promise state changes, the corresponding callback function in the then
If the status changes pending
fulfilled
, the success callback function will be triggered. If the status changes pending
rejected
, the failure callback function will be triggered.
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = null;
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
}
};
const reject = (value) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.value = value;
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value);
}
if (this.state === 'rejected') {
onRejected(this.value);
}
}
}
Next, you can write some test code to test the function
const p1 = new MyPromise((resolve, reject) => resolve('resolved'));
p1.then(
(res) => console.log(res), // resolved
(err) => console.log(err)
);
const p2 = new MyPromise((resolve, reject) => reject('rejected'));
p2.then(
(res) => console.log(res),
(err) => console.log(err) // rejected
);
However, if you test with the following code, you will find that nothing is output.
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved'), 1000);
});
p1.then(
(res) => console.log(res),
(err) => console.log(err)
);
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => reject('rejected'), 1000);
});
p2.then(
(res) => console.log(res),
(err) => console.log(err)
);
This is because when the then
method is called, the Promise is still in the pending
state. onFulfilled
and onRejected
callback functions are not executed.
Therefore, the next step is to support asynchrony.
Support asynchronous Promise
In order to support asynchronous, you need to save the onFulfilled
and onRejected
callback functions. Once the Promise status changes, the corresponding callback function will be executed immediately.
: There is a detail that needs attention, that is, onFulfilledCallbacks
and onRejectedCallbacks
are arrays, because Promise may be called multiple times, so there will be multiple callback functions.
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach((fn) => fn(value));
}
};
const reject = (value) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.value = value;
this.onRejectedCallbacks.forEach((fn) => fn(value));
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
if (this.state === 'fulfilled') {
onFulfilled(this.value);
}
if (this.state === 'rejected') {
onRejected(this.value);
}
}
}
Next, you can use the previous test code to test the function
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved'), 1000);
});
p1.then(
(res) => console.log(res), // resolved
(err) => console.log(err)
);
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => reject('rejected'), 1000);
});
p2.then(
(res) => console.log(res),
(err) => console.log(err) // rejected
);
But if you use the following code to test, you will find an error.
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved'), 1000);
});
p1.then(
(res) => console.log(res),
(err) => console.log(err)
).then(
(res) => console.log(res),
(err) => console.log(err)
); // Uncaught TypeError: Cannot read property 'then' of undefined
This is because the first then
method did not return any value, but the then
method was continuously called.
Therefore, the next step is to implement the then
chain call.
Promise that supports then
To support the then
chain call, the then
method needs to return a new Promise.
Therefore, you need to modify the then
method to return a new Promise. onRejected
onFulfilled
or 061ad7161c1a89 is executed, execute the resolve
or reject
function of the new Promise.
class MyPromise {
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
resolve(fulfilledFromLastPromise);
} catch (err) {
reject(err);
}
});
this.onRejectedCallbacks.push(() => {
try {
const rejectedFromLastPromise = onRejected(this.value);
reject(rejectedFromLastPromise);
} catch (err) {
reject(err);
}
});
}
if (this.state === 'fulfilled') {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
resolve(fulfilledFromLastPromise);
} catch (err) {
reject(err);
}
}
if (this.state === 'rejected') {
try {
const rejectedFromLastPromise = onRejected(this.value);
reject(rejectedFromLastPromise);
} catch (err) {
reject(err);
}
}
});
}
}
Next, you can test the function with the following code
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved'), 1000);
});
p1.then(
(res) => {
console.log(res); // resolved
return res;
},
(err) => console.log(err)
).then(
(res) => console.log(res), // resolved
(err) => console.log(err)
);
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => reject('rejected'), 1000);
});
p2.then(
(res) => console.log(res),
(err) => {
console.log(err); // rejected
throw new Error('rejected');
}
).then(
(res) => console.log(res),
(err) => console.log(err) // Error: rejected
);
However, if you use the following code to test, you will find then
method does not output as expected ('resolved'), but outputs the Promise returned in the onFulfilled
then
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved'), 1000);
});
p1.then(
(res) => {
console.log(res); // resolved
return new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved'), 1000);
})
},
(err) => console.log(err)
).then(
(res) => console.log(res), // MyPromise {state: "pending"}
(err) => console.log(err)
);
This is because onFulfilled
/ onRejected
after executing the callback function, simply will onFulfilled
/ onRejected
perform complete return values passed resolve
/ reject
function to perform, and did not consider onFulfilled
/ onRejected
executing the return of a new case of Promise , So the second success callback function of the then
method directly outputs the Promise returned in the success callback function of the then
Therefore, this problem needs to be solved next.
First of all, you can change the above test code to another way of writing, which is convenient for sorting out ideas.
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved'), 1000);
});
const p2 = p1.then(
(res) => {
console.log(res);
const p3 = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved'), 1000);
});
return p3;
},
(err) => console.log(err)
);
p2.then(
(res) => console.log(res),
(err) => console.log(err)
);
As you can see, there are three Promises:
The first Promise
That is, p1 constructed
new
The second Promise
That is, p2 returned by calling the
then
The third Promise
That is, p3 returned in the success callback function parameter of the
p1.then
The problem now is that then
method is called, p3 is still in the pending
state.
In order to realize that the callback function in the p2.then
resolve
/ reject
in p3, you need to wait for the state of p3 to change and then transfer the changed value to resolve
/ reject
in p2. In other words, the sequence of the state changes of the three Promises should be p1 --> p3 --> p2.
class MyPromise {
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
if (fulfilledFromLastPromise instanceof MyPromise) {
fulfilledFromLastPromise.then(resolve, reject);
} else {
resolve(fulfilledFromLastPromise);
}
} catch (err) {
reject(err);
}
});
this.onRejectedCallbacks.push(() => {
try {
const rejectedFromLastPromise = onRejected(this.value);
if (rejectedFromLastPromise instanceof MyPromise) {
rejectedFromLastPromise.then(resolve, reject);
} else {
reject(rejectedFromLastPromise);
}
} catch (err) {
reject(err);
}
});
}
if (this.state === 'fulfilled') {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
if (fulfilledFromLastPromise instanceof MyPromise) {
fulfilledFromLastPromise.then(resolve, reject);
} else {
resolve(fulfilledFromLastPromise);
}
} catch (err) {
reject(err);
}
}
if (this.state === 'rejected') {
try {
const rejectedFromLastPromise = onRejected(this.value);
if (rejectedFromLastPromise instanceof MyPromise) {
rejectedFromLastPromise.then(resolve, reject);
} else {
reject(rejectedFromLastPromise);
}
} catch (err) {
reject(err);
}
}
});
}
}
The final version of Promise
Finally, a simple Promise is completed, supporting asynchronous and chain calls of then
The complete code is as follows:
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach((fn) => fn(value));
}
};
const reject = (value) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.value = value;
this.onRejectedCallbacks.forEach((fn) => fn(value));
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
if (fulfilledFromLastPromise instanceof Promise) {
fulfilledFromLastPromise.then(resolve, reject);
} else {
resolve(fulfilledFromLastPromise);
}
} catch (err) {
reject(err);
}
});
this.onRejectedCallbacks.push(() => {
try {
const rejectedFromLastPromise = onRejected(this.value);
if (rejectedFromLastPromise instanceof Promise) {
rejectedFromLastPromise.then(resolve, reject);
} else {
reject(rejectedFromLastPromise);
}
} catch (err) {
reject(err);
}
});
}
if (this.state === 'fulfilled') {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
if (fulfilledFromLastPromise instanceof Promise) {
fulfilledFromLastPromise.then(resolve, reject);
} else {
resolve(fulfilledFromLastPromise);
}
} catch (err) {
reject(err);
}
}
if (this.state === 'rejected') {
try {
const rejectedFromLastPromise = onRejected(this.value);
if (rejectedFromLastPromise instanceof Promise) {
rejectedFromLastPromise.then(resolve, reject);
} else {
reject(rejectedFromLastPromise);
}
} catch (err) {
reject(err);
}
}
});
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。