Writing in the front
For front-end novices, Promise is a difficult thing to learn and requires a lot of details to understand. For front-end interviews, promises are the most frequently asked question, especially when it comes to hand-torn source code. It is well known that the JavaScript language execution environment is “single-threaded”.
Single thread means that only one task can be completed at a time. If there are multiple tasks, they must wait in a queue. The first task is completed, and then the next task is executed. This “single thread” mode is inefficient and takes a long time to execute.
To solve this problem, there is asynchronous mode, also known as asynchronous programming.
Asynchronous programming
The so-called “asynchronous” simply means that a task is divided into two parts, the first part is executed first, and then the other parts are executed. When the first part has the execution result, the second part is executed again.
There are two reasons for asynchronous programming in JavaScript:
- JavaScript is single-threaded.
- To improve CPU utilization.
While improving CPU utilization, it also increases development difficulty, especially in code readability.
Then the scenarios where asynchrony exists are:
- Fs file operation
require("fs").readFile("./index.html",(err,data)=>{})
Copy the code
- Database operations
- AJAX
$.get("/user",(data)=>{})
Copy the code
- The timer
setTimeout(()=>{},2000)
Copy the code
What is Promise
Promise to understand
(1) Abstract expression
Promise
Is a new technology (es6
Specification)Promise
isjs
A new solution for asynchronous programming in
(2) Specific expressions
- Grammatically speaking:
Promise
Is a constructor - In terms of function:
Promise
The object encapsulates an asynchronous operation and retrieves its success/failure result
Why use Promise
(1) More flexibility in the way callback functions are specified
promise
: Starts an asynchronous task => Returnspromise
= > to objectpromise
Object binding callback function
(2) Support chain call mode, can solve the problem of callback hell
- What is callback hell?
Callback hell is the nested use of callback functions, the result of the asynchronous execution of the external callback function is the nested callback execution condition
- Callback hell’s faults
- Unreadable
- Not convenient for exception handling
- The solution
The state of the Promise
- Promise must have three states: Pending, Rejected, and Resolved
- If the state of a Promise is pending, it can become a pity or a failed Promise
- If a promise is a success state, it cannot transition to any state and requires a success value that cannot be changed
- If a promise is a failed state, it cannot be converted to any state, and a reason for the failure is required, and the value cannot be changed
The state of the Promise changes
Pending refers to properties built into the instance state
(1)pending
变为resolved/fullfilled
(2) pending to rejected
Note: There are only two state changes of a Promise, and a Promise object can only be changed once. No matter whether it fails or succeeds, it will get a result output. The successful result is generally value, and the failed result is generally Reason.
Whether the state succeeds or fails, promises are returned.
The value of the Promise
Another property in the instance object, [PromiseResult], holds the resolve/ Reject result of the success/failure of the asynchronous task.
The Promise of the API
Apis in Handwriting Promide:
Promise(executor){}
executor
Actuators:(resolve,reject)=>{}
resolve
: internally defines the function we need to call on successvalue=>{}
reject
: internally defines the function we call when failure occursreason=>{}
Note: Executor calls immediately and synchronously within a Promise, and asynchronous operations are performed in the executor
Prototype. Then (onResolved, Rejected)=>{}
onResolved
Function: successful callback functionvalue=>{}
rejected
Function: failed callback functionreason=>{}
Note: Return a new Promise object by specifying a success callback for a success value and a failure callback for a failure Reason
(3)Promise.prototype.catch
方法:(onRejected)=>{}
The first three are the handwritten code that will be implemented in this article, but Promise also has other apis.
(1) Promise. Prototype. Finally () method
The finally() method is used to specify actions that will be performed regardless of the final state of the Promise object. Regardless of the final state of the promise, the callback specified by the finally method is executed after the callback specified by then or catch is executed.
promise
.then(result= >{...}). The catch (error= >{...}). Finally,() = > {···});
Copy the code
(2) the Promise. All () method
The promise.all () method is used to wrap multiple Promise instances into a new Promise instance.
const p = Promise.all([p1, p2, p3]);
Copy the code
The state of P is determined by P1, P2 and P3, which can be divided into two cases.
-
Only when the states of P1, P2 and P3 become depressing, the state of P will become depressing. At this time, the return values of P1, P2 and P3 will form an array and be passed to the callback function of P.
-
If p1, P2, and P3 are rejected, P becomes rejected, and the return value of the first rejected instance is passed to p’s callback function.
(3) Promise. Race () method
The promise.race () method again wraps multiple Promise instances into a new Promise instance.
const p = Promise.race([p1, p2, p3]);
Copy the code
If one instance of P1, P2, and P3 changes state first, then the state of P changes. The return value of the first changed Promise instance is passed to p’s callback.
(4) Promise. AllSettled () method
The promise.allSettled () method takes a set of Promise instances as parameters and wraps them into a new Promise instance. The wrapper instance will not complete until all of these parameter instances return the result, whether this is fulfilled or Rejected.
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3')];await Promise.allSettled(promises);
removeLoadingIndicator();
Copy the code
This method returns a new Promise instance. Once it is fulfilled, the state is always fulfilled and will not become Rejected. After the state becomes depressing, the Promise listener receives an array of parameters, each member corresponding to an incoming promise.allSettled () Promise instance.
(5) Promise. Any () method
ES2021 introduces the promise.any () method. The method takes a set of Promise instances as parameters and returns them wrapped as a new Promise instance. As long as one parameter instance becomes a depressing state, the packaging instance will become a depressing state. If all parameter instances become the Rejected state, the wrapper instance becomes the Rejected state.
(6) Promise. Reject (reason) method
The promise.Reject (Reason) method also returns a new Promise instance with a state of Rejected.
const p = Promise.reject('Wrong');
/ / is equivalent to
const p = new Promise((resolve, reject) = > reject('Wrong'))
p.then(null.function (s) {
console.log(s)
});
/ / make a mistake
Copy the code
(7) The promise.resolve () method sometimes needs to convert an existing object into a Promise object, which the promise.resolve () method does.
Promise.resolve('foo')
/ / equivalent to the
new Promise(resolve= > resolve('foo'))
Copy the code
Who came first by changing promsie status and specifying the callback function?
(1) Both are possible. It is normal to specify the callback function and then change the state, but it is also possible to change the state and then specify the callback function.
(2) How to change the state and then specify the callback?
- Called directly in the executor
resolve/reject
- Delay making the call longer
then
(3) When will the data be available?
- If the callback is specified first, the callback function is called when the state changes, and the data is returned
- If the state is changed first, the callback function is called when the callback is specified, and the data is returned
Example:
let p = new Promise((resolve,reject) = >{
resolve("It worked");
reject("Failed.");
});
p.then((value) = >{
console.log(value);
},(reason) = >{
console.log(reason);
})
Copy the code
Promise specification
- A “Promise” is an object or function with a then method that behaves according to this specification. That is, a Promise is an object or a function
- “Thenable” is an object or function that has then methods, that is, the object must have THEN methods
- “Value” is any valid JS value (including undefined or promise)
- Exceptions in promises need to be thrown using a throw statement
- When a promise fails, you need to give a reason for the failure
Then method description
- A promise must have a then method and access to the promise’s final outcome, success or failure
- The then method needs to receive two parameters, onfulfilled and onRejected are optional parameters
- Promise The function bound to the THEN executes regardless of whether the then method completes or not, as long as the promise state changes.
Chain calls
The big advantage of promises is that they can be chain-called, and if a THEN method returns a normal value, that value is passed to the next THEN as a result of success.
If a promise is returned, the result of the promise’s execution is passed on, depending on whether the promise succeeds or fails.
If an error is returned, the next then failure function is executed.
Write Promise code by hand
The handwritten Promise code often tested in interviews can be carefully understood.
/ / written Promise
// First define a constructor that passes a function executor when creating a Promise object,
// This function will be called immediately, so we execute this function immediately inside the Promise.
function Promise(executor){
// Used to save the state of the promise
this.status = "pending";
this.value;/ / initial value
this.reason;// The initial cause
this.onResolvedCallbacks = [];// Store all successful callback functions
this.onRejectedCallbacks = [];// Store all failed callback functions
// Define resolve
const resolve = (value) = >{
if(this.status === "pending") {this.status = "resolved";
this.value = value;
this.onResolvedCallbacks.forEach(function(fn){
fn()
})
}
}
// Define reject
const reject = (reason) = >{
if(this.status === "pending") {this.status = "rejected";
this.reason = reason;
this.onRejectedCallbacks.forEach(function(fn){
fn()
})
}
}
executor(resolve,reject);
}
Promise.prototype.then = function(onFulfilled,onRejected){
/* Each then method returns a new promise. We need to get the result of the success or failure of the current THEN method. The return value of the previous THEN method is passed to the next then method. This will be a big pity (self.value) and onRejected(self.reason). This will be a big pity. If a reject fails, you need to execute reject, using try... Catch catches an error. That is, determine the relationship between the result of the then function's execution and the promise returned. * /
return new Promise((resolve,reject) = >{
// When the Promise state is Resolved
if(this.status === "resolved") {try{
resolve(onFulfilled(this.value))
}catch(error){
reject(error)
}
}
// When the Promise state is Rejected
if(this.status === "rejected") {try {
resolve(onRejected(this.reason))
} catch (error) {
reject(error)
}
}
// When the Promise state is Pendding
if(this.status === "pending") {this.onResolvedCallbacks.push(function(){
try{
resolve(onFulfilled(this.value))
}catch(error){
reject(error)
}
});
this.onRejectedCallbacks.push(function(){
try {
resolve(onRejected(this.reason))
} catch(error) { reject(error) } }); }})}Copy the code
Upgrade to Promise:
class Promise{
/* First we define a constructor. When we create a Promise object, we pass a function executor that will be called immediately, so we execute this function immediately inside the Promise. * /
constructor(executor){
this.executor = executor(this.resolve,this.reject);
this.onResolvedCallbacks = [];// Store all successful callback functions
this.onRejectedCallbakcs = [];// Store all failed callback functions
}
// Used to store the corresponding state
status = "pending";
/ / initial value
value;
// The initial cause
reason;
// Executor passes two methods to execute, one is resolve,
// A reject, so we create these two functions, and we need to pass them to executor.
// This is a big pity. // When we succeed or fail, execute the onFulfilled and onRejected functions,
Resolve and reject loop through the array.
// Define a success event
resolve(value){
if(status === "pending"){
status = "resolved";
value = value;
this.onResolvedCallbacks.forEach(fn= >{fn()})
}
}
// Define the failure event
reject(){
if(this.status === "pending") {this.status = "rejected";
this.reason = reason;
this.onRejectedCallbakcs.forEach(fn= >{fn()}); }}// When we asynchronously execute resolve, the then bound function will be executed, and when we bind multiple THEN bound methods will be executed.
// The Promise object will have a THEN method, which will have two parameters, one is the successful callback ondepressing,
// The other callback is onFulfilled (reject). As long as we call 'resolve', onFulfilled.
To keep this messy, we define a self to store this. When we call resolve or reject, we need to let the state change.
// It is important to note that the state of a Promise can only be changed once, so we need to decide to change the state only if the state does not change.
then(onFulfilled,onRejected){
// Determine the current status of the callback
if(this.status === "resolved"){
onFulfilled(self.value)
};
if(this.status === "rejected"){
onRejected(self.reason)
}
// When the state is still pending
// This is a big pity because onFulfilled and onRejected need to pass in the corresponding value when they are implemented, so we will use a function to encapsulate the corresponding value.
if(this.status === "pending") {this.onResolvedCallbacks.push(() = >{onFulfilled(this.value)});
this.onResolvedCallbacks.push(() = >{onRejected(this.reason)}); }}}Copy the code
Use your own handwritten Promise
let p = new Promise((resolve,reject) = >{
setTimeout(() = >{
resolve("It worked")},1000)}); p.then(function(value){
return 123;
}).then(value= >{
console.log("Received news of success :",value);
}).catch(error= >{
console.log(error);
});
p.then(value= >{
console.log(value);
})
Copy the code
Fourth, Async/Await
Async is used to indicate that a function is asynchronous. The return value of the defined async function is a Promise object, and callbacks can be added using the then method.
Await can be understood as short for async wait. Await must appear inside async functions and cannot be used alone. The current function must use async modifier as long as it uses await in the function.
So the terminator of the callback function is async/await.
Async command
- The async function returns a Promise object.
- The value returned by the return statement inside the async function becomes the argument to the callback of the then method.
- An error is thrown inside the async function, which causes the returned Promise object to become reject.
- The thrown error object is received by the catch method callback.
Promise objects returned by async functions will not change state until all Promise objects following the internal await command have been executed, unless a return statement or an error is thrown.
That is, the callback function specified by the THEN method is executed only after the asynchronous operation inside the async function is completed.
async function fun(){
// return "hello wenbo";
throw new Error("ERROR");
}
fun().then(v= > console.log(v),reason= >console.log(reason));//Error: ERROR```
Copy the code
Await orders
Normally, the await command is followed by a Promise object that returns the result of that object. If it is not a Promise object, the corresponding value is returned.
async function fun(){
return await "zhaoshun";
// return "zhaoshun";
}
fun().then(value= >console.log(value));//zhaoshun
Copy the code
Alternatively, if an await command is followed by a Thenable object (that is, the object that defines the then method), await will equate it with a Promise object.
class Sleep{
constructor(timeout){
this.timeout = timeout;
}
then(resolve,reject){
const startTime = Date.now();
setTimeout(() = >resolve(Date.now() - startTime),this.timeout); }} (async() = > {const sleepTime = await new Sleep(1000);
console.log(sleepTime);/ / 1012
}
)()
// There is no hibernate syntax in js, but with await command can make program pause time
const sleepFun = (interval) = > {
return new Promise(resolve= >{
setTimeout(resolve,interval); })}/ / usage
const asyncFun = async() = > {for(let i = 1; i <= 5; i++){
console.log(i);
await sleepFun(1000);
}
}
asyncFun();
Copy the code
As you can see from above, the await command is followed by an instance of the Sleep object. This instance is not a Promise object, but because the then method is defined, await will treat it as a Promise.
If the Promise object following the await command becomes reject, the reject argument is received by the catch callback.
Note: In this code, the await statement is preceded by no return, but the reject argument is still passed to the catch callback. Here the effect is the same if return is preceded by await.
- Any Promise object after an await statement becomes reject, and the entire async function breaks.
- Use a try/catch to handle the case where an await is interrupted and nothing is executed.
Example:
const fun = async() = > {try {
await Promise.reject("ERROR");
} catch (error) {
}
return await Promise.resolve("success");
}
fun().then(
value= >console.log(value),reason= >console.log(reason,"error")//
).catch(
error= >console.log(error)//ERROR
);
Copy the code
The alternative is to await a Promise object followed by a catch method to handle any errors that may occur earlier.
const fun = async() = > {await Promise.reject("error").catch(e= >console.log(e));
return await Promise.resolve("success");
}
fun().then(v= >console.log(v));//success
Copy the code
Error handling
First point: If an asynchronous operation following await fails, the promise object equivalent to the async function is rejected.
const fun = async() = > {await new Promise((resolve,reject) = >{
throw new Error("error")
})
}
fun().then(v= >console.log(v)).catch(e= >console.log(e));
Copy the code
Second point: asynchronous operations after multiple await commands. If there is no secondary relationship, it is better to let them fire simultaneously.
const [fun1,fun2] = await Promise.all([getFun(),getFoo()]);
const fooPromise = getFoo();
const funPromise = getFun();
const fun1 = await fooPromise();
const fun2 = await funPromise();
Copy the code
Third point: await command can only be used in async functions. If used in normal functions, an error will be reported.
async function dbFuc(db) {
let docs = [{}, {}, {}];
/ / an error
docs.forEach(function (doc) {
await db.post(doc);
});
}
Copy the code
Fourth point: Async functions can preserve the run stack.
summary
In this article we summarized the asynchronous programming and callback function solution Promise, and the callback terminator Aysnc /await.
- Promise has three states:
pending
,rejected
,resolved
. - A Promise’s state can only change once, in two ways:
pending
intoresolved/fullfilled
,pending
intorejected
. - The big advantage of promises is that they can make chain calls.
async
To indicate that the function is asynchronous, the async function defined returns a promise object.- Function, just use
await
, the current function must use async modifier. - The terminator of the callback function is
async/await
.
Refer to the article
Async terminator async await
“Once you know async/await, solve callback hell”
Interview Promise
Classic BAT Front-end Interview Questions: The Most Detailed Handwritten Promise Tutorial ever
Promise Implementation Principle (source code attached)
JavaScript Advanced Programming (4th Edition)
JavaScript You Don’t Know (Middle Volume)
Introduction to ES6 by Ruan Yifeng
Write in the last
Thank you for reading, I will continue to share with you more excellent articles, this article reference a large number of books and articles, if there are mistakes and mistakes, hope to correct.