Abstract:

  • 1. What is asynchronous? Why asynchrony?
  • 2. How is ASYNCHRONOUS JS implemented?
  • 3. Evolution of JS asynchronous programming mode
  • 4, in actual combat

1. What is asynchronous?

Synchronization: Perform the next task only after the task is completed and the result is obtained. Asynchronous: The system directly executes the next task before completing the task.

Most of the scenarios in our daily work can be implemented using synchronous code. Tell the compiler what you should do first and then:

Do (' Come and draw a dragon with me on the left '); // step 1 do(' Draw a rainbow on your right (walk) '); // step 2 do(' Draw a rainbow with me on the left '); // step 3 do(' Draw another dragon on your right (don't stop) '); // step 4 do(' Draw a Aaron Kwok on your chest '); // do('... "') / /...Copy the code

If the function is synchronous, it will wait until it gets the expected result, even if the function is called to perform a time-consuming task.

However, there may be situations where synchronization is not sufficient, and asynchronous writing is required:

/ / timer
setTimeout((a)= > {
    console.log('Hello');
}, 3000)
// Read the file
fs.readFile('hello.txt'.'utf8'.function(err, data) {
    console.log(data);
});
// Network request
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // Add the callback function
xhr.open('GET', url);
xhr.send(); // Initiate the function
Copy the code

If the function is asynchronous, the call will return immediately, but not immediately. The caller does not have to wait actively. When the caller gets the result, it will actively notify the caller through the callback function.

“Asynchronous mode” is very important. On the browser side, long operations should be performed asynchronously to prevent the browser from becoming unresponsive. The best example of this is Ajax operations. On the server side, “asynchronous mode” is even the only mode, because the execution environment is single-threaded, and if you allow all HTTP requests to be executed synchronously, the server performance deteriorates dramatically and quickly becomes unresponsive.

2. How is ASYNCHRONOUS JS implemented?

Multithreading VS single threading

In the course of the introduction to asynchrony, you might wonder why asynchrony exists when JavaScript is single-threaded, and who does the time-consuming operations?

Javascript is known to be single-threaded, and it was originally designed as a GUI programming language for the browser. One of the features of GUI programming is to ensure that the UI thread must not block, otherwise the experience will be poor, or even the interface will freeze.

Single-threaded means that you can only complete one task at a time. If there are multiple tasks, they must be queued, the first task completed, the next task executed, and so on.

JavaScript is a language, and whether it’s single-threaded or multi-threaded depends on the environment in which it’s running. JS is usually run in the browser, specific by the JS engine to parse and run. Let’s take a closer look at the browser.

The browser

The most popular browsers are: Chrome, Internet Explorer, Safari, FireFox and Opera. The browser kernel is multithreaded. A browser usually has the following resident threads:

  • Rendering engine thread: As the name implies, this thread is responsible for rendering the page
  • JS engine thread: responsible for PARSING and executing JS
  • Timed trigger threads: Handle timed events, such as setTimeout and setInterval
  • Event-triggered thread: Handles DOM events
  • Asynchronous HTTP request thread: Handles HTTP requests

Note that the rendering thread and the JS engine thread cannot run at the same time. The JS engine thread will be suspended while the render thread executes its task. Because JS can manipulate the DOM, browsers can be overwhelmed if JS handles the DOM in rendering.

JS engine

Usually when we talk about browsers, we talk about two engines: the rendering engine and the JS engine. The rendering engine is how to render a page. Chrome/Safari/Opera uses Webkit, IE uses Trident and FireFox uses Gecko. Different engines implement the same style inconsistently, leading to the oft-maligned browser style compatibility problem. We’re not going to go into specifics here.

A JS engine is a JS virtual machine, responsible for parsing and executing JS code. It usually involves the following steps:

  • Lexical analysis: Breaking source code into meaningful participles
  • Parsing: Parsing segmentation words into a syntax tree using a parser
  • Code generation: Generate code that the machine can run
  • Code execution

Javascript engines vary from browser to browser. Chrome uses V8, FireFox uses SpiderMonkey, Safari uses JavaScriptCore, and IE uses Chakra.

JavaScript is single threaded because the browser only has one JS engine thread open at runtime to parse and execute JS. So why only one engine? If there are two threads working on the DOM at the same time, the browser will be overwhelmed again.

So while JavaScript is single-threaded, the browser is not single-threaded internally. Some I/O operations, timer timing, and event listening (click, keyDown…) And so on are done by other threads provided by the browser.

Message queues and event loops

As you can see from the above, JavaScript also interacts asynchronously with other threads in the browser through the JS engine thread. But when exactly is the callback added to the JS engine thread? What is the order of execution?

The explanation for all this requires a continuing understanding of message queues and event loops.

As shown in the figure above, the stack on the left stores synchronous tasks, which are tasks that can be performed immediately and are not time-consuming, such as initialization of variables and functions, binding of events, etc. Operations that do not require a callback function fall into this category. The heap on the right is used to store declared variables and objects. The following queue is the message queue, which will be pushed into as soon as an asynchronous task has a response. Each asynchronous task is associated with a callback function, such as a user click event, a browser response to a service, and an event to be executed in setTimeout.

The JS engine thread is used to execute the synchronization task in the stack. When all synchronization tasks are completed, the stack is emptied, and then a pending task in the message queue is read, and the related callback function is pushed onto the stack. The single thread starts to execute the new synchronization task.

The JS engine thread reads tasks from the message queue in a continuous loop. Every time the stack is cleared, it will read new tasks in the message queue. If there is no new task, it will wait until there is a new task, which is called the event loop.

The example above shows an asynchronous AJAX request. After an asynchronous task is initiated, the AJAX thread performs time-consuming asynchronous operations, while the JS engine thread continues to perform other synchronous tasks in the heap until all asynchronous tasks in the heap are completed. The messages are then pulled out of the message queue in sequence and executed as a synchronous task in the JS engine thread, and the AJAX callback function is called to execute at some point.

The instance

Cite an interview question from an article that examines JavaScript asynchrony.

Execute the following code, after execution, click twice within 5s, after a period of time (>5s), click again twice, what is the output of the whole process?

setTimeout(function(){
    for(var i = 0; i < 100000000; i++){}
    console.log('timer a');
}, 0)

for(var j = 0; j < 5; j++){
    console.log(j);
}

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

function waitFiveSeconds(){
    var now = (new Date()).getTime();
    while(((new Date()).getTime() - now) < 5000) {}console.log('finished waiting');
}

document.addEventListener('click'.function(){
    console.log('click');
})

console.log('click begin');
waitFiveSeconds();
Copy the code

To see the output of the code above, first take a look at timers. The function of setTimeout is to insert the callback function into the message queue after a certain interval of time, and execute it after all synchronization tasks in the stack are completed. Because synchronization tasks in the stack also take time, the interval is generally greater than or equal to the specified time. SetTimeout (fn, 0) means to insert the callback function fn into the message queue immediately and wait for execution, rather than immediately. Look at an example:

setTimeout(function() {
    console.log("a")},0)

for(let i=0; i<10000; i++) {}
console.log("b")

// b a
Copy the code

The printout shows that the callback function is not executed immediately, but waits for the task in the stack to complete. It has to wait as long as the task on the stack executes.

Once you understand the function of the timer, it’s easy to figure out the output.

First, perform the synchronization task. WaitFiveSeconds is a time-consuming operation, which lasts for up to 5s.

0
1
2
3
4
click begin
finished waiting
Copy the code

Then, when the JS engine thread executes, the callback from the timer ‘timer A ‘, the callback from the timer ‘timer B ‘, and the callback from the two clicks are placed in the message queue. When the JS engine thread is idle, it checks whether there are any events to execute before processing other asynchronous tasks. This results in the following output order. .

click
click
timer a
timer b

Copy the code

Finally, the two click events after 5s are put into the message queue and executed immediately since the JS engine thread is free.

click
click
Copy the code

After understanding the mechanism of JS asynchronous implementation, let’s take a look at the evolution of JS asynchronous programming.

3. Evolution of JS asynchronous programming mode

The history of asynchro can be summarized as: callback -> promise -> generator + co -> async+await

The following steps will show each of them.

3.1 Callback functions

This is the most basic use of asynchronous programming.

To print a message after 1 second:

function asyncPrint(value, ms) {
    setTimeout((a)= > {
        console.log(value);
    },ms)
}
asyncPrint('Hello World'.1000);
Copy the code

The advantages of callback functions are that they are simple, easy to understand, and deploy. The disadvantages are that they are not conducive to code reading and maintenance, that they are highly coupled between parts, that the process can be confusing, and that only one callback function can be specified per task.

3.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 first proposed and implemented by the community, and ES6 has written it into the language standard, unifying usage, and providing Promise objects natively.

What Promise really is. When we create a Promise with a New Promise, all you’re really creating is a simple JavaScript object that calls the two methods then and catch. This is the key thing. When the state of the Promise becomes progressively, the function passed to the. Then will be called. If the state of the Promise changes to Rejected, the function passed to the.catch will be called. This means that when you create a Promise, you pass in the function that you want to call if the asynchronous request succeeds through.then, and the function that you want to call if the asynchronous request fails through.catch.

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* Asynchronous operation succeeded */){
    resolve(value);
  } else{ reject(error); }});Copy the code

For example, the above example could be written as:

function asyncPrint(value, ms) {
    return new Promise((resolve,reject) = > {
        setTimeout(resolve, ms, value);
    })
}

asyncPrint('Hello Wolrd'.1000).then((value) = > {
  console.log(value);
});
Copy the code

Promise not only avoids callback hell, but also uniformly captures the cause of failure. The downside of this approach is that it’s relatively difficult to write and understand

3.3 the Generator (ECMAScript6)

  1. A generator is a function that requires an * and can be used to generate iterators
  2. Generator functions are not the same as normal functions, which, once called, always finish executing, but can be paused in the middle of the generator function.
  3. Unlike normal functions, generators are not called immediately
  4. It returns an iterator for this generator, which is an object that returns a value object each time next is called
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
Copy the code

Using Generator, the above example can be rewritten as:

function *asyncPrint(value, ms) {
    let timer = yield setTimeout((value) = >{console.log(value)}, ms, value);
    return timer;
}

var a = asyncPrint('Hello World'.1000);
a.next();
Copy the code

3.4 co

With the rapid development of the front end, the titans felt the need to write asynchronously like synchronous code, and co was born. Co was TJ’s library that combined promises with generators and actually helped us automate iterators

function asyncPrint(value, ms) {
    return new Promise((resolve,rejuect) = >{ setTimeout(resolve, ms, value); })}function *print() {
  let a = yield asyncPrint('Hello World'.1000);
  return a;
}
function co(gen) {
  let it = gen();// We want to keep our generator running
  return new Promise(function (resolve, reject) {!function next(lastVal) {
        let {value,done} = it.next(lastVal);
        if(done){
          resolve(value);
        }else{ value.then(next,reject); }}} ()); } co(print).then(function (data) {
  console.log(data);
});
Copy the code

3.5 async/await

To implement async functions, replace the asterisk (*) of generator functions with async and replace yield with await.

function timeout(ms) {
  return new Promise((resolve) = > {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world'.1000);
Copy the code

4. JS asynchronous combat

With the warm-up of the previous content, let’s strike while the iron is hot and look at a more typical problem.

Red 3s on once, green 1s on once, yellow 2s on once; How do I make three lights turn on again and again?

Please use the above methods to achieve, specific implementation can refer to: traffic light task control

For the sake of space, the macro task, micro task, Promise, Generator and other knowledge points are not mentioned, you can baidu or Google.

That’s all about asynchronous programming in JavaScript. If you find it rewarding, feel free to like it and leave a comment.

Refer to the article

How JavaScript works

Four approaches to Asynchronous programming in JavaScript

JS asynchronous development flow – asynchronous history

History of Asynchronous Programming (Asynchronous Process History)

Promises/A+ 100 lines of code

Javascript asynchronous details