This article will take you through a step-by-step detailed analysis, starting from zero, write promise source code.
The core logical implementation of the Promise class
First, let’s take a look at what promises look like when they finally come true
let promise = new Promise((resolve, reject) = > {
resolve('success');
reject('failure');
});
promise.then(
(value) = > {
console.log(value)
},
(reason) = > {
console.log(reason)
}
);
Copy the code
Through the code, we can see that the promise uses the new keyword. The constructor passes in a function that takes resolve and reject.
Based on the above analysis, we can write the following code
class MyPromise { // Name the class MyPromise
constructor(executor){ // The constructor passes in a function, executor, which executes immediately
executor(resolve,reject)
}
}
Copy the code
We also know that resolve and reject in promises are used to implement state state transitions. Resolve converts the wait state to a success state, reject converts the wait state to a failure state. That is, there will be a state quantity in the promise, which will be converted from wait -> success/failure through the class methods in the promise. So improve the code:
// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'
class MyPromise { // Class naming conventions
constructor(executor){
executor(this.resolve,this.reject)
}
status = PENDING // State variables
resolve(){ // State transition on success
this.status = FULFILLED
}
reject(){ // State transition on failure
this.status = REJECT
}
}
Copy the code
At the same time, we know that state transitions can only be made by waiting to transition to success or failure. That is, we do not perform state transitions when the initial state is not waiting. The above code is optimized to become
// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'
class MyPromise { // Class naming conventions
constructor(executor){
executor(this.resolve,this.reject)
}
status = PENDING // State variables
resolve(){ // State transition on success
if(this.status ! == PENDING)return // The transition is not performed in the wait state
this.status = FULFILLED
}
reject(){ // State transition on failure
if(this.status ! == PENDING)return // The transition is not performed in the wait state
this.status = REJECT
}
}
Copy the code
In the use of promise, then is used to handle callbacks, promise.then(). Two functions are passed to represent successful and failed callbacks. Successful callback functions take value and failed functions take Reason, completing the Promise class as follows
// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'
class MyPromise { // Class naming conventions
constructor(executor){
executor(this.resolve,this.reject)
}
status = PENDING // State variables
resolve = () = >{ // State transition on success
if(this.status ! == PENDING)return
this.status = FULFILLED
}
reject= () = >{ // State transition on failure
if(this.status ! == PENDING)return
this.status = REJECT
}
then(successCallback,failCallback){ // Then passes two callback functions, one successful and one failed
if(this.status === FULFILLED){
successCallback(value) // This is a big pity
}else if(this.status === REJECT){
failCallback(reason) // The status is REJECT callback}}}Copy the code
We also know that the value of successCallback and reason in failCallback are the arguments passed in when the state transition is performed. We define value and Reason as one variable.
// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'
class MyPromise { // Class naming conventions
constructor(executor){
executor(this.resolve,this.reject)
}
status = PENDING // State variables
value = undefined // Success value
reason = undefined // Cause of failure
resolve=value= >{ // State transition on success
if(this.status ! == PENDING)return
this.status = FULFILLED
this.value = value // Save the amount of success
}
reject=reason= >{ // State transition on failure
if(this.status ! == PENDING)return
this.status = REJECT
this.reason = reason // Save the failure cause after the failure
}
then(successCallback,failCallback){ // Then passes two callback functions, one successful and one failed
if(this.status === FULFILLED){
successCallback(this.value) // This is a big pity
}else if(this.status === REJECT){
failCallback(this.reason) // The status is REJECT callback}}}module.exports = MyPromise;
Copy the code
With that said, the core logic of promise has been implemented, and now let’s test the code
const MyPromise = require("./myPromise"); / / import
let promise = new MyPromise((resolve, reject) = > {
resolve("Success"); // Implement a successful function
// reject(" reject ");
});
promise.then(
(value) = > {
console.log(value);
},
(reason) = > {
console.log(reason); });Copy the code
At this point, our entire promise core logic is completed.
Add asynchronous logic to the Promise class
The MyPromise class we implemented above does not consider the implementation of asynchronous code. We put asynchronous logic in the middle of the test code.
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) = > {
// resolve(" resolve ");
// reject(" reject ");
setTimeout(() = > { // The state transition is executed after 2 seconds. The then method is still PANDING, but there is no logic for processing PENDING state in then.
resolve("Success");
}, 2000);
});
promise.then(
(value) = > {
console.log(value);
},
(reason) = > {
console.log(reason); });Copy the code
Since we do not implement PENDING logic in the THEN method, we implement it now. Since the state transition has not been implemented while waiting for the state, we store the two callback functions first. The state transition functions resolve and reject are then called to ensure that the callback is fired.
// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
// Class naming conventions
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; // State variables
value = undefined; // Success value
reason = undefined; // Cause of failure
successCallback = undefined; // Success callback function
failCallback = undefined; // Failed callback function
resolve = (value) = > {
// State transition on success
if (this.status ! == PENDING)return;
this.status = FULFILLED;
this.value = value;
this.successCallback && this.successCallback(value); // Check whether the successful callback function exists
};
reject = (reason) = > {
// State transition on failure
if (this.status ! == PENDING)return;
this.status = REJECT;
this.reason = reason;
this.failCallback && this.failCallback(reason); // Check whether the failed callback function exists
};
then(successCallback, failCallback) {
// Then passes two callback functions, one successful and one failed
if (this.status === FULFILLED) {
successCallback(this.value); // This is a big pity
} else if (this.status === REJECT) {
failCallback(this.reason); // The status is REJECT callback
} else {
// Wait state, let's first store the callback function
this.successCallback = successCallback;
this.failCallback = failCallback; }}}module.exports = MyPromise;
Copy the code
Using the asynchronous test code above again, the test found perfect execution, and now we have implemented the asynchronous logic in the Promise class.
Implement multiple calls to THEN
We know that promise’s then method can be called multiple times, but the callback function in our code above can only store one function, so we need to modify it to implement multiple calls to THEN
// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
// Class naming conventions
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; // State variables
value = undefined; // Success value
reason = undefined; // Cause of failure
successCallback = []; // Success callback function
failCallback = []; // Failed callback function
resolve = (value) = > {
// State transition on success
if (this.status ! == PENDING)return;
this.status = FULFILLED;
this.value = value;
// this.successCallback && this.successCallback(value);
// console.log(this.successCallback);
while (this.successCallback.length) // Loop to determine if there are any callback functions in the success array that have not yet been executed. If there are any callback functions in the success array, exit the queue and execute the function
this.successCallback.shift()(this.value);
};
reject = (reason) = > {
// State transition on failure
if (this.status ! == PENDING)return;
this.status = REJECT;
this.reason = reason;
// this.failCallback && this.failCallback(reason);
while (this.failCallback.length) this.failCallback.shift()(this.reason);// Check if there are any callback functions in the failed array that have not yet been executed
};
then(successCallback, failCallback) {
// Then passes two callback functions, one successful and one failed
if (this.status === FULFILLED) {
successCallback(this.value); // This is a big pity
} else if (this.status === REJECT) {
failCallback(this.reason); // The status is REJECT callback
} else {
// Wait state, let's first store the callback function
this.successCallback.push(successCallback);
this.failCallback.push(failCallback); }}}module.exports = MyPromise;
Copy the code
So far, we have implemented multiple calls to THEN.
Chain calls to THEN
We know that promise can be invoked in a Xi ‘an chain, but the code above doesn’t do that. And let’s change the test code
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) = > {
// resolve(" resolve ");
// reject(" reject ");
setTimeout(() = > {
// The state transition is executed after 2 seconds. The then method is still PANDING, but there is no logic for processing PENDING state in then.
resolve("Success");
}, 2000);
});
promise //promise
.then(
(value) = > {
console.log(value);
},
(reason) = > {
console.log(reason);
}
)
.then((value) = > {
console.log(value);
});
Copy the code
And we looked at chained calls and we saw that in order to do chained calls we have to return a promise object in each THEN because only promise objects can call then methods, and we also have to pass values between chained THEN’s. Next class, we’re going to implement a chain call to THEN
Let’s write the test code first
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) = > {
resolve("Success");
// reject(" reject ");
// setTimeout(() => {
// // : the state transition will take two seconds. The code executes the then method first, but the state is still PANDING, but there is no logic for processing PENDING state in then, so we add it.
// resolve("hahha1");
// }, 2000);
});
promise
.then((value) = > {
// console.log(123);
console.log(value);
return 100;
})
.then((value) = > {
console.log(value);
});
Copy the code
Then modify the code as follows
// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
// Class naming conventions
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; // State variables
value = undefined; // Success value
reason = undefined; // Cause of failure
successCallback = []; // Success callback function
failCallback = []; // Failed callback function
resolve = (value) = > {
// State transition on success
if (this.status ! == PENDING)return;
this.status = FULFILLED;
this.value = value;
// this.successCallback && this.successCallback(value);
// console.log(this.successCallback);
while (this.successCallback.length)
this.successCallback.shift()(this.value);
};
reject = (reason) = > {
// State transition on failure
if (this.status ! == PENDING)return;
this.status = REJECT;
this.reason = reason;
// this.failCallback && this.failCallback(reason);
while (this.failCallback.length) this.failCallback.shift()(this.reason);
};
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) = > { Define a new Promise object
// Then passes two callback functions, a successful callback and a failed callback. Since the function passed to the constructor is executed, other statements can be put in it
if (this.status === FULFILLED) {
let x = successCallback(this.value); // This is a big pity
resolve(x); This is where the implementation passes the return value of the previous Promise object to the next promise
} else if (this.status === REJECT) {
failCallback(this.reason); // The status is REJECT callback
} else {
// Wait state, let's first store the callback function
this.successCallback.push(successCallback);
this.failCallback.push(failCallback); }});return promise2; // Return this Promise object}}module.exports = MyPromise;
Copy the code
So now we have a chain call to the then method. But sometimes our then returns a promise object in addition to a value. That is, we now need to determine whether the returned x is a normal value or a Promise object. If it is a normal value, we call resolve directly. If it is a Promise object, we look at the result that the promise object returns, and decide whether the call returns resolve or reject.
Let’s write the test code first
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) = > {
resolve("Success");
// reject(" reject ");
// setTimeout(() => {
// // : the state transition will take two seconds. The code executes the then method first, but the state is still PANDING, but there is no logic for processing PENDING state in then, so we add it.
// resolve("hahha1");
// }, 2000);
});
promise
.then((value) = > {
// console.log(123);
console.log(value);
return other();
})
.then((value) = > {
console.log(value);
});
function other(){
return new Promise((resolve,reject) = >{
resolve('successful 123')})}Copy the code
The next improvement to our original code is as follows
// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
// Class naming conventions
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; // State variables
value = undefined; // Success value
reason = undefined; // Cause of failure
successCallback = []; // Success callback function
failCallback = []; // Failed callback function
resolve = (value) = > {
// State transition on success
if (this.status ! == PENDING)return;
this.status = FULFILLED;
this.value = value;
// this.successCallback && this.successCallback(value);
// console.log(this.successCallback);
while (this.successCallback.length)
this.successCallback.shift()(this.value);
};
reject = (reason) = > {
// State transition on failure
if (this.status ! == PENDING)return;
this.status = REJECT;
this.reason = reason;
// this.failCallback && this.failCallback(reason);
while (this.failCallback.length) this.failCallback.shift()(this.reason);
};
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) = > {
// Then passes two callback functions, one successful and one failed
if (this.status === FULFILLED) {
let x = successCallback(this.value); // This is a big pity
resolvePromise(x, resolve, reject); // Call resolvePromise
} else if (this.status === REJECT) {
failCallback(this.reason); // The status is REJECT callback
} else {
// Wait state, let's first store the callback function
this.successCallback.push(successCallback);
this.failCallback.push(failCallback); }});returnpromise2; }}If x is a Promise, then resolve (reject) is called
function resolvePromise(x, resolve, reject) {
if (x instanceof Promise) {
//x is the promise object
x.then(resolve, reject);
} else {
//x is normalresolve(x); }}module.exports = MyPromise;
Copy the code
We are now ready to implement processing of objects that return a promise value. The problem is that if the promise object itself is returned in the then method, the code above will loop, which is not allowed. In the system’s promise, an error occurs in this case, but no error occurs.
Loop calls:
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) => {
resolve("Success");
// reject(" reject ");
// setTimeout(() => {
// // : the state transition will take two seconds. The code executes the then method first, but the state is still PANDING, but there is no logic for processing PENDING state in then, so we add it.
// resolve("hahha1");
// }, 2000);
});
let p1 = promise.then((value) => { // There is a loop call for promise
// console.log(123);
console.log(value);
return p1; // Returns the invoked promise itself}); p1.then( (value) => { console.log(value); }, (reason) => { console.log(reason); });function other(a) {
return new Promise((resolve, reject) => {
resolve("L" success);
});
}
Copy the code
In the resolvePromise, the x is the same as the promise that invokes it. If so, do not proceed to execute the code. The improved code looks like this
// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
// Class naming conventions
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; // State variables
value = undefined; // Success value
reason = undefined; // Cause of failure
successCallback = []; // Success callback function
failCallback = []; // Failed callback function
resolve = (value) = > {
// State transition on success
if (this.status ! == PENDING)return;
this.status = FULFILLED;
this.value = value;
// this.successCallback && this.successCallback(value);
// console.log(this.successCallback);
while (this.successCallback.length)
this.successCallback.shift()(this.value);
};
reject = (reason) = > {
// State transition on failure
if (this.status ! == PENDING)return;
this.status = REJECT;
this.reason = reason;
// this.failCallback && this.failCallback(reason);
while (this.failCallback.length) this.failCallback.shift()(this.reason);
// while (this.failCallback.length) console.log(this.failCallback);
};
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) = > {
// Then passes two callback functions, one successful and one failed
if (this.status === FULFILLED) {
setTimeout(() = > { ResolvePromise () {resolvePromise () {resolvePromise () {resolvePromise () {resolvePromise (); So that's how you get promise2
let x = successCallback(this.value); // This is a big pity
resolvePromise(promise2, x, resolve, reject);
}, 0);
} else if (this.status === REJECT) {
failCallback(this.reason); // The status is REJECT callback
} else {
// Wait state, let's first store the callback function
this.successCallback.push(successCallback);
this.failCallback.push(failCallback); }});returnpromise2; }}// Determine if x is a promise object, if resolve, if reject
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject( // If the call is circular, a type error is reported
new TypeError("Chaining cycle detected for promise #<Promise>")); }if (x instanceof Promise) {
//x is the promise object
x.then(resolve, reject);
} else {
//x is normalresolve(x); }}module.exports = MyPromise;
Copy the code
Improve code to increase robustness
Actuator error
So let’s remodel it
Some of my insights are:
Start with the end to analyze the problem, by the use of promise to reverse the derivation. Then use the pyramid principle step by step.
From the beginning of mastering the use of tools, human beings are accelerating progress, mastering tools is a good way to achieve