33 concepts JavaScript developers should understand 12-Promise, async and wait
Directory
- call stack
- primitive type
- Value types and reference types
- Implicit, explicit, nominal and duck typing
- == and ===, typeof and instanceof
- this, call, apply and bind
- Function scope, block scope and lexical scope
- Closure
- Higher order functions such as map, reduce, filter etc.
- expressions and statements
- variable promotion
- Promise async and wait
- Immediate function execution, modularity, namespace
- recursion
- algorithm
- data structure
- Message queue and event loop
- setTimeout, setInterval and requestAnimationFrame
- Inheritance, polymorphism and code reuse
- Bitwise operators, array-like objects and typed arrays
- DOM tree and rendering process
- new and constructor, instanceof and instance
- Prototype inheritance and prototype chain
- Object.create and Object.assign
- Factory functions and classes
- Design Patterns
- Memoization
- Pure functions, function side effects and state changes
- Performance-consuming operations and time complexity
- JavaScript engine
- Binary, decimal, hexadecimal, scientific notation
- Partial functions, currying, Compose and Pipe
- Code cleanliness
Introduction
Record a process of learning JavaScript. The article is not written in order. The directory link will be updated after it is written. The directory of this article was created with reference to @leonardomso. The English version project address is here
Foreword
This article is divided into four parts
- Introduction and use of promises
- handwritten promise
- Using promises in interview questions and projects
- async and wait
1.Introduction and use of promise
1.1 Introduction
A Promise
object represents the final completion (or failure) of an asynchronous operation and its result value. A Promise
is a proxy , which represents a value that is not necessarily known when the promise is created. It allows you to associate a handler with the final success value or failure reason of an asynchronous operation. This allows an asynchronous method to return a value just like a synchronous method: instead of returning the final value immediately, an asynchronous method returns a promise to provide that value at some point in the future.
A Promise
must be in one of the following states:
- Pending: Initial status, neither honored nor rejected.
- Fulfilled: means the operation was completed successfully.
- Rejected: means the operation failed.
Once a Promise is resolved or rejected, it cannot be migrated to any other state (i.e. state immutable)
look at an example
let p1 = new Promise((resolve, reject) => {<!-- --> resolve('success') reject('failed') }) console.log('p1', p1) let p2 = new Promise((resolve, reject) => {<!-- --> reject('failed') resolve('success') }) console.log('p2', p2)
Promise is only subject to the first time
. If it succeeds for the first time, it will be permanently
as fulfilled
. If it fails for the first time, the status will always be . rejected
Can be moved to any other state
1.2.Problems solved by Promise
Before Promise appeared, we handled multiple asynchronous network requests, something like this
Request 1(function(request result 1){<!-- --> Request 2(function(request result 2){<!-- --> Request 3(function(request result 3){<!-- --> Request 4(function(request result 4){<!-- --> Request 5(function(request result 5){<!-- --> Request 6(function(request result 3){<!-- --> ... }) }) }) }) }) })
With promise
new Promise(request 1) .then(request 2(request result 1)) .then(request 3(request result 2)) .then(request 4(request result 3)) .then(request 5(request result 4)) .catch(Exception handling (exception information))
Summarized as follows
-
A promise may have three states: pending, fulfilled, and rejected.
-
The state of a promise can only be changed from “waiting” to “complete” or “rejected”, and cannot be converted in the reverse direction. At the same time, the “completed” state and “rejected” state cannot be converted to each other.
-
Promise must implement the
then
method (it can be said that then is the core of promise), and then must return a promise. Then of the same promise can be called multiple times, and the execution order of callbacks is the same as when they were defined. in the same order -
The then method accepts two parameters. The first parameter is a callback on success, which is called when the promise transitions from the “waiting” state to the “completion” state. The other is a callback on failure, which is called when the promise transitions from the “waiting” state to the “completion” state. Called when in “reject” state. At the same time, then can accept another promise passed in, and also accept an object or method “like then”, that is, a thenable object.
-
Promise is a solution for asynchronous programming that can solve the problem of asynchronous callback hell and make asynchronous programming clearer and more concise. In traditional asynchronous programming, if there are multiple asynchronous tasks, each asynchronous task needs to be completed after the previous asynchronous task is completed. This will form nested callback functions, making the code difficult to maintain and read. Promise can execute multiple asynchronous tasks serially or in parallel, and can uniformly handle the success or failure status of asynchronous tasks.
When using Promise, multiple asynchronous tasks can be connected in series through chain calls, avoiding the problem of nested callback functions. At the same time, Promise also provides a unified catch method, which can capture errors in all Promise chains and handle exceptions more conveniently.
Therefore, Promise solves the readability and maintainability problems of asynchronous programming and improves the quality of code and the efficiency of development.
1.4 Use of Promise
Basic usage
let p = new Promise((resolve, reject) => {<!-- --> //Do some asynchronous operations setTimeout(() => {<!-- --> console.log('Execution completed'); resolve('I am successful!!'); }, 2000); });
Usage of then
const p1 = new Promise((resolve, reject) => {<!-- --> resolve('success 1') }).then(res => {<!-- --> console.log(res) }).catch(error => {<!-- --> console.log(error); }) //Input successful 1 //It can also be abbreviated as follows const p1 = new Promise((resolve, reject) => {<!-- --> resolve('success 1') }).then(res => console.log(res), err => console.log(err))
then there is a timer situation
const p1 = new Promise((resolve, reject) => {<!-- --> setTimeout(() => {<!-- --> resolve('success 1') }, 2000) }).then(res => {<!-- --> console.log(res) }).catch(error => {<!-- --> console.log(error); }) //Output successful 1 after 2 seconds
catch
const p1 = new Promise((resolve, reject) => {<!-- --> reject('Failed 1') }).then(res => {<!-- --> console.log(res) }).catch(error => {<!-- --> console.log(error); }) //Output failed 1
As can be seen from the above example
The created promise will eventually end with resolved status (fulfilled)
or rejected status (rejected)
, And call the corresponding callback function (passed to then and catch) when completed.
then.Chain call The next then execution is affected by the return value of the previous then
The then method itself will return a new Promise object
const p1 = new Promise((resolve, reject) => {<!-- --> resolve(100) }).then(res => {<!-- --> return res + 1 }).then(res=>{<!-- --> console.log(res); }) //Output 101
then is a micro task
const p1 = new Promise((resolve, reject) => {<!-- --> resolve('1') }).then(res => {<!-- --> console.log(res) }).catch(error => {<!-- --> console.log(error); }) console.log(2) //Output sequence 2, 1
promise method
Method name | Description |
---|---|
Promise.all([Promise1, Promise2]) | All successes are successful, If one fails, the failed one will be returned Non-Promise items will be returned successfully |
Promise.any([ Promise1, Promise2]) | Contrary to all, if both fail, an error will be reported If one succeeds, one will return the successful one If it is not a Promise item, then this item will be regarded as successful |
Promise.allSettled([Promise1, Promise2]) | Collect the results of each Promise into an array and return |
Promise .race([Promise1, Promise2]) | Which Promise gets the result the fastest, that result will be returned, regardless of success or failure |
2.Handwritten promise
1.1 Structure
The native promise is written like this
const p1 = new Promise((resolve, reject)=>{<!-- -->})
It can be seen that the native promise can be used by new to execute the resolve and reject methods by itself.
So we write like this: define a Mypromise and use the constructor to execute the resolve and reject methods
class Mypromise{<!-- --> constructor(func) {<!-- --> func(this.resolve,this.reject()) } resolve(){<!-- -->} reject(){<!-- -->} } // Constructor is a special method in JavaScript that is automatically called when creating a new object instance. The constructor method is executed when a class is instantiated and is used to initialize the property values of the instance.
There are three states of promise: pending (pending), fulfilled (success), rejected (failed). The default state is pending, and then when executed, the state becomes fulfilled or rejected, fulfilled or rejected. Either fulfilled or rejected of the promise can pass in a parameter.
we can write like this
class Mypromise {<!-- --> static PENDING = 'Pending'; static FULFILLED = "Success"; static REJECTED = "Rejected" constructor(func) {<!-- --> this.status = Mypromise.PENDING; //The default status is pending this.result = null; func(this.resolve, this.reject()) } resolve(result) {<!-- --> if (this.status === Mypromise.PENDING) {<!-- --> this.status = Mypromise.FULFILLED this.result = result } } reject(result) {<!-- --> if (this.status === Mypromise.PENDING) {<!-- --> this.status = Mypromise.REJECTED this.result = result } } }
Knocking on the blackboard, all properties and methods modified by static are static methods and properties. They can only be called by class names and cannot be called by instantiated objects. At the same time, they cannot be inherited by subclasses. In other words, they belong to the current one. Class.
then method
class Mypromise {<!-- --> static PENDING = 'Pending'; static FULFILLED = "Success"; static REJECTED = "Rejected" constructor(func) {<!-- --> this.status = Mypromise.PENDING; //The default status is pending this.result = null; func(this.resolve.bind(this), this.reject.bind(this)) //bind Bind this to the resolve method of the instance as the current instance object, and execute the resolve method } resolve(result) {<!-- --> if (this.status === Mypromise.PENDING) {<!-- --> this.status = Mypromise.FULFILLED this.result = result } } reject(result) {<!-- --> if (this.status === Mypromise.PENDING) {<!-- --> this.status = Mypromise.REJECTED this.result = result } } then(onFULFILLED,onREJECTED){<!-- --> if (this.status === Mypromise.FULFILLED) {<!-- --> onFULFILLED(this.result) } if (this.status === Mypromise.REJECTED) {<!-- --> onREJECTED(this.result) } } } let p1 = new Mypromise((resolve,reject)=>{<!-- --> resolve("success") }) p1.then( result => {<!-- --> console.log(result) }, result => {<!-- --> console.log(result.message) } ) //output successful
Use try catch to throw an exception and determine whether the function is empty
class Mypromise {<!-- --> static PENDING = 'Pending'; static FULFILLED = "Success"; static REJECTED = "Rejected" constructor(func) {<!-- --> this.status = Mypromise.PENDING; //The default status is pending this.result = null; try{<!-- --> func(this.resolve.bind(this), this.reject.bind(this)) }catch (error){<!-- --> this.reject(error) } //bind Bind this to the resolve method of the instance as the current instance object, and execute the resolve method } resolve(result) {<!-- --> if (this.status === Mypromise.PENDING) {<!-- --> this.status = Mypromise.FULFILLED this.result = result } } reject(result) {<!-- --> if (this.status === Mypromise.PENDING) {<!-- --> this.status = Mypromise.REJECTED this.result = result } } then(onFULFILLED,onREJECTED){<!-- --> onFULFILLED = typeof onFULFILLED === "function" ? onFULFILLED :() =>{<!-- -->}; onREJECTED = typeof onREJECTED === "function" ? onREJECTED :() =>{<!-- -->}; if (this.status === Mypromise.FULFILLED) {<!-- --> onFULFILLED(this.result) } if (this.status === Mypromise.REJECTED) {<!-- --> onREJECTED(this.result) } } } let p1 = new Mypromise((resolve,reject)=>{<!-- --> resolve("success") }) p1.then( result => {<!-- --> console.log(result) }, result => {<!-- --> console.log(result.message) } )
3.Interview questions
Question 1
const promise = new Promise((resolve, reject) => {<!-- --> console.log(1); resolve(); console.log(2); reject('error'); }) promise.then(() => {<!-- --> console.log(3); }).catch(e => console.log(e)) console.log(4);
Rule 1, the code in the promise
constructor will be executed immediately, and the code in then
or reject
will be put into the asynchronous microtask queue, and in the macrotask It will be executed immediately after completion. Rule 2: Once the status of promise
changes to success or failure, it will not change again, so the execution result is: 1,2,4,3 u>. The functions in catch
will not be executed again.
Question 2
const promise = new Promise((resolve, reject) => {<!-- --> setTimeout(() => {<!-- --> console.log('once') resolve('success') }, 1000) }) promise.then((res) => {<!-- --> console.log(res) }) promise.then((res) => {<!-- --> console.log(res) })
The constructor of promise
will only be executed once, while the then
method can be called multiple times, but the second time it returns the result directly, there will be no asynchronous waiting time, so execution The result is: Print after one second: once,success,success
.
Question three
const p1 = () => (new Promise((resolve, reject) => {<!-- --> console.log(1); let p2 = new Promise((resolve, reject) => {<!-- --> console.log(2); const timeOut1 = setTimeout(() => {<!-- --> console.log(3); resolve(4); }, 0) resolve(5); }); resolve(6); p2.then((arg) => {<!-- --> console.log(arg); }); })); const timeOut2 = setTimeout(() => {<!-- --> console.log(8); const p3 = new Promise(reject => {<!-- --> reject(9); }).then(res => {<!-- --> console.log(res) }) }, 0) p1().then((arg) => {<!-- --> console.log(arg); }); console.log(10);
From the perspective of code execution sequence, the program initially executes the code in the order of the code. When it encounters a synchronous task, it is executed immediately; when it encounters an asynchronous task, it just calls an asynchronous function to initiate an asynchronous request. At this point, the asynchronous task starts to perform the asynchronous operation, and after the execution is completed, it is queued in the message queue. After the program is executed according to the code sequence, check whether there are any waiting messages in the message queue. If so, put the messages from the message queue into the execution stack in order. After the execution is completed, the message is obtained from the message queue, executed again, and repeated.
Event loop: There is an event loop EventLoot*p rule in the execution rules of javascript
. In the event loop, asynchronous events will be placed in the asynchronous queue, but the asynchronous queue is divided into macro tasks And microtasks, browser-side macrotasks generally include: script tag, setTimeout, setInterval, setImmediate, requestAnimationFrame
. Microtasks include: MutationObserver,Promise.then catch finally
. Macro tasks will block the browser’s rendering process, and micro tasks will be executed immediately after the macro task ends, before rendering.
The result of the above question is **: 1,2,10,5,6,8,9,3’**
- Step 1: The
Promise
constructor will be executed immediately and the code will be executed in order.Output 1, 2, 10
. At this time, there are asynchronous tasks in the event loop and placed in the asynchronous queue. - Step 2 The asynchronous queue is divided into macro tasks and micro tasks. Then execute the micro tasks first and then the macro tasks.
Micro tasks include: p2.then, p1().then
Macro tasks include: timeOut1 timeOut2. Select Execute microtaskOutput 5,6
- Step 3: After the microtask is executed, the macrotask is executed: timeOut1 timeOut2. The actual setTimeout stack sequence is timeOut2>timeOut1. Because setTimeout2 is added to the macro asynchronous task after the promise is executed, it is queued at the back. Execute timeOut2
and output 8
, during the execution of the macro task, the p3.then microtask enters the queue. After the macrotask is executed, the microtask will be executed.Output: 9
Then timeOut1 is executed,Output: 3
- The constructor of
promise
will only be executed once and 4 will not be printed.
4.async and wait
async/await
Execute asynchronous tasks in a synchronous manner- async declares that the function is asynchronous and the function will return a promise.
- await must be used in async functions
1.How does the async function handle its return value? Look at the example below
async function test() {<!-- --> return 'hello async'; } let result = test(); console.log(result);
Printed results
The async function returns a Promise object
What if the async function does not return a value?
It returns Promise.resolve(undefined).
2.await What are you waiting for? Look at the example below
function getSomething(){<!-- --> return "something"; } async function testAsync(){<!-- --> return Promise.resolve('hello async'); } async function test(){<!-- --> let v1 = await getSomething(); let v2 = await testAsync(); console.log(v1,v2); } test(); console.log('I executed'); //The execution result is: //I executed //something,hello async
in conclusion
1. If it is not waiting for a Promise object, the result of the await expression is what it is waiting for.
2. If it is waiting for a Promise object, await will be busy, waiting for the Promise object to resolve, and then get the value of resolve as the result of the await expression.
3.await + function
function fn() {<!-- --> console.log('fn start') console.log('fn end') } async function run() {<!-- --> console.log('start 1') const res = await fn() console.log("111",res) console.log('end') } run() console.log('3')
Results of the
Conclusion: If the right side of await is a function
, it will execute this function immediately, and only after the execution of this function ends (that is, the function is completed)! Only then will the remaining async tasks be pushed into the microtask queue
When await
is encountered, the code behind it (not the entire code) inside the function will be blocked to execute the synchronization code outside the function; when the external synchronization code is completed, return This function executes the remaining code. And when await
is executed, the code of the microtask queue will be processed first
-
await plus async function
look at an example
function funOne() { return ("Execute the first function"); } function funTwo() { $.ajax({ url:'./data.json', success: (res) => { return("Execute the second callback function"); } }) } function funThree() { return ("Execute the third function"); } function run() { console.log(funOne()); console.log(funTwo()); console.log(funThree()); } run()
Print results
- Execute the first function
- Execute the third function
- Execute the second callback function
We add an asynchronous method
function funOne() { return ("Execute the first function"); } function funTwo() { return new Promise((resolve,reject) => { resolve("Execute the second callback function"); }).then(res => { console.log(res) }) } function funThree() { return ("Execute the third function"); } function run() { console.log(funOne()); console.log(funTwo()); console.log(funThree()); } run()
Print results
- Execute the first function
- Execute the third callback function
- Execute the second function
Use await and async to transform it
function funOne() {<!-- --> return ("Execute the first function"); } function funTwo() {<!-- --> return new Promise((resolve,reject) => {<!-- --> resolve("Execute the second callback function"); }).then(res => {<!-- --> console.log("1111",res) }) } function funThree() {<!-- --> return ("Execute the third function"); } async function run() {<!-- --> console.log(funOne()); console.log(await funTwo()); console.log(funThree()); } run()
Print results
- Execute the first function
- Execute the third function
- Execute the second callback function