preface
One of the best ways to understand something is to write it yourself. So, here he comes. So without further ado, let’s see how to do that.
“Wuyue return don’t see mountains, Huangshan return don’t see mountains.” Hopefully, after reading this post, you won’t have to read any more about how promises work.
Promise parsing
Let’s start with the Promise.
new Promise((resolve, reject) => {
resolve('hello'); // or reject('hello')}). Then (res = > {}). The catch (err = > {}) -- -- -- -- -- -- -- -- -- -- - line / / decomposition, is also like thislet executor = (resolve, reject) => {
resolve('hello'); // or reject('hello')
}
new Promise(executor)
.then(res => {})
.catch(err => {})
Copy the code
Let’s analyze what features it has:
- 1. Pass two arguments to a constructor (resolve, reject)
- 2. Resolve Callback to execute if successful
- Reject Callback executed when a failure occurs
- 4. Three states
- Pending Indicates the initial state
- This is a big pity
- The operation failed
- 5, The Promise object method then
- 6. Asynchronous implementation
- 7. OnFulfilled and onRejected
- 8. Value penetration
- 9, The Promise object method catch
- 10, The Promise object method all
- 11. The Promise object method race
- The Promise object method resolve
- 13, Promise object method reject
- 13, The Promise object method allSettled (last monthTC39New features coming out)
Next, we will tear down his disguises and reveal his true colors.
The basic structure of Promise implementation
Based on the above analysis results, we will implement the first three functions:
- 1. Pass two arguments to a constructor (resolve, reject)
- 2. Resolve Callback to execute if successful
- Reject Callback executed when a failure occurs
Constructor (executor) {// Define resolveletResolve = res => {} // define rejectletReject = err => {} // Execute executor(resolve, reject) automatically; New Promise(resolve, reject) => {console.log(resolve, reject)'Executed ~')})Copy the code
You can copy the above code to the console to see the effect:
Promise is implemented in three states
Ok, fine. Now, let’s implement her three states.
- 4. Three states
- Pending Indicates the initial state
- This is a big pity
- The operation failed
The PROMISE state has the following characteristics: 1. The initial state of the PROMISE object is pending
2. When you call resolve(success), there will be pending => depressing
3. Reject => Rejected when you call reject
Promsie status can only be changed by pending => depressing/Rejected. Once modified, it cannot be changed again
class Promise {
constructor(executor) {
this.status = "pending"; // Default state this.value; // resolve = this.error; // reject Specifies the reject valuelet resolve = res => {
if(this.status === "pending") {
this.value = res;
this.status = "resolved"; }}let reject = err => {
if(this.status === "pending") {
this.error = err;
this.status = "rejected"; } } executor(resolve, reject); }}Copy the code
1) Pending
Test, if not resolve, reject
New Promise(resolve, reject) => {})Copy the code
So Promise should be the initial state. Let’s test the above code and get the following result:
{status: "pending"}
This is a big pity
When we execute resolve
New Promise((resolve, reject) => {resolve();'Success!');
})
Copy the code
The result is as follows:
3) The operation failed
When you perform reject
New Promise((resolve, reject) => {resolve();'Failed ~')})Copy the code
The Promise object method is then implemented
- 5, The Promise object method then
Promise has a then method, or do we analyze it first, what does that then have?
Then accepts two callbacks
promise.then(onFulfilled, onRejected); // Here we assume that promises inherit from the Promise classCopy the code
We continue with the then method in the previous Promise class:
class Promise {
constructor(executor) {
this.status = "pending"; // Default promise state this.value; // resolve = this.error; // reject Specifies the reject valuelet resolve = res => {
if(this.status === "pending") {
this.value = res;
this.status = "resolved"; }}let reject = err => {
if(this.status === "pending") {
this.error = err;
this.status = "rejected"; }} executor(resolve, reject)} // declarethen
then(onFullfilled, onRejected) {
if(this.status === "resolved") {
onFullfilled(this.value)
}
if(this.status === "rejected") {
onRejected(this.error)
}
}
}
Copy the code
Test it out:
new Promise((resolve, reject) => {
resolve("Success!"); / / or reject ("Failed!")
})
.then(res => {
console.log(res);
}, err => {
console.log(err);
})
Copy the code
Results obtained:
Asynchronous implementation
- 6. Asynchronous implementation
At this point, simple synchronization code is basically implemented, but when resolve is executed within setTimeout, then state is still pending. We need to store success and failure in each array when we call then, and call either Reject or resolve as soon as we call them.
Similar to distributed subscription, the two functions in THEN are stored first. Since promises can have multiple THEN, they are stored in the same array. Call them with forEach when they succeed or fail.
class Promise {
constructor(executor) {
this.status = "pending"; // Default promise state this.value; // resolve = this.error; // reject + this.resolveQueue = []; + this.rejectQueue = []; // Fail to store method arraylet resolve = value => {
if(this.status === "pending") {
this.value = value;
this.status = "resolved"; / / once resolve execution, success of the array function called + enclosing resolveQueue. ForEach (fn = > fn ()); }}let reject = value => {
if(this.status === "pending") {
this.error = value;
this.status = "rejected"; ForEach (fn => fn());} // Once reject is executed, call the array's function + this.rejectQueue.foreach (fn => fn()); } executor(resolve, reject)} // Execute tothenwhenthen(onFullfilled, onRejected) {
if(this.status === "resolved") { this.resolveQueue.push(() => { onFullfilled(this.value); })}if(this.status === "rejected") { this.rejectQueue.push(() => { onRejected(this.error); })} // When the state is pending +if(this.status === "pending"Push (() => {+ onFullfilled(this.value); // onFullfilled(this.value); Push (() => {+ onRejectQueue.push () => {// OnRejectQueue.push () => {+ onRejectQueue.push (); +}) +}}}Copy the code
Chain calls to THEN
- Chain calls to THEN
We often use new Promise().then().then(). This is the chain call, originally used to solve the hell callback. So how do you do that? To do this, we can return a Promise within the first THEN function and pass the value of the new Promise to the next THEN.
In a word:
Chain calls to THEN by returning a new Promise in then!
The code is as follows:
class Promise {
constructor(executor) {
this.status = "pending"; // Default promise state this.value; // resolve = this.error; // reject this. ResolveQueue = []; This. rejectQueue = []; // Callback queue on failurelet resolve = value => {
if(this.status === "pending") {
this.value = value;
this.status = "resolved";
this.resolveQueue.forEach(fn => fn())
}
}
let reject = value => {
if(this.status === "pending") {
this.error = value;
this.status = "rejected";
this.rejectQueue.forEach(fn => fn())
}
}
executor(resolve, reject)
}
then(onFullfilled, onRejected) {
let promise2;
if(this.status === "resolved") {
promise2 = new Promise((resolve, reject) => {
letx = onFullfilled(this.value); resolvePromise(promise2, x, resolve, reject); })}if(this.status === "rejected") {
promise2 = new Promise((resolve, reject) => {
letx = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); })}if(this.status === "pending") {
promise2 = new Promise((resolve, reject) => {
this.resolveQueue.push(() => {
let x = onFullfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.rejectQueue.push(() => {
letx = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); })})}returnpromise2; }} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line / / the code above organize class Promise {constructor (executor) {this status ="pending"; // Default promise state this.value; // resolve = this.error; // reject this. ResolveQueue = []; This. rejectQueue = []; // Callback queue on failurelet resolve = value => {
if(this.status === "pending") {
this.value = value;
this.status = "resolved";
this.resolveQueue.forEach(fn => fn())
}
}
let reject = value => {
if(this.status === "pending") {
this.error = value;
this.status = "rejected";
this.rejectQueue.forEach(fn => fn())
}
}
executor(resolve, reject)
}
then(onFullfilled, onRejected) {
let promise2;
promise2 = new Promise((resolve, reject) => {
if(this.status === "resolved") {
letx = onFullfilled(this.value); // the resolvePromise function handles itselfreturnResolvePromise (promise2, x, resolve, reject); }if(this.status === "rejected") {
let x = onRejected(this.value);
resolvePromise(promise2, x, resolve, reject);
}
if(this.status === "pending") {
this.resolveQueue.push(() => {
let x = onFullfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.rejectQueue.push(() => {
letx = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); }}})); // Return promise to achieve the chain effectreturnpromise2; }}Copy the code
Finally, we complete the above resolvePromise function. For the moment, we return the first then value as x. In this function, we need to determine whether x is a promise or not (important here!). :
- If yes, take its outcome as the outcome of the new promise2 success
- No: directly as a result of the success of the new Promise2
The resolvePromise code is as follows:
/** * the function that handles promise recursion ** promise2 {promise} returns promise * x {*} ourselves by defaultreturnResolve * reject */functionResolvePromise (promise2, x, resolve, reject)if(x === promise2){// reject Error thrownreturn reject(new TypeError('Chaining cycle detected for promise')); } // lock to prevent multiple callsletcalled; // x is not null and x is an object or functionif(x ! = null && (typeof x ==='object' || typeof x === 'function') {// A+ specify, declarethen= xthenmethodslet then= x.then; / / ifthenIf it's a function, it's promise by defaultif (typeof then= = ='function'{/ /thenThen. Call (x, y => {// Only one successful or failed callback can be calledif (called) return;
called = true; // If the result of resolve is still a promise // if the result of resolve is still a promise // ResolvePromise (promise2, y, resolve, reject); }, err => {// Only one can be called on success or failureif (called) return;
called = true; reject(err); // Fail to fail})}else{ resolve(x); }} catch (e) {// Go to catchif (called) return;
called = true; / /thenIf something goes wrong, reject(e); }}else{ resolve(x); }}Copy the code
Then chain calls tests
The complete test code is as follows, which can be copied to the browser console for execution:
functionResolvePromise (promise2, x, resolve, reject)if(x === promise2){// reject Error thrownreturn reject(new TypeError('Chaining cycle detected for promise')); } // lock to prevent multiple callsletcalled; // x is not null and x is an object or functionif(x ! = null && (typeof x ==='object' || typeof x === 'function') {// A+ specify, declarethen= xthenmethodslet then= x.then; / / ifthenIf it's a function, it's promise by defaultif (typeof then= = ='function') {// Just do itthenThen. Call (x, y => {// Only one successful or failed callback can be calledif (called) return;
called = true; ResolvePromise (promise2, y, resolve, reject); }, err => {// Only one can be called on success or failureif (called) return;
called = true; reject(err); // Fail to fail})}else{ resolve(x); }} Catch (e) {// Also a failureif (called) return;
called = true; / /thenIf something goes wrong, reject(e); }}else {
resolve(x);
}
}
class Promise {
constructor(executor) {
this.status = "pending"; // Default promise state this.value; // resolve = this.error; // reject this. ResolveQueue = []; This. rejectQueue = []; // Callback queue on failurelet resolve = value => {
if(this.status === "pending") {
this.value = value;
this.status = "resolved";
this.resolveQueue.forEach(fn => fn())
}
}
let reject = value => {
if(this.status === "pending") {
this.error = value;
this.status = "rejected";
this.rejectQueue.forEach(fn => fn())
}
}
executor(resolve, reject)
}
then(onFullfilled, onRejected) {
let promise2;
promise2 = new Promise((resolve, reject) => {
if(this.status === "resolved") {
letx = onFullfilled(this.value); // the resolvePromise function handles itselfreturnResolvePromise (promise2, x, resolve, reject); }if(this.status === "rejected") {
let x = onRejected(this.value);
resolvePromise(promise2, x, resolve, reject);
}
if(this.status === "pending") {
this.resolveQueue.push(() => {
let x = onFullfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.rejectQueue.push(() => {
letx = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); }}})); // Return promise to achieve the chain effectreturnpromise2; New Promise((resolve, reject) => {resolve(); }).then((res)=>{ console.log('Enter the first then! ')
return new Promise((resolve,reject)=>{
resolve('hello world');
})
}).then((res)=>{
console.log('Enter the second then! ', res);
})
Copy the code
OnFulfilled and onRejected asynchronous call
- This is a big pity and onRejected
Core ideas:
Use setTimeout to solve asynchronous problems
The code is as follows:
class Promise {
constructor(executor) {
this.status = "pending"; // Default promise state this.value; // resolve = this.error; // reject this. ResolveQueue = []; This. rejectQueue = []; // Callback queue on failurelet resolve = value => {
if(this.status === "pending") {
this.value = value;
this.status = "resolved";
this.resolveQueue.forEach(fn => fn())
}
}
let reject = value => {
if(this.status === "pending") {
this.error = value;
this.status = "rejected";
this.rejectQueue.forEach(fn => fn())
}
}
executor(resolve, reject)
}
then(onFullfilled, onRejected) {
let promise2;
promise2 = new Promise((resolve, reject) => {
if(this.status === "resolved") {// async +setTimeout(() => {
letx = onFullfilled(this.value); // the resolvePromise function handles itselfreturnResolvePromise (promise2, x, resolve, reject); +}, 0)}if(this.status === "rejected") {// async +setTimeout(() => {
letx = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); +}, 0)}if(this.status === "pending") {this.resolvequeue.push (() => {// async +setTimeout(() => {
let x = onFullfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
+ }, 0)
})
this.rejectQueue.push(() => {
// 异步
+ setTimeout(() => {
letx = onRejected(this.error); resolvePromise(promise2, x, resolve, reject); +}, 0)})}}); // Return promise to achieve the chain effectreturnpromise2; }}Copy the code
Value penetration call
- 9. Value penetration
new Promise((resolve, reject)=>{
resolve('YoYo');
}).then().then().then().then().then().then().then((res)=>{
console.log(res);
})
Copy the code
When executing more than one THEN, we expect the last THEN to print ‘YoYo’.
The implementation is simple: if onFulfilled is not a function, ignore onFulfilled and return value!
If onRejected is not a function, throw an error without onRejected.
Add the following code to the then of the previous Promise class:
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected : err => { throw err; } <! -... Omit - >}Copy the code
The Promise object method catch
- 10, The Promise object method catch
Core ideas:
Catch is a failed callback, equivalent to executing this.then(null,fn)
class Promise { constructor(executor) { <! -... Omit - >}then(onFullfilled, onRejected) { <! -... } + catch(onRejected) {+return this.then(null, onRejected)
+ }
}
Copy the code
In addition, we also need to use try/catch to catch exceptions outside of several other functions, which are not expanded here.
The Promise object method all
- 10, The Promise object method all
This is a classic interview question!
Promise.all() takes an array of arguments and returns an instance of Promise that will be resolved if all promises in iterable arguments are “resolved” or if the arguments do not contain promises; If the promise parameter has a failed (Rejected), this instance calls back failed (Reject), which is the result of the first failed promise.
Usage:
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo'); }); Promise.all([p1, p2, p3]).then(values => { console.log(values); / / [3, 1337,"foo"]});Copy the code
Here’s how to do it: write it by hand, no tests, come back! Work first ~
Promise.all = function(promises) {
let count = 0;
let res = [];
return new Promise((resolve, reject) => {
for(let i = 0; i<promises.length; i++) {
promises[i].then(res => {
res.push(res);
count++;
if(count === promises.length) resolve(res); }) } }) .catch(err => { reject(err); })}Copy the code
The Promise object method race
- 11. The Promise object method race
Promise.race() also takes an array of Promise objects as arguments and returns a new Promise object. Once a promise in the iterator is resolved or rejected, the returned promise is resolved or rejected.
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
for(leti = 0; i<promises.length; i++) { promises[i].then(resolve, reject); }})}Copy the code
The Promise object method resolve
- The Promise object method resolve
Promise.resolve = function(value) {
returnnew Promise((resolve, reject) => { resolve(value); })}Copy the code
Promise object method reject
- 13, Promise object method reject
Promise.reject = function(value) {
returnnew Promise((resolve, reject) => { reject(value); })}Copy the code
The Promise object method allSettled
Please write your own homework and put it in the reply
Reference: Promises/A +