preface

We know that both Promise and Async/await functions are used to solve the asynchrony problem in JavaScript, from the initial callback function to asynchrony, to the Promise function to asynchrony, to the Generator to Async/await function to asynchrony. Each new technology update makes the way JavaScript handles asynchrony more elegant, and at present Async/await is considered the ultimate solution to asynchronous processing, making JS asynchronous processing more and more like synchronous tasks. The ultimate state of asynchronous programming is that you don’t care if it’s asynchronous at all.

If this article helps you, ❤️ follow + like ❤️ to encourage the author, the article public account first, followThe front nine southGet the latest articles for the first time

The evolution of asynchronous solutions

1. Callback function

From early Javascript code, before ES6, almost all asynchronous processing was based on callback functions. You may have seen code like this:

ajax('aaa'.() = > {
    // Callback function body
    ajax('bbb'.() = > {
        // Callback function body
        ajax('ccc'.() = > {
            // Callback function body})})})Copy the code

Yes, before ES6, this kind of code was pretty much everywhere. It solves the problem of asynchronous execution, but with it comes the familiar callback hell problem:

  • There is no order: nested function execution makes debugging difficult and maintenance and reading difficult
  • Too coupling: Changes to one nesting level can affect the execution of the entire callback

So, in order to solve this problem, the community first proposed and implementedPromiseES6 has written it into the language standard and unified its usage.

2.Promise

Promise is a solution to asynchronous programming that makes more sense and is more powerful than traditional solutions — callback functions and events. It was created to solve the problem of the callback function.

With the Promise object, asynchronous operations can be expressed as a flow of synchronous operations, avoiding layers of nested callback functions. In addition, Promise objects provide a unified interface that makes it easier to control asynchronous operations.

So we can change the callback to this :(assuming ajax is wrapped with Promise)

ajax('aaa').then(res= >{
  return ajax('bbb')
}).then(res= >{
  return ajax('ccc')})Copy the code

By using Promise to handle asynchrony, it looks cleaner than previous callback functions, solves callback hell, and makes chained calls to Promise’s THEN more acceptable and consistent with our idea of synchronization.

But Promise has its downsides:

  • Internal misuse of Promisetry catchYou can’t capture it. You have to use itthenThe second callback orcatchTo capture the
let pro
try{
    pro = new Promise((resolve,reject) = > {
        throw Error('err.... ')})}catch(err){
    console.log('catch',err) // Can't print
}
pro.catch(err= >{
    console.log('promise',err) / / prints
})
Copy the code
  • Once a Promise is created, it is executed immediately and cannot be canceled

I wrote an article about how to use and implement a Promise before, explaining how Promise is used and how to implement it internally. If you’re not sure about Promise, check it out

3.Generator

Generator functions are an asynchronous programming solution provided by ES6 with completely different syntactic behavior from traditional functions. The Generator function takes JavaScript asynchronous programming to a whole new level.

The statement

Similar to function declarations, except that there is an asterisk between the function keyword and the function name, and that the function body uses yield expressions to define different internal states (yield means “output” in English).

function* gen(x){
 const y = yield x + 6;
 return y;
}
// If used in another expression, the yield should be placed inside ()
// Do not add () to the right of =
function* genOne(x){
  const y = 'This is the first yield execution:The ${yield x + 1}`;
 return y;
}
Copy the code

perform

const g = gen(1);
// Executing Generator returns an Object, instead of returning the value after return as normal functions do
g.next() // { value: 7, done: false }
The next method, which calls the pointer, executes from the head of the function or where it was last stopped until the next yield expression or return is paused, executing the yield line
// An Object is returned,
// value is the value after yield, and done indicates whether the function is finished
g.next() // { value: undefined, done: true }
// Since the last line of return y is executed, done is true
Copy the code

When a Generator function is called, it does not execute and returns not the result of the function’s execution, but a pointer Object to the internal state, known as an Iterator Object. Next, the next method of the traverser object must be called to move the pointer to the next state.

So the above callback function can be written like this again:

function *fetch() {
    yield ajax('aaa')
    yield ajax('bbb')
    yield ajax('ccc')}let gen = fetch()
let res1 = gen.next() // { value: 'aaa', done: false }
let res2 = gen.next() // { value: 'bbb', done: false }
let res3 = gen.next() // { value: 'ccc', done: false }
let res4 = gen.next() // {value: undefined, done: true} done indicates that the execution is complete
Copy the code

Because the Generator returns a traverser object that only calls to the next method iterate over the next internal state, it actually provides a function to pause execution. The yield expression is the pause flag.

The next method of the traverser object runs as follows.

(1) When a yield expression is encountered, the following operation is paused, and the value immediately following the yield expression is used as the value of the returned object’s value property.

(2) The next time the next method is called, the execution continues until the next yield expression is encountered.

(3) If no new yield expression is encountered, the function is run until the end of the return statement, and the value of the expression following the return statement is used as the value of the returned object’s value property.

(4) If the function does not have a return statement, the value attribute of the returned object is undefined.

yieldThe expression itself returns no value, or always returnsundefined.nextA method can take one argument, which is treated as the previous oneyieldThe return value of an expression.

How to understand this sentence? Let’s look at this example:

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
Copy the code

Since yield (x+1) returns no value, the value of (yield (x+1)) is undefined, so the second execution of a.ext () is 2*undefined, so the value of NaN, so the second execution of b.ext () is 12. It will be treated as the return value of the first execution of b.ext (), so it will evaluate correctly in the b example. The value in the next result cannot be confused with the yield return value, which is not the same thing

Yield vs. return

Similarities:

  • Can return the value of the expression following the statement
  • Can suspend function execution

The difference between:

  • A function can have multiple yields, but only one return
  • Yield has location memory,return does not

4.Async/await

Async/await is the syntactic sugar of the Generator above. Async functions are funciton * and await functions are yield. The async/await mechanism automatically includes the spawn auto-execute function we encapsulated above.

So the above callback can be written more succinctly:

async function fetch() {
  	await ajax('aaa')
    await ajax('bbb')
    await ajax('ccc')}This can be done if the three requests are dependent on each other, otherwise it will cause performance problems because you need to wait for each request to complete before issuing a request. If there are no dependencies, it is recommended that they both issue requests at the same time, using promise.all ()
Copy the code

The improvements of async over Generator are shown in the following four aspects:

  • Built-in actuators:asyncFunctions execute just like normal functions, unlikeGeneratorFunction, need to be callednextMethod, or usecoThe module can actually execute
  • Clearer semantics:asyncandawaitCompared to asterisks andyieldThe semantics are clearer.asyncIt means that there are asynchronous operations in the function,awaitIndicates that the following expression needs to wait for the result.
  • Wider applicability:coModule convention,yieldThe command can only be followed by a Thunk function or a Promise object, andasyncFunction of theawaitThe command can be followed by a Promise object and a value of the original type (numeric, string, and Boolean, but will be automatically converted to an immediate Resolved Promise object).
  • The return value is Promise:asyncThe function returns a Promise object, which is much more convenient than Generator functions that return an Iterator. You can usethenMethod specifies what to do next.

Async function

The async function returns a Promise object, so it can call the then method

async function fn() {
  return 'async'
}
fn().then(res= > {
  console.log(res) // 'async'
})
Copy the code

Await the expression

The expression to the right of await is usually a promise object, but it can be any other value

  1. If the expression is a Promise object, await returns the promise success value
  2. If the expression is another value, return this value directly as await
  3. The Promise object after await blocks the code that follows. The Promise object resolve then gets the value of resolve as the result of the operation on the await expression
  4. So this is why await must be used in async, which just returns a Promise object that can block asynchronously
function fn() {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            resolve(1000)},1000); })}function fn1() { return 'nanjiu' }
async function fn2() {
    // const value = await fn() // await is a Promise
    // const value = await '
    const value = await fn1()
    console.log('value', value)
}
fn2() // value 'nanjiu'
Copy the code

Comparison of asynchronous schemes

The last three schemes are proposed to solve the traditional callback function, so their advantages over the callback function is self-evident. Async /await is the syntactic sugar of Generator functions.

  • Internal misuse of Promisetry catchYou can’t capture it. You have to use itthenThe second callback orcatchTo capture, whileasync/awaitError can be usedtry catchcapture
  • PromiseOnce created, it executes immediately without blocking subsequent code, whileasyncAn await followed by a Promise object in a function blocks the following code.
  • asyncThe function implicitly returns onepromisethepromisethereosolveThe value is the value of the function return.
  • useasyncFunctions can make code more concise, without the need to look likePromiseYou also need to callthenMethod to get the return value without writing an anonymous functionPromiseResolves, does not need to define redundant data variables, and avoids nested code.

Said so much, incidentally look at a topic ~

console.log('script start')
async function async1() {
    await async2()
    console.log('async1 end')}async function async2() {
    console.log('async2 end')
}
async1()

setTimeout(function() {
    console.log('setTimeout')},0)

new Promise(resolve= > {
    console.log('Promise')
    resolve()
})
.then(function() {
    console.log('promise1')
})
.then(function() {
    console.log('promise2')})console.log('script end')
Copy the code

Resolution:

The printing order should be: script start -> async2 end -> Promise -> script end -> asynC1 end -> promise1 -> promise2 -> setTimeout

As usual, global code is executed from top to bottom, print script start, then async1(), await async2(), execute async2, print async2 end, then await code is placed in microtask queue. Then I go down new Promise, print out Promise, I meet resolve, I put the first THEN method on the microtask queue, I go down and print out script End, the global code is done, I fetch the first microtask from the microtask queue, This then method is fulfilled. The current Promise will be fulfilled, which is a big Promise. It can also trigger the callback of the THEN, so the second THEN will be added to the microtask queue. Then the microtask queue is used to fetch the microtask execution and print promise2, the microtask queue is empty, and the macro task queue is used to print setTimeout.

Problem-solving tips:

  • As long as the code executes normally or returns normally, the current new Promise instance will be fulfilled. If an error is reported or promise.reject () is returned, the new Promise instance is in the Rejected state.
  • The depressing state can trigger the then callback
  • The Rejected state can trigger a catch callback
  • The async function is executed and returns a Promise object
  • Await is equivalent to the Promise’s THEN and everything under await in the same scope is the content of the callback in the THEN
  • In asynchrony, micro tasks are performed before macro tasks

Recommended reading

  • 【Vue source code learning 】 rely on collection
  • 【Vue source code learning 】 responsive principle exploration
  • Causes and solutions of unreliable JS timer execution
  • From how to use to how to implement a Promise
  • Explains the page loading process in great detail

The original original address point here, welcome everyone to pay attention to the public number “front-end South Jiu”.

I’m Nan Jiu, and we’ll see you next time!!