preface

When it comes to asynchronous Javascript programming, one of the most important aspects of front-end practice is that it evolves very quickly from the initial callback function to the later callback function. Why do I say that? Because despite all the syntactic evolution, evolution, and ultimately a return to callback, the most popular asynchronous programming solutions can be divided into four basic categories

  • Callback Asynchronous solution (callback function)
  • Promise asynchronous solution
  • Async await syntax sugar
  • Generator Asynchronous solution (traversal object Generator)

Before we share asynchronous programming, let’s first understand what is synchronous and what is asynchronous

Synchronous mode

The plain language description may not be easy to understand, but let me show you a little code

console.log(1)
function hs () {
    console.log(3)}function hs2 () {
    console.log(2)
    hs()
}
hs2()
console.log(4)

1,2,3,4
Copy the code

Since Js is a single thread, during execution, synchronous tasks and asynchronous tasks enter different threads. Synchronous tasks enter the main thread, and asynchronous tasks enter the Event Table. When the specified asynchronous task is completed, Event Table moves this function to the Event Queue. When the main thread is empty, the Event Queue reads the corresponding function and enters the main thread for execution. This process is repeated over and over again, known as an Event Loop. For those who are interested, check out my other article to learn more about Js event loops and eventloops

So in this example of synchronous mode, you just go straight into the main thread and execute from top to bottom, so the output is: 1,2,3,4

Asynchronous mode

Again, a little code

console.log(1)
setTimeout(function hs () {
    console.log(2)},2000)
setTimeout(function hs2 () {
    console.log(3)
    setTimeout(function hs3 () {
     console.log(4)},3000)},1000)
console.log(5) The result believes many children shoes also can say1.5.3.2.4
Copy the code

How does it work? This is the event loop expressed above. Let’s go through the logic (regardless of the global call stack)

  1. The Js single thread executes from top to bottom, going to console.log(1) and pushing > execute the call stackPrint 1> the stack
  2. Then, go to the first setTimeout to perform the push operation > execute the call stack (execute the hs function after registering Event 2s in the Event Table) > unstackNotice that no code is executed, just that the corresponding events are registered in the event list
  3. Then continue down to the second setTimeout to perform the push operation > execute the call stack (perform hs2 function after registering the Event in the Event Table for 1s) > unstackDo the same as step 2
  4. Finally, go to console.log(5). Similarly, push > Execute call stackPrint 5> the stack
  5. At this point, the entire sample code, there’s no code on the call stack to execute, and the console prints one, five

The call stack is gone, but we still have it in our event list, step 2,3

  1. So what Js is going to do, it’s going to move the registered events from the Event Table to the Event Queue according to the order in which they were executed, to the Event Queue, to the Event Queue, to what, here comes the heroEvent LoopEvent loop, soEvent LoopWhen will it be implemented? Look at Step 5, there are no more tasks in the call stack to execute, and the corresponding function execution will be read from the event queue, ok
  2. throughEvent loopEvent loop from event queueEvent QueueTo read the corresponding function into the execution stack
  3. The hs2 function will be executed after 1sPrint 3(Execute hs3 function after registering Event 3S in Event Table) > unstack
  4. 2 seconds after the hs function, push > execute the call stackPrint 2> the stack
  5. At this point, there is no code in the call stack to execute, again, look in the event list
  6. Alas, there is a 3s after the execution function HS3, move into the event queue, through the event loop, push > call execution stackPrint 4> the stack
  7. over

These 12 steps describe the execution rules of the Event Loop in detail, and also clearly express the execution logic of the above code. After careful reading twice, you will definitely see, haha, I am a lazy person and do not like drawing, So ~

OK, through two simple examples, understand the difference between synchronous and asynchronous, back to the topic, what are the asynchronous solutions, how are they implemented, let’s discuss one by one

The callback function

Suppose we need to request an interface, and then render the data that the interface returns to the page; Normally we would write render logic in the success callback. In order to make the logic code more concise and clear, the return function plays a very important role

General idea coding

var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
 if(this.readyState === 4) {if(this.status === "200") {
            // Get the this.responseText return value
            // do your own business logic
        }
    }
}
xhr.open("get"."http://www.xxx.com")
xhr.send()
Copy the code

The idea of a callback function

What does it mean to return to a function? In asynchro, go back and call the function you defined earlier, and I’m going to simulate this in a simple way, wrapping an Ajax request, passing in parameters to request address url and callback

function myAjax (url,callback){
    var xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function () {
     if(this.readyState === 4) {if(this.status === "200") {
                callback(this.responseText)
            }
        }
    }
    xhr.open("get",url)
    xhr.send()
}
Copy the code

How to use it?

myAjax("http://www.xxx.com".function (res) {
    // Get the return value and do the logic
    console.log(res)
})
Copy the code

Is it clear at a glance, simple business logic, transparent, so it is really good? Let’s change the scenario, suppose I need to ask for a specific address, logical city by province, search for city, search for district by city, search for street by district, search for specific address by street, ok, logical, write down the code

myAjax("http://www. Get province.com".function (res) {
    // Ignore judgment
    myAjax("http://www. Get city.com? Id = province ID".function (res) {
        // Ignore judgment
        myAjax("http://www. Get zone.com? Id = market ID".function (res) {
            // Ignore judgment
            myAjax("http://www. Get street.com? Id = district ID".function (res) {
                // Ignore judgment
                myAjax("http://www. Get street.com? Id = street ID".function (res) {
                    // OK to query the detailed address})})})})})Copy the code

It’s thoughtful, regular, but it doesn’t look very elegant. This is the myth of callback hell, relying on the results of the previous level to do the next thing, so Promise comes in

Promise

What is Promise? It is primarily designed to solve callback hell, and is currently one of the leading asynchronous solutions

Detailed understanding of Promise use and principle, fast track, handwritten Promsie

So here’s a simple example of how it works, right?

new Promise((resolve,reject) = > { 
    setTimeout(() = > { 
        resolve("hellow promise")},1000) 
})
.then(
    res= > { console.log(Successful `${res}`)})Promise {<pending>} VM696:6Successful hellow promise// Prints after 1s
Copy the code

New a Promise instance, then call the success callback resolve to change the state and trigger the success callback then method. We use Promsie to do a simple optimization for the above province, city, district, street, and detailed address

First, we’ll rewrite the myAjax method. Instead of passing in a callback, we’ll simply return a promise in the myAjax method and trigger the promise’s success callback after the request succeeds. If you don’t know how to use a Promise, you need to learn how to use a Promise or write a Promise by hand in the link article above

function myAjax (url){
    return new Promise((resolve,reject) = > {
        var xhr = new XMLHttpRequest()
        xhr.onreadystatechange = function () {
         if(this.readyState === 4) {if(this.status === "200") {
                    resolve(this.responseText)
                }
            }
        }
        xhr.open("get",url)
        xhr.send()
    })
}
Copy the code

Well, with one small change to myAjax, our actual code has changed substantially

myAjax("http://www. Get province.com")
.then(res= > {
   // res.resid (province ID)
   return myAjax("http://www. Obtain market.com? Id =resId")
})
.then(res= > {
   // res.resid
   return myAjax("http://www. Get area.com? Id =resId")
})
.then(res= > {
  // res.resid (ID)
   return myAjax("http://www. Get street.com? Id =resId")
})
.then(res= > {
  // res.resid (street ID)
   return myAjax("http://www. Obtain detailed address. Com? Id =resId")
})
.catch(err= > {
// Exception handling
})
Copy the code

Then chain calls. Is there a better way to solve this problem? The answer is of course Async await

Async await

ES2017 introduced async, so what is it and what does it solve? It’s just a syntactic sugar for a Generator function. What is a Generator function? Two things async await and Generator are raised here

In fact, async functions are not very different from ordinary functions. In use, they are basically the same. Just add an async keyword in front of the function declaration

function foo () {return "I'm a normal function."}
foo() // "I am a normal function"

async function foo () {return "I'm async."}
foo() // Promise {< big pity >: "I am async function "}
Copy the code

Through a short code, it can be known that the ordinary function execution will directly return the function result, while the async function execution will return a promise and the default state is fulfilled successfully, which will return the successful result. Therefore, in the face of the async function execution, We can externally obtain the result of a function by using.then, like this

async function foo () {return "I'm async."}
foo() // Promise {< big pity >: "I am async function "}

foo().then(c= > {console.log(c)})
// I am async
Copy the code

The foo function entity above essentially returns a promise, hence the code below

async function foo () {
    return new Promise( resolve= > {
        resolve("I'm async.")
    })
}
foo().then(c= > {console.log(c)})
// I am async
Copy the code

After async, what is the use of await? Async means that there is an asynchronous operation in a function, and await means that the following expression needs to wait for the result to be used

Use a PROMISE + setTImeout to simulate asynchronous sleep delay. Await can also be followed by an asynchronous expression, such as a network request

/ / sleep function
let sleep = time= > new Promise(f= > setTimeout(f,time))

async function foo () {
   await sleep(2000)
   console.log("I came in 2 seconds later.")
   return "I came in 2 seconds later."
}

foo()
// execute console.log(" I came in after 2s ")

// Extension: the result returned by async can be called back via the then method
foo().then(c= > {console.log(c)})
// 2 seconds later I came in
Copy the code

To sum up, write asynchronous tasks in synchronous mode!

Ok, after a brief look at the use of async await, let’s go back to optimizing a version of the province, city, district, street, and detailed address

The original myAjax wrapper remains the same, because the async function execution itself returns a Promise, and myAjax returns a Promise, which is fine

function myAjax (url){
    return new Promise((resolve,reject) = > {
        var xhr = new XMLHttpRequest()
        xhr.onreadystatechange = function () {
         if(this.readyState === 4) {if(this.status === "200") {
                    resolve(this.responseText)
                }
            }
        }
        xhr.open("get",url)
        xhr.send()
    })
}
Copy the code

So it is our business code block that has been optimized, leaving out request failures and exception handling

// Define a method that executes Async functions
async function excutorAsync () {
   let s0 = await myAjax("http://www. Get province.com")
   let s1 = await myAjax("http://www. Get market.com? Id ="+s0)
   let s2 = await myAjax("http://www. Get zone. Com? Id ="+s1)
   let s3 = await myAjax("http://www. Get street.com? Id ="+s2)
   let s4 = await myAjax("http://www. Obtain detailed address. Com? Id ="+s3)
}
Copy the code

What the hell is that? Take it easy. Let’s get this straight

  1. Let s0 = myAjax(“http://www. Get province.com”) The myAjax method returns a promise
  2. Let s0 = new Promsie(resolve => resolve)
  3. addawait: let s0 = await new Promsie(resolve => resolve(” result of successful request “))
  4. The first example of async function is described above. Mark the paragraph in red and execute the async function to return the successful result of a promise default state fulfilled
  5. So: let s0 = (await success result of asynchronous expression, save Id)
  6. Similarly, asynchronous tasks are executed from top to bottom in sequential synchronous mode

Emmm ~ over, this looks a lot more optimistic.

The Generator function

We have just said that async await is the syntactic sugar of Generator functions. Now that we have sugar, the original Generator will be gradually forgotten by the public. Therefore, it is missing in the current mainstream asynchronous solutions of Js, and is gradually replaced by async await. Today we’ll also look briefly at its use

Generator functions are an asynchronous programming solution provided by ES6 with two minor differences from normal functions.

  1. functionThere is an asterisk * between the keyword and the function name
  2. Function body for internal useyieldexpression

Let’s do a little code

function* foo () {
    yield "hello"
    yield "Grenerator"
    return "over"
}

foo() // foo {<suspended>}

let g = foo()
g.next() // {value: "hello", done: false}
g.next() // {value: "Grenerator", done: false}
g.next() // {value: "over", done: true}
Copy the code

It’s interesting to look at the print. If you execute foo separately, you don’t execute any code. Instead, you return a pointer to an internal state (Iterator Object).

Quoting teacher Ruan Yifeng’s description, it is necessary to call the next method of the traverser object to make the pointer move to the next state. That is, each time the next method is called, the internal pointer executes from the head of the function or where it was last stopped until the next yield expression (or return statement) is encountered. In other words, Generator functions are executed piecewise, yield expressions are paused tokens, and the next method can resume execution.

So what does the return value represent after executing the next method on the iterator object?

{ 
    value: "hello".// value: yield the result of the expression behind it
    done: false        // done: indicates whether traversal is complete
} 

Copy the code

After analysis, understand its execution logic and rules, and then return to the above example, to practice

function* foo () {
    let s0 = yield myAjax("http://www. Get province.com")
    let s1 = yield myAjax("http://www. Get market.com? Id ="+s0)
    let s2 = yield myAjax("http://www. Get zone. Com? Id ="+s1)
    let s3 = yield myAjax("http://www. Get street.com? Id ="+s2)
    let s4 = yield myAjax("http://www. Obtain detailed address. Com? Id ="+s3)
    return s4
}

var g = foo()

/ / define
function co (res) {
    if(res.done) return;
    res.value.then(data= > {
        co(g.next())
    })
}

co(g.next())
Copy the code
  1. To define aGeneratorThe function foo
  2. Write down the logical code for the request along the lines of synchronization
  3. Gets the traverser objectg
  4. Defines iterator object looping execution methodsco, first callco(g.next())
  5. Each callco(g.next())According to themyAjaxMethod encapsulation returns onepromise, so the return value is{ value:promsie,done:true/false }
  6. socoThe recursive processing of the function isres.value.then(...)And passed in next timeg.next()Until thedonetrue

It’s over. Does it feel like deja vu? Foo for Generator functions is basically the same as excutorAsync for async await functions, except that * is replaced with async, yield with await, This is why async await is the syntactic sugar of the Generator. This has the advantage of not calling the next method of the traversal object itself and is more semantic.

End, Js asynchronous solution is a front-end player must have the core knowledge, you have mastered?

Small encouragement, great growth, welcome to praise