Asynchronous evolution of JavaScript

Time:2021-6-11

Synchronous and asynchronous

Usually, code is executed from top to bottom. If there are more than one task, it must be queued, the former task is completed, and the latter task will be executed. This mode of implementation is called:Synchronous. Novices are easy to confuse the synchronization in computer language with that in daily language. For example, synchronization in “synchronize files to the cloud” refers to “keep… Consistent.”. In computer, synchronization refers to the mode in which tasks are executed from top to bottom. For example:

Example 1

A();
B();
C();

In the above code, a, B and C are three different functions, each of which is an unrelated task. In synchronous mode, the computer will execute task a first, then Task B, and finally task C. In most cases, synchronous mode is OK. However, if Task B is a time-consuming network request and task C is just to present a new page, there is no dependency between B and C. This will cause the web page to jam. There is a solution that B is executed after C, but the only disadvantage is that B’s network request will be sent later.

There is another more perfect solution, which divides the B task into two parts. One part is to execute the task of network request immediately; The other part is the task that is executed after the requested data comes back. This pattern in which one part is executed immediately and the other part is executed in the future is calledAsynchronous. The pseudo code is as follows:

Example 2

A();
//Send request now
ajax('url1',function B() {
  //At some point in the future
})
C();
//Execution order a = > C = > b

In fact, the JavaScript engine first performs the task of calling the browser’s network request interface (part of the task), and then the browser sends the network request and listens for the request return (this task is not performed by the JavaScript engine, but by the browser); After the request is put back, the browser notifies the JavaScript engine to start the task (another part) in the callback function. The essence of JavaScript’s asynchronous ability is the multithreading ability of browser or node.

callback

The function to be executed in the future is usually called a callback. Using the asynchronous mode of callback solves the problem of blocking, but it also brings some other problems. In the beginning, our functions were written from top to bottom and executed from top to bottom, which is very consistent with our thinking habits, but now they are interrupted by callback! In the above code, it skips the B task and executes the C task first! This kind of asynchronous “non-linear” code is more difficult to read than synchronous “linear” code, so it is easier to breed bugs.

Try to judge the execution order of the following code, you will have a deeper understanding of the “nonlinear” code is more difficult to read than the “linear” code.

Example 3

A();
ajax('url1', function(){
    B();
    ajax('url2', function(){
        C();
    }
    D();
});
E();

//Here's the answer. Are you right?
// A => E => B => D => C

In example 3, our reading code isA => B => C => D => E, but the order of execution isA => E => B => D => C. The order of execution from top to bottom is disrupted by callback, which is the bad thing about non-linear code.

In the above example, we canajaxTasks to be performed laterEAnd tasksDIn advance, to optimize the code. This technique is very useful when writing multiple nested code. After improvement, the results are as follows.

Example 4

A();
E();
ajax('url1', function(){
    B();
    D();
    ajax('url2', function(){
        C();
    }
});
//With a little optimization, the code is easier to understand
// A => E => B => D => C

In example 4, only successful callbacks are handled, not exception callbacks. Next, add the exception handling callback and discuss the problem of “linear” code execution.

Example 5

A();

ajax('url1', function(){
    B();

    ajax('url2', function(){
        C();
    },function(){
        D();
    });

},function(){
    E();

});

In example 5, after adding the exception handling callback,url1The successful callback function B and the exception callback function E are separated. This kind of “non-linear” situation appears again.

In node, in order to solve the “non-linear” problem of exception handling, the error priority strategy is formulated. The first parameter of callback in node is used to determine whether an exception occurs.

Example 6

A();

get('url1', function(error){
    if(error){
        E();
    }else {
        B();

        get('url2', function(error){
            if(error){
                D();
            }else{
                C();
            }
        });
    }
});

So far, the “nonlinear” problem caused by callback has been basically solved. Unfortunately, once the number of nesting layers increases, it is not very convenient to read. In addition, once an exception occurs in a callback, it can only be handled inside the current callback, and there is no overall exception bottoming scheme.

promise

In the asynchronous evolution history of JavaScript, a series of libraries have emerged to solve the problem of callback, and promise has become the final winner, and has been successfully introduced into ES6. It will provide a better “linear” way of writing and solve the problem that asynchronous exceptions can only be caught in the current callback.

Promise is like a mediator, which promises to return a trusted asynchronous result. The two parties that sign the protocol are asynchronous interface and callback. First, promise signs a protocol with the asynchronous interface. When successful, callresolveFunction to notify promise. When exception occurs, callrejectInform promise. On the other hand, promise and callback also sign a protocol when the asynchronous interfaceresolveorrejectWhen called, promise returns a trusted value tothenandcatchCallback registered in.

The simplest example of promise is as follows:

Example 7

//Create a promise instance (asynchronous interface signs protocol with promise)
var promise = new Promise(function (resolve,reject) {
  ajax('url',resolve,reject);
});

//Call the then catch method of the instance (successful callback, exception callback sign protocol with promise)
promise.then(function(value) {
  // success
}).catch(function (error) {
  // error
})

Promise is a very good mediator. It only returns credible information to callback. How to understand the concept of credibility? To be exact, a callback is bound to be rejectedAsynchronous call, andIt will only be called once. For example, when using a third-party library, due to some reasons, the (fake) “asynchronous” interface is unreliable. It executes synchronous code instead of entering asynchronous logic, such as example 8.

Example 8

var promise1 = new Promise(function (resolve) {
  //For some reason, the asynchronous interface is executed synchronously
  if (true ){
    //Synchronization code
    resolve('B');
  } else {
    //Asynchronous code
    setTimeout(function(){
      resolve('B');
    },0)
  }

});

//Promise is still executed asynchronously
promise1.then(function(value){
    console.log(value)
});

console.log('A');
//A = > b (a before B)

For another example, asynchronous interfaces are unreliable for some reasons,resolveorrejectIt was executed twice. However, promise will only notify callback of the result returned by the first asynchronous interface. For example 9:

Example 9

var promise2 = new Promise(function (resolve) {
  //Resolve was executed twice
  setTimeout(function(){
    Resolve ("first time");
  },0)
  setTimeout(function(){
    Resolve ("the second time");
  },0)
});

//But callback will only be called once,
promise2.then(function(msg){
    Console.log (MSG) // first time
    console.log('A')
});
//A (only one)

After introducing the features of promise, let’s see how it uses chain call to solve the problem of asynchronous code readability in callback mode. Chain call refers to: functionreturnAn object that can continue to be executed, which can continue to be called, andreturnAnother object that can continue to execute, so repeatedly to achieve the result of continuous call. For example 10:

Example 10

//Return a promise object that can continue to execute
var fetch = function(url){
    return new Promise(function (resolve,reject) {
        ajax(url,resolve,reject);
    });
}

A();
fetch('url1').then(function(){
    B();
    //Returns a new promise instance
    return fetch('url2');
}).catch(function(){
    C();
    //In case of exception, a new promise instance can be returned
    return fetch('url2');
    //Call the then method of this new promise instance using chaining
}).then(function() {
    //You can continue to return or not to return to end the chain call
    D();
})
//A B C D (sequential execution)

By repeatedly returning a promise object, promise gets rid of the problem of layer by layer nesting of callback and the problem of “non-linear” execution of asynchronous code.

In addition, promise also solves a difficult problem: callback can only capture the current error exception. Promise is different from callback. Each callback can only know its own error report, but promise represents all callbacks. All error reports of callbacks can be handled by promise. So, you can set one at the endcatchTo catch an exception that was not caught before.

Promise solves the problem of asynchronous call of callback, but promise does not get rid of callback. It just puts the callback into a trusted intermediate organization, which links the callback and asynchronous interface. In addition, chaining is not very elegant. The async function scheme introduced next will give a better solution.

Asynchronous function

Async function is a new feature of ES7. It combines promise to let us get rid of the shackles of callback and write asynchronous functions directly in “synchronous” mode. Note that synchronization here refers to writing synchronization, but it is still executed asynchronously.

To declare an asynchronous function, just add a keyword before the normal functionasyncFor example:

async function main(){}

In asynchronous functions, you can use theawaitKeyword, which means to wait for the execution result of the following expression, and then continue to execute. Expressions are generally promise instances. For example, example 11:

Example 11

var  timer = function (delay) {
  return new Promise(function create(resolve,reject) {
    if(typeof delay !== 'number'){
      reject(new Error('type error'));
    }
    setTimeout(resolve,delay,'done');
  });
}

async function main{
    var value = await timer(100);
    //It will not be executed immediately. It will be executed after 100ms
    console.log(value);  // done
}

main();

Asynchronous functions are called in the same way as ordinary functions and are executed firstmain()Function. After that, it will be executed immediatelytimer(100)Function. Wait until(await)Promise function(timer(100))After the result is returned, the program will execute the next line of code.

Asynchronous functions are similar to ordinary functions in writing, except that the declaration method mentioned above is similar to the call method, it can also be usedtry...catchTo catch an exception, you can also pass in parameters. But it’s used in asynchronous functionsreturnIt doesn’t work. It’s the same as the normal callback functionreturnIt doesn’t work for the same reason. The callback or asynchronous function is executed separately in the JavaScript stack, and the synchronous code has been executed.

In asynchronous functions, use thetry...catchThe scheme of exception capture replaces promisecatchException capture scheme based on. Examples are as follows:

Example 12

async function main(delay){
  try{
    //Timer is declared in example 11
    var value1 = await timer(delay);
    var value2 = await timer('');
    var value3 = await timer(delay);
  }catch(err){
    console.error(err);
      // Error: type error
      //   at create (<anonymous>:5:14)
      //   at timer (<anonymous>:3:10)
      //   at A (<anonymous>:12:10)
  }
}
main(0);

What’s more, asynchronous functions follow the principle of “function is the first citizen”. It can also be passed into normal functions and asynchronous functions for execution as values. It should be noted that in asynchronous functions, the asynchronous function should be usedawaitOtherwise, the asynchronous function will be executed synchronously. Examples are as follows:

Example 12

async function doAsync(delay){
    //Timer is declared in example 11
    var value1 = await timer(delay);
    console.log('A')
}

async function main(main){
  doAsync(0);
  console.log('B')
}

main(main);
// B A

The value printed at this time isB A. explaindoAsyncIn functionawait timer(delay)And it was executed synchronously. If you want to execute it correctly and asynchronouslydoAsyncFunction, which needs to be added before the functionawaitKeywords, as follows:

async function main(delay){
    var value1 = await timer(delay);
    console.log('A')
}

async function doAsync(main){
    await main(0);
    console.log('B')
}

doAsync(main);
// A B

Because asynchronous functions use class synchronization writing method, novices may write as follows when handling multiple concurrent requests:

Example 13

var fetch = function (url) {
  return new Promise(function (resolve,reject) {
    ajax(url,resolve,reject);
  });
}

async function main(){
  try{
    var value1 = await fetch('url1');
    var value2 = await fetch('url2');
    conosle.log(value1,value2);
  }catch(err){
    console.error(err)
  }
}

main();

But it can lead tourl2Your request must wait untilurl1Will not be sent until your request comes back. Ifurl1Andurl2There is no mutual dependence. It is better to send these two requests at the same time.

Promise.allThis method can handle concurrent requests well.Promise.allAccept multiple promise instances as parameters and wrap them into a new promise instance. So,Promise.allAll requests will be sent out in the first time; It will not be triggered until all requests come back successfullyPromise.allOfresolveFunction; Called immediately when a request failsPromise.allOfrejectFunction.

var fetch = function (url) {
  return new Promise(function (resolve, reject) {
    ajax(url, resolve, reject);
  });
}

async function main(){
  try{
    var arrValue = await Promise.all[fetch('url1'),fetch('url2')];
    conosle.log(arrValue[0], arrValue[1]);
  }catch(err){
    console.error(err)
  }
}

main();

Finally, the content of asynchronous function is summarized

  • Statement:async function main(){}

  • Asynchronous function logic: you can useawait

  • Call:main()

  • Catch exception:try...catch

  • Incoming parameters:Main ('first parameter ')

  • Return: invalid

  • Asynchronous functions are passed into other functions as parameters: Yes

  • Processing concurrency logic:Promise.all

At present, the latest Chrome / node supports the writing of ES7 asynchronous functions. In addition, you can also use Babel to escape the asynchronous functions to Es5 syntax. You can try it yourself, using asynchronous functions, using class synchronization, writing asynchronous code.