The use of the Promise
The callback hell
First, let’s take a look at the context in which promises come, assuming the following program
let name = getUserNameById(id);
let score = getScoreByName(name);
let scholarship = getScholarshipByScore(score);
console.log(scholarship);
Copy the code
This program first according to the ID to get the name, then according to the name to get the score, finally according to the score to get the scholarship, finally print out the scholarship. But does the program really work as intended? The answer is no, because JavaScript is asynchronous and typically time-consuming operations are not executed immediately. Instead, functions are stored in a queue until the code is finished. So none of the above functions will be executed immediately, so of course there is no return value, so the above name, score, scholarship are undefined.
To solve this situation, we usually use the form of the callback function. After we get the name based on the ID, we pass the name to the callback function to ensure the “synchronization” effect, so we modify the above code as follows
getUserNameById(id, function(name) {
getScoreByName(name, function(score) {
getScholarshipByScore(score, function(scholarship) {
console.log(scholarship)
})
})
})
Copy the code
But the above program still looks scary, function within function, and a situation like this is called callback hell. Callback hell cannot use try… Catch catches exceptions. Return cannot be used, and the code is poorly readable and error-prone. You may not realize it now, but above we only nested three layers. In reality, there could be many more layers, making the code hard to read.
Simple use of Promise
In order to solve this problem, Promise was introduced in ES6 to solve this problem. Let’s see how to use Promise
let p = new Promise(resolve= > { // fn1
setTimeout(() = > {
console.log("1s")
resolve(1);
}, 1000);
});
p.then(value= > { // fn2
console.log(value);
});
Copy the code
The Promise constructor accepts a callback, fn1, and returns an object that has a then method. The then method also accepts a callback, fn2, only if resolve is received in Fn1. This is a big pity. We can say that the Promise object enters the fulfilled state), and then FN2 will be fulfilled.
Using the code above as an example, we first pass a callback function, fn1, into the Promise, which executes immediately
// The callback function fn1 passed into the constructor
resolve => {
setTimeout(() = > {
console.log("1s")
resolve(1);
}, 1000);
}
Copy the code
Fn2 will be executed only after fn1’s resolve function is executed, so fn2 will be executed only after 1s
// then receives the callback function fn2
value => {
console.log(value);
}
Copy the code
Fn2 receives value as resolve(resolve(1)), and fn2 receives value as resolve(1). The result of the above execution is
You can see that fn2 is executed after the resolve function, which ensures that the code executes sequentially, and instead of writing nested callbacks, “parallel” calls are made using the THEN method.
Chain calls
A chained call means that the then method can be called after it has been called, as follows
let p = new Promise(resolve= > { // fn1
resolve(1);
});
p.then(value= > { // fn2
console.log(value);
return 2;
}).then(value= > { // fn3
console.log(value);
});
Copy the code
The output is as follows
1
2
Copy the code
As shown above, we call THEN twice in a row, and when executed, fn3 is executed after fn2, and the argument value in fn3 is the return value of fn2. The then method can also pass no arguments
let p = new Promise(resolve= > { // fn1
resolve(1);
});
p.then(value= > { // fn2
console.log(value);
return 2;
})
.then() // No arguments are passed
.then(value= > { // fn3
console.log(value);
});
Copy the code
Fn3 accepts value as the return value of the second THEN callback, but since the second THEN has no callback, there is no return value at all. The return value of the previous layer, fn2, is passed to FN3
So the output above is
1
2
Copy the code
In addition to returning the normal value, the then callback can return a Promise object. If a Promise object is returned, then Then the callback function in THEN will be executed only after the resolve method in the Promise object executes, which enters the depressing state. And the parameters required by the callback function in THEN are the values passed in by the return Promise object resolve. The following
let p = new Promise(resolve= > {
resolve(1);
});
p.then(value= > { // fn1
console.log(value);
return new Promise(resolve= > {
setTimeout(() = > {
console.log("Then returns a Promise");
resolve(2);
}, 1000);
})
})
.then(value= > { // fn2
console.log(value);
});
Copy the code
Fn2 will be implemented only after the Promise returned by FN1 enters the fulfilled state
The above execution result is
Prototype method
The resolve and THEN methods were introduced above, and the callback received by then is executed only after we execute the resolve and THEN methods. The Promise callback has two arguments, resolve and reject. When a reject error occurs, we call reject and say that the Promise object is in the rejetced state. The then method does not execute. Instead, a catch method receives a callback that is used to handle errors. The catch callback will be executed only when the Promise object enters the Rejected state. Similar to then, the callback in catch takes the arguments passed in reject, as follows
let p = new Promise((resolve, reject) = > {
reject("error");
}).then(() = > {
console.log("Then method executes.");
}).catch(error= > {
console.log("Catch method execution");
console.log("message: " + error);
});
Copy the code
The results for
catchMethods to performmessage: error
Copy the code
We called reject, which does not execute the callbacks in then. Instead, we execute the callbacks in catch.
The then method accepts two callbacks, the first to process resolve and the second to process reject
let p = new Promise((resolve, reject) = > { reject("error"); }).then(() = > { console.log("Then method executes."); }, error= > { console.log("Catch method execution"); console.log("message:" + error); }) Copy the code
Catch (onError) then(null, onError)
Sometimes we want to execute a function, whether it is successful or not. The function is usually used to recycle resources. Finally function is used to complete this function, and this function also receives a callback function
let p = new Promise((resolve, reject) = > {
/ / rejected state
reject();
}).then(() = > {
console.log("Then method executes.");
}, () = > {
console.log("Catch method execution");
}).finally(() = > {
console.log("Finally executed")});Copy the code
The final execution result is
catchMethods to performfinallyTo perform theCopy the code
Now we’ll change reject() to resolve()
let p = new Promise((resolve, reject) = > {
/ / fulfilled state
resolve();
}).then(() = > {
console.log("Then method executes.");
}, () = > {
console.log("Catch method execution");
}).finally(() = > {
console.log("Finally executed")});Copy the code
The result of this execution is
The then method executesfinallyTo perform theCopy the code
So the callback function in finally will be executed in either state.
A static method
Static methods refer to methods that are invoked directly through promises.
resolve
Let’s start with the resolve method, which takes an argument and returns a Promise object with a different value depending on the argument
-
The argument is a Promise object that returns directly
let p1 =new Promise((resolve, reject) = > { resolve(123); }); let p2 = Promise.resolve(p1); console.log(p1 === p2); // true Copy the code
-
If the argument is an object that contains a THEN method (also called a Thenable object), the then method is executed immediately. The then method takes two arguments, the resolve and Reject methods of the returned Promise
let p = Promise.resolve({ then(resolve, reject) { resolve(123); }}); p.then(value= > { console.log(value); / / 123 }); Copy the code
-
If the parameters are not the above two cases, a depressing Promise will be returned, which will pass the parameters to the later THEN
let p = Promise.resolve("123"); p.then(value= > { console.log(value); / / 123 }); Copy the code
all
This method will also return a Promise object, which accepts an array of Promise objects. The returned Promise will become a fulfilled state only after all the Promise objects in the array become a fulfilled state
let p1 = new Promise(resolve= > {
setTimeout(() = > {
console.log("p1");
resolve(1);
}, 1000);
});
let p2 = new Promise(resolve= > {
setTimeout(() = > {
console.log("p2");
resolve(2);
}, 2000);
});
let p3 = new Promise(resolve= > {
setTimeout(() = > {
console.log("p3");
resolve(3);
}, 3000);
});
Promise.all([p1, p2, p3]).then(results= > {
console.log(results); // [1, 2, 3]
});
Copy the code
The three Promise objects will become a big pity after 1s, 2s and 3s, respectively. Therefore, the returned Promise object will become a big pity after 3s. And an array of the values passed to resolve by the three Promise objects is passed into the resolve of the returned Promise
The result of executing the above code is
race
The race method will also return a Promise object. Like the All method, it will also accept an array of Promise objects, but not after all the Promise objects become fulfilled, The return Promise object will become a big pity, but these Promise objects compete. When one of the fastest Promise objects becomes a big pity, the returned Promise object will become a big pity
let p1 = new Promise(resolve= > {
setTimeout(() = > {
console.log("p1");
resolve(1);
}, 1000);
});
let p2 = new Promise(resolve= > {
setTimeout(() = > {
console.log("p2");
resolve(2);
}, 2000);
});
let p3 = new Promise(resolve= > {
setTimeout(() = > {
console.log("p3");
resolve(3);
}, 3000);
});
Promise.race([p1, p2, p3]).then(result= > {
console.log(result);
});
Copy the code
And the value passed to the resolve of the fastest fulfilled Promise object will be passed to the resolve method of the returned Promise object
So the execution result of the above program is
It can be seen that after P1 becomes the depressing state, the returned Promise also becomes the depressing state, and the value passed in to P1’s resolve is obtained.
Realize the Promise
Simple implementation
Here’s a simple Promise to implement. First of all, we should make it clear that the Promise has three states: pending, depressing and Rejected. The initial Promise is a pending state, which will become the depressing state after invoking the resolve method. When the state is fulfilled, the first callback function in then will be executed. Reject changes to rejected, and when rejected is in, the second callback in then is executed or the callback in catch is executed. Moreover, the fulfilled state and the rejected state can only be changed from pending state, and generally become the fulfilled state or the rejected state. Then the state cannot be changed again
class Promise {
// Three states of Promise
PENDING = 'pending';
FULFILLED = 'fulfilled';
REJECTED = 'rejected';
// Current status
state = this.PENDING;
// The value passed to the callback function in then
value = null;
// The constructor accepts a callback, executes it immediately and passes _resolve and _reject
constructor(fn) {
fn(this._resolve.bind(this), this._reject.bind(this));
}
// If the THEN method is fulfilled but the state is not fulfilled, the callback function passed to the THEN will be saved first
callbacks = [];
_resolve(value) {
// If the status is not pending, the status has changed and the execution is no longer required
if (this.state ! = =this.PENDING) {
return;
}
// The value to be passed to the callback function in then is saved
this.value = value;
// Change the state
this.state = this.FULFILLED;
// Execute the ondepressing function passed to then
this.callbacks.forEach(callback= > callback.onFulfilled(this.value));
}
_reject(error) {
// Process is similar to reject
if (!this.PENDING) {
return;
}
this.value = error;
this.state = this.REJECTED;
this.callbacks.forEach(callback= > callback.onRejected(this.value));
}
then(onFulfilled, onRejected) {
// If the current state is pending, store the callback in the callbacks function and wait until the state changes
if (this.state === this.PENDING) {
this.callbacks.push({
onFulfilled,
onRejected
});
return;
}
// If the state is depressing, perform the ondepressing method
if (this.state === this.FULFILLED) {
onFulfilled(this.value);
return;
}
/ / same as above
if (this.state === this.REJECTED) {
onRejected(this.value);
return; }}}Copy the code
The above comments describe the functionality of the program, which is relatively easy to understand. Now let’s test if it works
let p = new Promise(resolve= > {
setTimeout(() = > {
console.log(After the "1 s");
resolve(1);
}, 1000);
});
p.then(value= > {
console.log(value);
});
Copy the code
The results are as follows
Chain calls
Now let’s implement the chained call. To implement the chained call we need to return a Promise. Each time we call the THEN method we return a new Promise, modified as follows
_resolve(value) {
if (this.state ! = =this.PENDING) {
return;
}
this.value = value;
this.state = this.FULFILLED;
// This is not a simple call because it needs to return the value of the then callback function
this.callbacks.forEach(callback= > this._handle(callback));
}
_reject(error) {
if (!this.PENDING) {
return;
}
this.value = error;
this.state = this.REJECTED;
/ / to resolve
this.callbacks.forEach(callback= > this._handle(callback));
}
then(onFulfilled, onRejected) {
// Return a new Promise
return new Promise((resolve, reject) = > {
this._handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
_handle(callback) {
// If the state is pending, delay the execution of the callback
if (this.state === this.PENDING) {
this.callbacks.push(callback);
}
if (this.state === this.FULFILLED) {
// If the then method does not pass in the ondepressing callback, the value returned by the previous layer will be passed in
if(! callback.onFulfilled) { callback.resolve(this.value);
return;
}
// If there is an onFulFilled function, pass the return value of the onFulFilled function into the resolve of the returned Promise object
try {
// The callback function passed by the user may fail, so try... Wrap up
let ret = callback.onFulfilled(this.value);
callback.resolve(ret);
} catch (e) {
// When the passed callback fails, the Promise changes to the Rejected statecallback.reject(e); }}if (this.state === this.REJECTED) {
if(! callback.onRejected) { callback.reject(this.value);
return;
}
let ret = callback.onRejected(this.value); callback.reject(ret); }}Copy the code
Now let’s test if we can make a chain call
let p = new Promise(resolve= > {
setTimeout(() = > {
console.log(After the "1 s");
resolve(1);
}, 1000);
});
p.then(value= > {
console.log(value);
return 2;
}).then() // The value returned by the previous then function is passed in
.then(value= > {
console.log(value);
});
Copy the code
The above execution result is
The last problem is that if the then callback returns a Promise, then we need to determine the value in _resolve. If the value is a Promise, call p1. The resolve method of p2, the Promise object returned by then, should be executed after the resolve execution of P1. Change _resolve to the following
_resolve(value) {
if (this.state ! = =this.PENDING) {
return;
}
// Just add the following code
if (value instanceof Promise) {
// The current resolve execution should follow the value resolve execution
value.then(this._resolve.bind(this), this._reject.bind(this));
return;
}
this.value = value;
this.state = this.FULFILLED;
this.callbacks.forEach(callback= > this._handle(callback));
}
Copy the code
Now let’s put it to the test
let p = new Promise(resolve= > {
setTimeout(() = > {
console.log(After the "1 s");
resolve(1);
}, 1000);
});
p.then(value= > {
console.log(value);
return new Promise(resolve= > {
setTimeout(() = > {
console.log("One second later.");
resolve(2);
}, 1000)}); }).then(value= > {
console.log(value);
});
Copy the code
The execution result is
Prototype method
We continue to implement the prototype methods of the Promise object, catch and finally. The implementation of catch is simple
catch(onRejected) {
return this.then(null, onRejected);
}
Copy the code
Next comes implementing the finally method, the most obvious version of which is
finally(onDone) {
return this.then(onDone, onDone);
}
Copy the code
The onDone method will be implemented whether in the fulfilled state or the Rejected state, but using this method has disadvantages
onDone
The method is executed on failure or success, so it should have no arguments, but usethen(onDone, onDone)
Is passed in as a parameter- if
onDone
Returns aPromise
Object, then will changefinally
The returnedPromise
The state of the
For the above consideration, we use the following implementation
finally(onDone) {
if (typeofonDone ! = ='function') {
return this.then();
}
// onDone() executes regardless of success and takes no arguments
In addition, executing finally does not affect the previous Promise state
return this.then(
value= > Promise.resolve(onDone()).then(() = > value),
error= > Promise.resolve(onDone()).then(() = > {throw error})
);
}
Copy the code
A static method
Finally, implement the Promise static method. First, implement the resolve method, the use of which is described in the usage section, so the implementation code is posted directly here
static resolve(value) {
// If a Promise object is passed in, return it
if (value instanceof Promise) {
return value;
}
// If thenable is passed in, then of the object is immediately executed and resolve and reject are passed in
if (value && typeof value === 'object' && typeof value.then === 'function') {
return new Promise((resolve, reject) = > value.then(resolve, reject));
}
if (value) {
// If not, and value exists, pass value directly to resolve
return new Promise(resolve= > resolve(value));
} else {
return new Promise(resolve= >resolve()); }}Copy the code
The all method is also described above. Only when all the incoming Promise objects become the depressing state, the returned Promise objects will become the depressing state. Therefore, we use a variable to count the number of Promise objects that have become the depressing state. When all Promise objects become the depressing state, the resolve method is executed. The returned Promise object becomes the fulfilled state, which is fulfilled as follows
static all(promises) {
return new Promise((resolve, reject) = > {
// Number of Promise objects
let itemLength = promises.length;
// Count the number of Promise objects that have become a pity state
let finishedPromise = 0;
// The array returned
let results = Array.from({length: itemLength});
promises.forEach(promise= > {
promise.then(result= > {
results[finishedPromise] = result;
finishedPromise++;
// When all the Promise objects become a big pity, the state of the returned Promise object becomes a big pity
if(finishedPromise == itemLength) resolve(results); }},error= > {
// If only one of them changes to Rejected, then it changes to the Rejected statereject(error); })})})}Copy the code
The use of the race method was also introduced, where we take advantage of the resolve method’s once-only execution and can quickly write code like this
static race(promises) {
return new Promise((resolve, reject) = > {
promises.forEach(promise= > {
promise.then(result= > {
resolve(result);
}, error= >{ reject(error); })})})}Copy the code
Because resolve is fulfilled only once, only the Promise object that becomes the fulfilled state first can pass in its resolve value.
The complete code
class Promise {
PENDING = 'pending';
FULFILLED = 'fulfilled';
REJECTED = 'rejected';
state = this.PENDING;
value = null;
constructor(fn) {
fn(this._resolve.bind(this), this._reject.bind(this));
}
callbacks = [];
_resolve(value) {
if (this.state ! = =this.PENDING) {
return;
}
if (value instanceof Promise) {
value.then(this._resolve.bind(this), this._reject.bind(this));
return;
}
this.value = value;
this.state = this.FULFILLED;
this.callbacks.forEach(callback= > this._handle(callback));
}
_reject(error) {
if (!this.PENDING) {
return;
}
this.value = error;
this.state = this.REJECTED;
this.callbacks.forEach(callback= > this._handle(callback));
}
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) = > {
this._handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onDone) {
if (typeofonDone ! = ='function') {
return this.then();
}
return this.then(
value= > Promise.resolve(onDone()).then(() = > value),
error= > Promise.resolve(onDone()).then(() = > {throw error})
);
}
_handle(callback) {
if (this.state === this.PENDING) {
this.callbacks.push(callback);
}
if (this.state === this.FULFILLED) {
if(! callback.onFulfilled) { callback.resolve(this.value);
return;
}
try {
let ret = callback.onFulfilled(this.value);
callback.resolve(ret);
} catch(e) { callback.reject(e); }}if (this.state === this.REJECTED) {
if(! callback.onRejected) { callback.reject(this.value);
return;
}
let ret = callback.onRejected(this.value); callback.reject(ret); }}static resolve(value) {
if (value instanceof Promise) {
return value;
}
if (value && typeof value === 'object' && typeof value.then === 'function') {
return new Promise((resolve, reject) = > value.then(resolve, reject));
}
if (value) {
return new Promise(resolve= > resolve(value));
} else {
return new Promise(resolve= >resolve()); }}static all(promises) {
return new Promise((resolve, reject) = > {
let itemLength = promises.length;
let finishedPromise = 0;
let results = Array.from({length: itemLength});
promises.forEach(promise= > {
promise.then(result= > {
results[finishedPromise] = result;
finishedPromise++;
if(finishedPromise == itemLength) { resolve(results); }},error= >{ reject(error); })})})}static race(promises) {
return new Promise((resolve, reject) = > {
promises.forEach(promise= > {
promise.then(result= > {
resolve(result);
}, error= >{ reject(error); })})})}}Copy the code
Refer to the article
- 1. Start with a basic Promise
- (2) : The Promise chain call
- Schematic Promise implementation principle (three) : Promise prototype method implementation
- Schematic Promise implementation principle (four) : Promise static method implementation
- Promise. All and Promise. Race source code implementation