preface

When we learn JS generally know JavaScript is single-threaded, the single thread is how to deal with the request of the complex network and time-consuming operation, such as files, speaking, reading and writing will efficiency is very low, is constantly in-depth study and understanding, also gradually understand the mysteries of them, this is also the following articles to write my understanding of the asynchronous programming.

The body of the

One, synchronous and asynchronous

synchronous

Before learning about asynchrony, let’s take a look at synchronization. For example, if you call a function that returns a value, you can get the expected result directly. If you do it in the order of your code, you can say that the function is executed synchronously.

Here’s an example:

// When the function returns, it has the desired effect of printing '123' on the console.
var A = function(){};
A.prototype.n = 123;
var b = new A();
console.log(b,n);  / / 123
Copy the code

If the function is synchronous, it will wait until the expected result is obtained, even if the task it is calling is time-consuming. Because it is executed in the order in which the code is executed.

asynchronous

A function is said to be asynchronous if it does not directly get the expected result (the expected return value) when it is called, but rather needs to be obtained in a way that is discontinuous and not in code order.

As follows:

// Read the file
fc.readFile('hello'.'utf8'.function(err,data){
    console.log(data)
});
// Network request
var pzh = new XMLHttpRequest();
pzh.onreadystatechange = yyy;  // Add the callback function here
pzh.open('GET',url);
pzh.send();// Initiate the function
Copy the code

In the example above, both the file reading function readFile and the network request sending function send perform time-consuming operations. Although the functions return immediately, they cannot obtain the expected results immediately because the time-consuming operations are handed over to other threads and cannot obtain the expected results temporarily. Function (err, data) {console.log(data); } and onreadyStatechange, which pass the result information to the callback function when the time-consuming operation is completed, notifying the thread executing the JavaScript code to execute the callback.

To put it simply: Synchronization is in the order of your code, asynchronous is not in the order of your code, and asynchronous is more efficient.

Two, the first to know the asynchronous mechanism

Multithreading in the browser kernel

We all know that JavaScript is single-threaded, but the browser kernel is multi-threaded; They work with each other under the control of the kernel to keep in sync, and a browser has at least three resident threads: Javascrpt engine threads, GUI rendering threads, and browser event-triggered threads.

  • JS engine: based on event driven single thread execution, JS engine has been waiting for the arrival of tasks in the task queue, and then to process; The browser has only one JS thread running the JS program at any given time.

  • GUI render thread: This thread executes when the interface needs to be redrawn or when some operation causes backflow. It is important to note that the render thread and the JS engine thread cannot work simultaneously.

  • Event-triggered thread: When an event is triggered, this thread adds the world to the end of the queue and waits for the JavaScript engine to process it. This time can come from the JavaScript engine executing the current block of code, such as setTimeOut, or from the browser kernel and other threads, such as mouse clicks. AJAX asynchronous requests, etc., but due to the single-threaded nature of JS, all these things have to be queued up for the JS engine to process.

Event loop mechanism

As shown in the figure above, the stack on the left holds synchronous tasks, which are tasks that can be executed immediately and are not time-consuming, such as initialization of variables and functions, binding of events, and other operations that do not require callback functions.

The heap on the right is used to store declared variables and objects. The following queue is the message queue, which is pushed into the queue once an asynchronous task has a response. Each asynchronous task is associated with a callback function, such as the user’s click event, the browser’s response to the service, and the event to be executed in setTimeout.

JS engine thread is used to execute the stack of synchronization tasks, when all synchronization tasks are completed, the stack is cleared, and then read a message queue to be processed, and the related callback function pressed into the stack, a single thread began to execute a new synchronization task.

JS engine thread reads tasks from message queue is continuous loop, every time the stack is empty, will read a new task in message queue, if there is no new task, will wait until there is a new task, this is called Eventloop (Eventloop).

What are macrotasks and microtasks?

We all know that Js is single-threaded, but some time-consuming operations cause process blocking problems. To solve this problem, Js has two modes of execution for tasks: Synchronous and Asynchronous.

In asynchronous mode, there are two types of asynchronous tasks: macro task and micro task. In the ES6 specification, macroTasks are called tasks and microtasks are called Jobs. Macro tasks are initiated by the host (browser, Node), while microtasks are initiated by JS itself.

  • 1) Macrotask: low priority, first defined first executed. These include: Ajax, setTimeout, setInterval, event binding, postMessage, and MessageChannel (for message communication).
  • 2) Microtask: High priority, and can jump the queue, not first defined first executed. These include: promise.then, async/await [generator], requestAnimationFrame, observer, MutationObserver, and setImmediate.

As can be seen from the above picture:

The first loop starts from the JS main thread (overall code). After the asynchronous task is initiated, the (orange) thread executes the asynchronous operation, and the JS engine main thread continues to execute the other synchronous tasks in the heap until all the asynchronous tasks in the heap are completed. The global context then enters the function call stack. Until the call stack is empty (global only). Then all micro-tasks are executed. When all available micro-tasks are completed. The loop starts again with macro-tasks, finds one of them and executes it, then executes all of them, and so on.

According to the event loop mechanism, let’s rearrange the process:

1) Execute the tasks in the stack first

2) Find the microtask queue first. If there is one in the microtask queue, get it from the microtask queue first and execute it in the storage order.

3) If there is no microtask in the queue, then search in the macro task queue. In the macro task queue, the first person to be executed is generally taken out according to the condition of the first person to arrive for execution.

4) And so on

After understanding the event loop, remember that Javascript asynchronous programming goes through four phases: the Callback phase, the Promise phase, the Generator phase, and the Async/Await phase.

Third, the Callback phase

Callback functions are the most basic method of asynchronous operations.

Demo1: Suppose there is an asynchronous operation (asyncFn) and a synchronous operation (normalFn).

function asyncFn(){
    setTimeout(() = > {
        console.log('asyncFn'); .0)}function normalFn(){
    console.log('normalFn');
}
asyncFn();   //asyncFn
normalFn();   //normalFn
    
Copy the code

If you follow the normal JS processing mechanism, the synchronous operation must occur before the asynchronous. If I want to change the order, the easiest way to do it is to use a callback.

function asyncFn(callback){
    setTimeout(() = > {
        console.log('asyncFn');
        callback();
    },0);
}
function normalFn(){
    console.log('normalFn');
}

asyncFn(normalFn);
//asyncFn
//normalFn
Copy the code

The callback function is simple, easy to understand and implement, the advantages of faults is unfavorable to the code to read and maintain, high coupling between parts, makes it hard for program structure chaos, process tracking (especially multiple nested callback function, it’s easy to have a callback hell, readability is poor), and each task can specify a callback function. In addition, you cannot use a try catch to catch an error. You cannot return the error directly.

Callbacks are confusing — pass arguments:

First, pass the parameters of the callback function as those of the same level as the callback function.

Second, the arguments to the callback function are created inside the calling callback function.

Event listening, publish and subscribe

Event listeners

Event listening is also a very common asynchronous programming pattern. It is a typical logical separation and is useful for code decoupling.

Let’s take the functions F1 and F2 again

f1.on('done', f2);  // F2 cannot be executed until f1 is finished
Copy the code
function f1() { 
    setTimeout(function () { // ... 
        f1.trigger('done'); 
    }, 1000); 
    }
Copy the code

Above, f1.trigger(‘done’) means that the done event is triggered immediately after the execution completes, thus starting the execution of F2.

The advantage of this approach is that it is easy to understand, that multiple events can be bound, that each event can specify multiple callbacks, and that it can be “decoupled,” which facilitates modularity.

The downside is that the entire program has to be event-driven, and the running process becomes very unclear. When you read the code, it’s hard to see the main flow.

Publish subscribe model

Publish subscribe has a wide range of applications, both for asynchronous programming and for more loosely coupled code.

Assume that there are three people in a family. Mom implements and publishes the signal as a “publisher”, dad “subscribes” and processes the signal as an intermediary, and finally Xiao Ming “subscriber” knows when he can start the implementation. This is called a publish-subscribe pattern.

Here’s the code:

// The subscriber receives the message
function eat() {
    console.log('Mommy's cooking. Let's go eat.');
}

function cooking() {
    console.log('Mom is cooking.');
    // The publisher publishes messages to the subscription mediation
    setTimeout(() = > {
        console.log('When the boy's father made dinner, he asked Xiao Ming to come for dinner.')
        Dad.publish("done");// The mediation receives the message
    },3000)}function read(){
    console.log('Xiao Ming pretends to study') // Subscriber and so on
    Dad.subscribe('done',eat);
}

// Execute the code
cooking();
read()

Xiao Ming pretended to learn when his father cooked the meal, and asked Xiao Ming to come for dinner. His mother cooked the meal, and went to eat
Copy the code

Asynchronous programming in this mode is essentially implemented with callbacks, but there are still instances of nested callbacks and failure to catch exceptions. Let’s go to the Promise phase and see if we can solve these two problems.

Iv. Promise Phase

Promise is not a specific implementation, but a specification (PromiseA+ specification) that is a mechanism for handling JavaScript async.

1. Three states of Promise

  • There are three kinds of Promise: pending, fulfilled and rejected
  • The state transition can only bePending to resolved
  • orPending to the rejected

Once the state has been converted, it cannot be converted again.

It can be represented by the following figure:

Attach the code chestnut:

let p = new Promise((resolve,reject) = > {
    reject('reject');
    resolve('success')  // Invalid code is not executed
})
p.then(
    value= > {
        console.log(value)
    },
    reason= > {
        console.log(reason)  //reject})Copy the code

When we construct a Promise, the code inside the constructor executes immediately

2. Chain Promise

Let’s start with two examples:

demo1;

/ / case 1:
Promise.resolve(1)
    .then(res= > {
        console.log(res);        / / print 1
        return 2   // Wrap promsie.resolve (2)
    })
    .catch(err= > 3);   // Here the catch catches an exception that is not caught
    .then(res= > console.log(res))   / / print 2
Copy the code

Call resolve(…) when a Promise creates an object Or reject (…). The Promise passes then(…) The registered callback function is fired at the new asynchronous point in time. (Chained calls to then); If you use return in then, the value of return is wrapped by promise.resolve ().

Demo2: Take housework assignment as an example:

function read() {
  console.log('Xiao Ming reads books carefully');
}

function eat() {
  return new Promise((resolve, reject) = > {
    console.log('All right, let's eat.');
    setTimeout(() = > {
      resolve('I've had enough.');
    }, 1000)})}function wash() {
  return new Promise((resolve, reject) = > {
    console.log('Oh, doing the dishes again.');
    setTimeout(() = > {
      resolve('The dishes are done');
    }, 1000)})}const cooking = new Promise((resolve, reject) = >{ 
    console.log('Mom cooks carefully'); 
    setTimeout(() = > { 
        resolve('Come here, Xiao Ming. It's time for dinner.'); 
    }, 2000); 
})

cooking.then(msg= > { 
    console.log(msg); 
    return eat(); 
}).then(msg= > { 
    console.log(msg); 
    return wash();
}).then(msg= > {
    console.log(msg);
    console.log('You've done your chores, you can play.')
})

read();
Copy the code
/* Execute order: Mom cook carefully xiao Ming read carefully Xiao Ming come here, eat ok, eat meal is full, and wash the dishes, finish the housework, can play */
Copy the code

As you can see, promise.then () can solve the callback hell, but it can’t catch the exception and needs to call the callback function to solve it.

In other words, the Promise doesn’t really leave the callback; the Promise just uses the THEN method to delay the binding of the callback.

Generators/ yields

Generator function is an asynchronous programming solution provided by ES6. The syntax behavior of Generator is completely different from traditional functions. The biggest feature of Generator is that it can control the execution of functions.

  • function *Will define a Generator function and return a Generator object whose internals can be passed throughyieldPause the code by callingnextResume execution.

Here’s a quick example:

 function * gen() {
     yield console.log('hello');
     yield console.log('world');
     return console.log('ending');
}

var hw = gen();
Copy the code

On the console type hw.next():

hw.next(); 
index.html:156 hello 
hw.next(); 
index.html:157 world 
hw.next(); 
index.html:158 ending
Copy the code

The above code defines a Generator function, helloWorldGenerator, with two yield expressions inside it (Hello and world). That is, the function has three states: hello, world, and a return statement (ending execution).

The next() method of the traverser object must be called to move the pointer to the next state. Each time the next method is called, the internal pointer executes from where it stopped until the next yield expression (or return statement) is encountered.

Generators are handy for handling asynchron (usually in conjunction with the TJ/CO library). Take co for example, which is a generator-based process control tool for Node.js and browsers. Promise allows you to write non-blocking code in a more elegant way.

Install co library: NPM install co

You can also go to Github to find the source code, understand

index.js

var co = require('co')
var fs = require('fs')
// wrap the function to thunk
function readFile(filename) {
    return new Promise(function(resolve, reject) {
        fs.readFile(filename, function(err, date) {
            if (err) reject(err)
            resolve(data)
        })
    })
}
/ / function generator
function *gen() {
    var file1 = yield readFile('./file/1.txt') // 1.txt The content is content in 1.txt
    var file2 = yield readFile('./file/2.txt') // 2.txt The content is content in 2.txt
    console.log(file1)
    console.log(file2)
    return 'done'
}
// co
co(gen).then(function(err, result) {
    console.log(result)
})
// content in 1.txt
// content in 2.txt
// done
Copy the code

The CO function library lets you not write an executor for the generator function. The generator function is automatically executed as long as it is placed in the CO function. Let’s do another example

co(function* (){ 
    try { 
      var res = yield get('http://baidu.com');
      console.log(res); 
    } catch(e) { 
      console.log(e.code) 
   } 
})
Copy the code

The great benefit of CO is that it allows asynchronous processes to be written synchronously, using try/catch.

Six, async/await

With async/await, you can easily do what you used to do with generators and co functions; In short, async functions are the syntax sugar for Generator functions.

We can then implement the above (two files) example with async/await:

var asyncReadFile = async function (){
  var f1 = await readFile('./file/1.txt');
  var f2 = await readFile('./file/2.txt');
  console.log(f1.toString());
  console.log(f2.toString());
};
Copy the code

A comparison shows that async functions replace the asterisk (*) of Generator functions with async and yield with await.

1. Features of async function:

1. Execute async function and return Promise objects

async function test1(){
     return 123;
}
async function test2(){
     return Promise.resolve(2);
}
const result1 = test1();
const result2 = test2();
console.log('result1',result1);   //promise
console.log('result2',result2)    //promise
Copy the code

You can print it yourself to verify.

2.Promise.then the successful case corresponds to await

async function test3(){
    const p3 = Promise.resolve(3);
    p3.then(data= > {
        console.log('data',data);    
    })
    // Await followed by a Promise object
    const data =await p3;  
    console.log('data',data);   //data3
}
test3()

async function test4(){       //await with an ordinary number
    const data4 = await 4;   //await Promise.resolve(4)
    console.log('data4',data4);     / / data4.4
}
test4();

async function test5(){
     const data5 = await test1();   // await is an asynchronous function
     console.log('data5',data5);     / / data5.123
}
test5()
Copy the code
  1. Promise.catch exceptions correspond to try… catch
async function test6(){
    const p6 = Promise.reject(6);
    // const data6 = await p6;
    Log ('data6', 'data6') // Error: Uncaught (in promise) 6
    try{
        const data6 = await p6;
        console.log('data6',data6);
    }catch(k){
        console.error('k',k);     // Catch exception k 6
    }
}
test6()
Copy the code

Sum up so much, if still not too understand, I recommend to take a look at these actual combat exercises (ES6Promise actual combat exercises) to accelerate help digestion.

This article for personal learning notes to share, limited technology, welcome to discuss learning.

Reference article:

1. Detailed explanation of JavaScript asynchronous mechanism

2. Six schemes of JS asynchronous programming

3. Four methods of Javascript asynchronous programming

4.JS based asynchronous (5) : Generator