Writing in the front
Promise is the core grammar in ES6, and it’s one of the most frequently asked questions in interviews, so knowing the rules is a must. Learning to write by hand will help you stand out in an interview and take a solid step toward getting an offer
In my previous article, I translated the Promise A+ specification. Before reading this article, I recommend reading: 【Promise】Promises/A+ Chinese translation
Write a Promise
An overview of the Promise
Promise, a scheme for managing asynchronous programming, is a constructor that creates instances each time available new is used; It has three states: The three states are not affected by the outside world. The status can only change from pending to fullfilled and the status of rejected will not change once the status changes. Once the status changes, the status will not change again. Resolve, Reject, catch, finally, then, All, race, and done are thrown. In the latest proposal, the allSettled method is added, which will return either success or failure. We implement the whole Promise ourselves
The executor function
As we know, when a Promise instance is created, the executor function is immediately executed. The executor function passes two arguments, resolve and reject. If the executor function executes incorrectly, the Promise instance status changes to Rejected
class MyPromise{
constructor(executor) {
this.status = "pending"; // Initialize status as pending this.value = undefined; Resolve (resolved); resolve (resolved); resolve (resolved)let resolve = result => {
if(this.status ! = ="pending") return; // Once the status changes, it does not change this.status ="resolved"; this.value = result; Reject (reject); reject (reject); reject (reject); reject (reject); reject (reject); reject (reject)let reject = reason => {
if(this.status ! = ="pending") return;
this.status = "rejected"; this.value = reason; Reject try {executor(resolve, reject)} catch(err) {reject(err)}}}Copy the code
So let’s verify, what does Promise look like now
let p1 = new MyPromise((resolve, reject) => {
resolve(1);
})
let p2 = new MyPromise((resolve, reject) => {
reject(2);
})
console.log(p1);
console.log(p2);
Copy the code
As you can see, the state has changed, and the values inside are also the result of success and the cause of failure. The then method takes two arguments. The first argument is executed on success and the second argument is executed on failure. Chain calls to then are the same as array calls, and each execution returns a Promise instance. If the successful function in the first THEN is null, it will continue to look down until the function that is not NULL is executed. The result of the previous THEN directly affects which function succeeds or fails in the next THEN
Then method
then(resolveFn, rejectFn) {// If the two arguments passed are not functions, the result will be returned directlylet resolveArr = [];
let rejectArr = [];
if(typeof resolveFn ! = ="function") {
resolveFn = result => {
returnresult; }}if(typeof rejectFn ! = ="function") {
rejectFn = reason => {
returnMyPromise.reject(reason); }}return new Mypromise((resolve, reject) => {
resolveArr.push(result => {
try {
let x = resolveFn(result);
if(x instanceof MyPromise) {
x.then(resolve, reject)
return;
}
resolve(x);
} catch(err) {
reject(err)
}
})
rejectArr.push(reason => {
try {
let x = rejectFn(reason);
if(x instanceof MyPromise) {
x.then(resolve, reject)
return;
}
resolve(x);
} catch(err) {
reject(err)
}
})
})
}
Copy the code
Let’s clean up the above code
class MyPromise{
constructor(executor) {
this.status = "pending"; // Initialize status as pending this.value = undefined; This.resolvearr = []; this.resolvearr = []; / / initializationthenThis.rejectarr = []; / / initializationthen// Define the change method, because it seems like resolve and reject have a lot in common 🤔let change = (status, value) => {
if(this.status ! = ="pending") return; // This. Status = status; this.value = value; // Determine the successful or failed method to execute based on the statuslet fnArr = status === "resolved"? this.resolveArr : this.rejectArr; // the methods in fnArr execute fnarr.foreach (item => {if(typeof item ! = ="function") return; item(this. value); Resolve (resolved); resolve (resolved); resolve (resolved)let resolve = result => {
change("resolved"Reject (reject); reject (reject); reject (reject); reject (reject); reject (reject)let reject = reason => {
change("rejected", reason); Reject {executor(resolve, reject)} Catch (err) {reject(err)}}then(resolveFn, rejectFn) {// If the two arguments passed are not functions, the result will be returned directlyif(typeof resolveFn ! = ="function") {
resolveFn = result => {
returnresult; }}if(typeof rejectFn ! = ="function") {
rejectFn = reason => {
returnMyPromise.reject(reason); }}return new MyPromise((resolve, reject) => {
this.resolveArr.push(result => {
try {
letx = resolveFn(result); // If x is a Promise instance, continue the callthenMethods the = = >thenThe realization of the chainif(x instanceof MyPromise) {
x.then(resolve, reject)
return; } // Instead of a promise instance, execute the successful resolve(x) method directly; } catch(err) { reject(err) } }) this.rejectArr.push(reason => { try {let x = rejectFn(reason);
if(x instanceof MyPromise) {
x.then(resolve, reject)
return;
}
resolve(x);
} catch(err) {
reject(err)
}
})
})
}
}
Copy the code
So let’s see what happens
new MyPromise((resolve, reject) => {
resolve(1);
}).then(res => {
console.log(res, 'success');
}, err => {
console.log(err, 'error');
})
Copy the code
At this point, the problem arises and we find that there seems to be no output. What if we make a small change to the above test example?
new MyPromise((resolve, reject) => {
setTimeout(_ => {
resolve(1);
}, 0)
}).then(res => {
console.log(res, 'success'); / / 1"success"
}, err => {
console.log(err, 'error');
})
Copy the code
This is because the executor function is executed immediately after the Promise instance is created, but the then method is not executed, so the array of successes and failures will be empty. Why does setTimeout work? This is because in the event queue mechanism, setTimeout will be placed in the event queue and executed after the main thread is completed. Then method will store the success or failure function, so either the success or failure array already has a value, and then execute completely 👌 ~
However, we can’t use setTimeout as a solution. Since we are encapsulating, we need to solve the problem in the encapsulated function. In this way, we can also use resolve and reject to determine whether there is a value in the array. We can use setTimeout to delay execution as follows
Resolve, execute on success, change the state to Resolved, and return the resultletResolve = result => {// If there are values in the array, change the state immediatelyif(this.resolveArr.length > 0) {
change("resolved", result)} // If there is no value, the state change is delayedlet timer = setTimeout(_ => {
change("resolved", result) clearTimeout(timer); Reject (reject); reject (reject); reject (reject); reject (reject); reject (reject); reject (reject)letReject = Reason => {// If there are values in the array, change the state immediatelyif(this.rejectArr.length > 0) {
change("rejected", reason); } // If there is no value, change the state laterlet timer = setTimeout(_ => {
change("rejected", reason); clearTimeout(timer); }}, 0)Copy the code
Now let’s try it again
New MyPromise((resolve, reject) => {resolve();'I did it, ho ho ho ~~~~');
reject('I've already made it, you can't make me fail, huh?');
}).then(res => {
console.log(res, 'success'); ~~~~ success}, err => {console.log(err,'error'); New MyPromise((resolve, reject) => {reject();'FAILED, I am so aggrieved, whoo-hoo ~ ~');
resolve('It has failed ~ ~ ~');
}).then(res => {
console.log(res, 'success');
}, err => {
console.log(err, 'error'); New MyPromise((resolve, reject) => {reject(resolve, reject))'FAILED, I am so aggrieved, whoo-hoo ~ ~');
resolve('It has failed ~ ~ ~');
}).then(res => {
console.log(res);
}, err => {
console.log(err, 'error'); // failed, I am so wronged, whoooo ~ ~ errorreturn 'I will work hard, will not be knocked down by difficulties, I will succeed!! '
}).then(res1 => {
console.log(res1, 'After unremitting efforts, I finally succeeded in the second time.'); // I will work hard, will not be knocked down by difficulties, I want to succeed!! }, err1 => {console.log(err1,'Failed a second time');
})
Copy the code
This perfectly solves the problem of not executing the THEN method on the first call. At the same time, the implementation of the chain call. For the chained call, I’m going to say a couple of words, but in fact, the chained call of the array is because the last time it returned was the same instance.
Catch method
The catch method catches the exception, which is the same as the second callback of the then method
catch(rejectFn) {
return this.then(null, rejectFn)
}
Copy the code
Resolve method
We know that Promsie can also be used this way
let p1 = MyPromise.resolve(1);
console.log(p1);
Copy the code
We expected one, but now we’re sure to throw an error: myPromise.resolve is not a method
Now we need to encapsulate the resolve method. We need to make it clear that after resolve, a Promise supports a chain call to THEN, so we need to execute the resolve method and return a Promise instance
Static resolve(result) {// Returns a new Promise instance, and executes the resolve method in the promise instancereturn new MyPromise(resolve => {
resolve(result)
})
}
Copy the code
Reject method
Just like the resolve method, except that it accepts a failed function
Static Reject (reason) {// Returns a new PROMISE instance, which executes the Reject method in the Promise instancereturnnew MyPromise((_, reject) => { reject(reason); })}Copy the code
The done method
In the introduction to the ES6 standards, the done method is explained as follows: Whether the callback chain of a Promise object ends in a then method or a catch method, if the last method throws an error, it may not be caught. To do this, Promise provides a done method that is always at the end of the fallback chain, guaranteed to throw any errors that might occur. Now that we know what this method does, let’s start writing
done(resolveFn, rejectFn) {
this.then(resolveFn, rejectFn)
.catch(reason => {
setTimeout(() => { throw reason; }, 0)})}Copy the code
It can accept callback functions with the fulfilled and Rejected states, or it can provide no arguments. But anyway, the done method catches any possible errors and throws them globally, right
The finally method
A finally method is a method that executes whether it succeeds or fails. Methods like this and the complete method ina small program are examples
finally(finallyFn) {
let P = this.constructor;
return this.then(
value => P.resolve(finallyFn()).then(() => value),
reason => P.reject(finallyFn()).then(() => reason)
)
}
Copy the code
So let’s verify that
new MyPromise((resolve, reject) => {
reject('FAILED, I am so aggrieved, whoo-hoo ~ ~');
resolve('It has failed ~ ~ ~');
}).then(res => {
console.log(res);
}, err => {
console.log(err, 'error'); // failed, I am so wronged, whoooo ~ ~ errorreturn 'I will work hard, will not be knocked down by difficulties, I will succeed!! '
}).finally(() => {
console.log('Has it been executed?'); // This will output"Has it been carried out?"
})
Copy the code
All methods
The all method takes an array and returns it when each instance of the array succeeds. It also returns an array, with each argument returning the result of a corresponding promise. If any of the items fails, all returns a failure
Static all(promiseList) {// Returns a new instance, which can be used after the callthenCatch and other methodsreturn new MyPromise((resolve, reject) => {
letIndex = 0, // Success count results = []; // The result returnedfor(let i = 0; i < promiseList.length; i++) {
letitem = promiseList[i]; // If item is not a Promise instanceif(! (item instanceof MyPromise))return;
item.then(result => {
index++;
results[i] = result;
if(index === promiseList.length) { resolve(results); } }).catch(reason => { reject(reason); })}})}Copy the code
Let’s verify that
// 1. Failure occurslet p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3])
.then(res => {
console.log(res);
}).catch(err => {
console.log(err, 'err'); / / 2"err"}) // 2. There is no failurelet p1 = MyPromise.resolve(1);
let p2 = MyPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3])
.then(res => {
console.log(res, 'success'); / / [1, 2, 3]"success"
}).catch(err => {
console.log(err, 'err');
})
Copy the code
Race method
The RACE method also takes an array of parameters, each of which is a Promise instance, and returns the result of the Promise instance method that changed its state the fastest
static race(promiseList) {
return new MyPromise((resolve, reject) => {
promiseList.forEach(item => {
if(! (item instanceof MyPromise))return;
item.then(result => {
resolve(result);
}).catch(err => {
reject(err)
})
})
})
}
Copy the code
validation
/ / 1.let p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
letp3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res); / / 1'success'
}).catch(err => {
console.log(err, 'err');
})
// 2.
let p1 = MyPromise.reject(1);
let p2 = MyPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res, 'success');
}).catch(err => {
console.log(err, 'err'); / / 1'err'}) / / 3.let p1 = MyPromise.reject(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.reject(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res, 'success');
}).catch(err => {
console.log(err, 'err'); / / 1'err'
})
Copy the code
Try implementing the allSettled method
The allSettled method also receives array parameters, but it returns either on success or failure
static allSettled(promiseList) {
return new MyPromise((resolve, reject) => {
let results = [];
for(let i = 0; i < promiseList.length; i++) {
let item = promiseList[i];
if(! (item instanceof MyPromise))return; item.then(result => { results[i] = result; }, reason => { results[i] = reason; }) resolve(results); }})}Copy the code
validation
/ / 1.let p1 = MyPromise.resolve(1);
let p2 = MyPromise.resolve(2);
letp3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res); / / [1, 2, 3]'success'
}).catch(err => {
console.log(err, 'err');
})
// 2.
let p1 = MyPromise.reject(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.reject(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res, 'success'); / / [1, 2, 3]'success'
}).catch(err => {
console.log(err, 'err'); }) / / 3.let p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res, 'success'); / / [1, 2, 3]'success'
}).catch(err => {
console.log(err, 'err');
})
Copy the code
The last
This article implements a Promise of their own, source code has been uploaded to Github: MyPromise source address. Get it by yourself if you need it
You can also follow my public account “Web front-end diary”, more timely to receive push messages ~