An overview,

Using single thread working mode, js execution environment is responsible for the execution of code only one thread. Advantages: safe and simple Disadvantages: when a task is time-consuming, the following tasks need to wait for the completion of the above tasks to be executed, which will cause suspended animation.

Two modes of execution: synchronous mode and asynchronous mode

Two, synchronous mode and asynchronous mode

The runtime provides apis that work in synchronous or asynchronous mode!!

1. Synchronization mode

Tasks in the code are executed in turn, and the later task must wait for the previous task to finish before it can begin to execute. The program is executed in the same order as the code is written. (Not simultaneously, but in a queue)

Log ('global begin') // (1) function foo() {console.log(' Task')} function foo() {console.log(' Task') Bar ()} foo() (2) console.log('global end') // (3) //global begin //foo Task //bar Task //global endCopy the code

To start execution, the JS engine pushes an anonymous call into the call stack, which can be understood as putting all the code in an anonymous function and then starting to execute line by line. Step and contents in the call stack Start (Anonymous) Step (1) (Anonymous) console.log(‘global begin’) Step (1) Finish (Anonymous) Step (2-1) (Anonymous) foo() Step (2-2) (anonymous) foo() console.log(‘foo Task’) Step (2-3) (anonymous) foo() bar() Step (2-4) (Anonymous) foo() bar() Console. log(‘bar Task’) Step (2-5) (anonymous) foo() bar() Step (2-6) (anonymous) foo() Step (2) Finish (Anonymous) Console. log(‘global end’) step (3) (anonymous) finally clears the shortcoming: waiting for the previous task to finish before starting the next task

2. Asynchronous mode

Instead of waiting for the previous task to finish before starting the next task, the next task is executed immediately after the start of the task, and the subsequent logic is defined through the callback function. Problem: Code execution sequence is out of order and does not follow the code order.

console.log('global begin')//(1) setTimeout(function timer1() { //(2) console.log('timer1 invoke') }, 1800) setTimeout(function timer2() { //(3) console.log('timer2 invoke') setTimeout(function inner() { console.log('inner Invoke ')},1000)},1000) console.log('global end')// (4)Copy the code

To start execution, the JS engine pushes an anonymous call onto the call stack.

steps <Call stack>content
Prior to the start (anonymous)
Step 1 (anonymous) console.log(‘global begin’)
Step (1) Is complete (anonymous)
Step 2 (anonymous) setTimeout(timer1)At the same time, an internal countdown timer is started for the timer1 function, which is then set aside (Web APIs) for 1.8s
Step (2) Is complete (anonymous)
Step 3 (anonymous) setTimeout(timer2)At the same time, an internal countdown timer is started for the timer2 function, which is then set aside (Web APIs) for one second
Step (3) Is complete (anonymous)
Step 4 (anonymous) console.log(‘global end’)
Step (4) Is complete (anonymous)
The last — (Empty)

Step execution, Web APIs content

steps Web APIs
Step 2 Timer1 ()【 countdown 1.8s】
Step 3 Timer1 ()【 countdown 1.8s】

Timer2 in the Web APIs will Queue it when the countdown ends, and so will Timer1

Point in time Queue
Timer2 () The countdown is over timer2()
Timer1 () The countdown is over timer2() timer1()

Our event loop listens for any changes in the Queue and pushes the first one in the Queue (timer2()) onto the call stack

Point in time <Call stack>content
Timer2 () The countdown is over timer2()
Point in time Content of the Queue
Timer2 () The countdown is over timer()

At this point, we start a new round of execution, encounter setTimeout(inner), and repeat the above execution until there are no more tasks in the task stack and message queue, and we consider the task to be finished.

If we think of a task stack as a list of tasks to execute, then a queue is a list of tasks to do. Js is to first process tasks in the task stack, and then get a task from the queue to complete through event loop. During this time, we can add new tasks to the queue at any time, and these tasks will be queued in the message queue waiting for completion. Js is single threaded, browsers are not!

Three, several ways of asynchronous programming

1. The foundation of asynchronous programmingThe callback function

A function defined by the caller and given to the executor, who is told what to do when the asynchronous task is over

Function foo(callback) {setTimeout(function () {callback()},300)} foo(function () {// 'takes the function as an argument Console. log(' I am a callback function ')})Copy the code

2.Promise asynchronous solution, macro task/microtask queue

(1) overview

An object that indicates whether an asynchronous task succeeds or fails after completion.

(2) Basic usage

const promise = new Promise(function (resolve, //(1) reject(new Error('promise rejected'))//(2)}) promise.then(function (value) { }, function (error) {console.log(' tryout ', value) // tryout 100}, function (error) {console.log(' try ', value) }) console.log('end') //end rejected error: promise (2)}) console.log('end') //end rejected error: promise (2)Copy the code

Output “end” first to prove asynchronous execution!

(3) Use cases

function ajax(url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest() xhr.open("GET", url) xhr.responseType = 'json' xhr.onload = function () { if (this.status == 200) { resolve(this.response) } else { Reject (new Error(this.statustext))}} xhr.send()})} *** Correct address *** ajax(' your request address ').then(function (res) {console.log(res) //{code: 200, buniss_code: 0,... }}, function (error) {console.log(error)}) *** error address *** ajax(' your wrong address ').then(function (res) {console.log(res)}, function (error) {console.log(res)}, function (error) { console.log(error) //Error: Not found })Copy the code

Promises are also essentially callbacks that define tasks that need to be executed after asynchronous tasks have finished. It’s just passed through the THEN method, which requires two functions, a success callback and a failure callback.

(4) Common misunderstandings

Then (function (res) {console.log(res) ajax(' your request address ').then(function (res) {console.log(res)... }, function (error) { console.log(error) }) }, function (error) { console.log(error) })Copy the code

If there are multiple callbacks that are prone to callback hell, try to keep the task flat, so use chained calls!

(5) Chained call

ajax('xxx').then(function (res) { console.log(res) //{code: 200, buniss_code: 0,... }}, function (error) { console.log(error) }).then(function (val) { console.log(1) //1 // return Ajax (' XXX ')// equivalent to ajax(' XXX ') above return, }).then(function (val) {console.log(2)//2 return 'ABC'}).then(function (val) {console.log(2)//2 return 'ABC'}).then(function (val) { console.log(3)//3 console.log(val)//'abc })Copy the code

Then returns an entirely new Promise object. Each THEN method is actually adding a state-specific callback to the last promise object returned, and these callbacks are executed in turn

  • The THEN method on the Promise object returns an entirely new Promise object
  • The subsequent THEN method registers the callback for the promise returned by the previous THEN
  • The return value of the callback function in the previous THEN method is taken as an argument to the later THEN method callback
  • If a promise is returned in the callback, then the callback to the then method waits for it to end

(6) Exception handling

The onRejected callback is executed if the Promise fails or is an exception. A more common way to register the onRejected callback is to use the catch method on the Promise instance to register it!

ajax('xxx').then(function (res) { console.log(res) //{code: 200, buniss_code: 0,... }}). Catch (function (error) {//catch = undefined console.log(error)}).Copy the code

Then the second argument catches an exception and the second argument catches an exception.

Then (function (res) {console.log(res) //{code: 200, buniss_code: 0,... } return ajax('error-url') }, Then (function (res) {console.log(res)}) ***catch (' XXX '). Then (function (res) {console.log(res)) //{code: 200, buniss_code: 0,... } return ajax('error-url')}).catch(function (error) {// Can catch the exception console.log(error)})Copy the code

Then the second argument fails to catch the exception inside the correct callback function. Catch can. Catch can catch the entire promise registry chain. In addition, we can register an unhandledrejection event on the global pair to handle exceptions that are not manually caught in our code.

Window.addeventlistener ('unhandledrejection', event => {const {reason, promise} = event console.log(reason, Promise) //reason => Promise failure reason, Process.on ('unhandledrejection', 'unhandleDrejection ',' unhandleDrejection ') Event => {console.log(reason, promise) // Reason => Promise failure cause, usually an error object // Promise => exception promise object})Copy the code

Catching exceptions globally is not recommended, we should explicitly catch every exception in our code!

(7) Static methods

Promise.resolve() // can quickly convert a value to a Promise object

Promise.resolve('foo').then(function (value) {console.log(value) //foo}) reject) { resolve('foo') }).then(val => console.log(val))//fooCopy the code
var a = new Promise(function (resolve, reject) {
      resolve('foo')
})
var b = Promise.resolve(a)
console.log(a === b)//true
Copy the code
Promise.resolve({ then: Function (onFullfilled, onRejected) {//then OnFullfilled ('foo')}}).then(function (value) {console.log(value)//foo can also get the value passed in})Copy the code

Promise.reject() // Any arguments passed in will be the cause of the Promise’s failure

Promise.reject('anything').catch(function (error) { 
  console.log(error)
})
Copy the code

(8) Parallel execution — Multiple tasks need to be executed in parallel, and multiple tasks are executed at the same time

  • Promise.all([]) // Returns a new Promise object until all tasks have finished
Ajax (' XXX ').then(value => {const urls = object.values (value)// object.values (value) ¶ Const tasks = urls.map(URL => Ajax (URL))// Turn the string array into an array of Promise objects containing all requested tasks return All (tasks)// Promise object array to form a new Promise return}).then(values => {console.log(values)})Copy the code
  • Promise.race()// will only wait for the first finished task,Can be used as a way to control Ajax timeout requests
  const request = ajax('xxx')
  const timeout = new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error('timeout')), 500)
  })
  Promise.race([request, timeout]).then(val => console.log(val)).catch(err => console.log(err))
Copy the code

(9) Execution sequence (Macro tasks and microtasks)

  console.log('global start')
  setTimeout(() => {
      console.log('setTimout')
  }, 0)
  Promise.resolve().then(() => console.log(1)).then(() => console.log(2)).then(() => console.log(3))
  console.log('global end')
  //global start
  //global end
  //1
  //2
  //3
  //setTimout
Copy the code

Callback queue called macros tasks, task of macros can temporarily during the process of task execution process plus some extra demand, these temporary plus some extra demand can be selected as a new macro task into the queue queue (such as: setTimeout), can also be used as a current task of task, directly in the current executed immediately after the end of this task (such as: Promise)

Microtasks: Improve overall responsiveness

Currently, most asynchronous calls are executed as macro tasks, while Promise, MutationObserver, and Process.nexttick (in Node) are executed as microtasks at the end of this round

3.Generator asynchronous scheme, Async/Await syntax sugar

Traditional asynchronous code writing

try {
    const val1 = ajax('xx1')
    console.log(val1)
    const val2 = ajax('xx2')
    console.log(val2)
    const val3 = ajax('xx3')
    console.log(val3)
    const val1 = ajax('xx4')
    console.log(val4)
} catch (e) { 
    console.log(e)
}
Copy the code

Two better asynchronous programming Generator, Async function

(1) Generator

<1> Asynchronous Scheme – Up
Function * foo() {console.log('start') yield 'foo' // We can use yield to return a value at any time within the function body. Yield does not immediately end execution like return does. Yield simply suspends execution of our generator until the next time we call the next method on our generator object. Const r = yield 'foo' console.log(r)//bar // Yield can receive the value passed in, bar as the return value of this statement, } catch(e){console.log(e)//Error: Generator error}} const generator = foo()// Calls a generator function that does not execute immediately, Instead, we get a generator object const res = generator.next()// until we call the body of the function manually, Console. log(res)//{value:'foo',done:false}// Get the return value done from the object returned by the next method Next ('bar')// In addition, if we pass an argument when we call our generator object method, that parameter will be returned by the yield statement. Throw (new Error('generator Error ')))// We manually call the throw method outside of the generator, This method can throw an exception inside the generator function. Inside the generator function it will get the exception during execution, and it will catch it with a try catchCopy the code
<2> Asynchronous Scheme – medium
function ajax(url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest() xhr.open("GET", url) xhr.responseType = 'json' xhr.onload = function () { if (this.status == 200) { resolve(this.response) } else { reject(new Error(this.statusText)) } } xhr.send() }) } function* main() { const res1 = yield ajax('xxx') Console. log(res1) //{code: 200, buniss_code: 0, message: "SUCCESS", data: {... //{code: 200, buniss_code: 0, message: "SUCCESS", data: {... } const g = main() //main {<suspended>} const r = g.next() //{value: Promise, done: Then (data => {// yield the Promise object callback by then, and get the execution result in the Promise object callback, By calling next again and passing in the resulting data, the main function will continue executing, and the passed data will be returned by the current yield, so that we can get the res const A1 = g.next(data)// inside the promise function like this, If (a1.done) return a1.value.then(data => {// so on, If we use the yield method in main to return a promise multiple times, and each time it returns a promise, we can keep calling next in the result then until next returns done true. Const a2 = g.ext (data) console.log(a2) if (a2.done) return //... })})Copy the code
<3> Asynchronous scheme – down

Change r.value. Then above to the recursive form

function ajax(url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest() xhr.open("GET", url) xhr.responseType = 'json' xhr.onload = function () { if (this.status == 200) { resolve(this.response) } else { reject(new Error(this.statusText)) } } xhr.send() }) } function* main() { try {//try catch const res1 = yield Ajax (' XXX ') console.log(res1) const res2 = yield ajax(' XXX ') console.log(res2)} catch (e) {console.log(e)} Const g = main() function handleResult(result) {if (result.done) return (data => { handleResult(g.next(data)) }, error => { g.throw(error) })) } handleResult(g.next())Copy the code
function co(generator) { const g = generator() //main {<suspended>} function handleResult(result) { if (result.done) Return result.value.then(data => {handleResult(g.ext (data))}, error => { g.throw(error) }) } handleResult(g.next()) } co(main)Copy the code

For the generator function executor above CO, there is a more complete library in the community, called CO, which is less popular after async/await

(2) Async function — Async/Await syntax sugar

function ajax(url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest() xhr.open("GET", url) xhr.responseType = 'json' xhr.onload = function () { if (this.status == 200) { resolve(this.response) } else { reject(new Error(this.statusText)) } } xhr.send() }) } async function main() { try { const res1 = await ajax('xxx') console.log(res1) //{code: 200,... } const res2 = await ajax(' XXX ') console.log(res2) {//{code: 200,... } catch (e) {console.log(e)}} const Promise = main() promise.then(() => {console.log('completed')//completed })Copy the code
  • Language level standard asynchronous syntax, no need to construct a generator such as CO
  • Async returns a Promise object, which gives us more control over the overall code
  • Await keyword can only appear inside async functions and cannot be used outside (top-level scope)

Async is the syntax sugar for generator and await is the syntax sugar for promise