Details of JS Asynchronous Development

To know what it is and why it is, first understand three concepts:

1. What is synchronization?

Synchronization means that when a “call” is issued, the “call” will not return until the result is obtained. But once the call returns, you get the return value. In other words, it is the “caller” who actively waits for the result of this “call”. Before this call is completed, code execution after blocking.

2. What is asynchronous?

After the “call” is issued, the call returns directly, so no result is returned. In other words, when an asynchronous procedure call is issued, the caller will not get the result immediately. Instead, after the “call” is issued, the “callee” notifies the caller through status, notification, or processes the call through a callback function. After the asynchronous call is issued, the execution of the following code will not be affected.

3. Why do you need asynchrony in 3.JavaScript?

First of all, we know that JavaScript is single threaded (JS is still single threaded in nature even if webworker is added). What does synchronization code mean? This means that there is a possibility of blocking. When we have a task that takes a long time, if we use the synchronization method, the subsequent code execution will be blocked. Asynchronous does not. We will not wait for the asynchronous code to continue executing the code after the asynchronous task.

More quality articles to stamp on: https://github.com/YvetteLau/ …

After understanding the concept, we will get to today’s topic. First of all, let’s think about: what asynchronous solutions are mainly used in our daily work, and what are the advantages and disadvantages of these asynchronous solutions?

The earliest solution to asynchrony is callback function, such as callback of event and callback in setInterval/setTimeout. However, there is a common problem with callback functions, which is the problem of callback to hell (which will be illustrated later).

In order to solve the problem of returning to hell, the community has proposed a Promise solution, which ES6 has written into the language standard. Promise solves the problem of callback to hell to some extent, but Promise also has some problems, such as errors cannot be try catch, and using Promise’s chain call actually does not fundamentally solve the problem of callback to hell, but it is written in a different way.

Generator function is introduced into ES6. Generator is an asynchronous programming solution. Generator function is the implementation of synergetics in ES6. Its greatest feature is that it can hand over the execution right of the function. Generator function can be seen as the container of asynchronous tasks. Any place that needs to be suspended is indicated by yield statement. But Generator is more complicated to use.

ES7 also proposes a new asynchronous solution: async/aware, async is the syntax sugar of Generator function, async/aware makes asynchronous code look like synchronous code, and the development goal of asynchronous programming is to make asynchronous logic code look like synchronous.

Callback function-> promise-> generator-> async/away.

1. callback function: callback

//node读取文件
fs.readFile(xxx, 'utf-8', function(err, data) {
    //code
});

Use scenario of callback function (including but not limited to):

  1. Event callback
  2. Node API
  3. Callback function in setTimeout/setInterval
  4. Ajax request

Advantages of callback function: simple.

Disadvantages of callback function:

Asynchronous callback nesting will make the code difficult to maintain, and it is not convenient to handle errors in a unified way. It cannottry catchAnd callback hell (e.g. read a text first, then read b according to a text and then read c … according to b).

fs.readFile(A, 'utf-8', function(err, data) {
    fs.readFile(B, 'utf-8', function(err, data) {
        fs.readFile(C, 'utf-8', function(err, data) {
            fs.readFile(D, 'utf-8', function(err, data) {
                //....
            });
        });
    });
});

2.Promise

Promise solves the problem of callback to hell to a certain extent. Promise was first proposed and implemented by the community. ES6 has written it into the language standard, unified its usage, and provided Promise objects in the original.

Then let’s look at how Promise solves the problem of callback to hell, still taking readFile above as an example (first read A text content, then read B according to A text content and then read C according to B content).

function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}
read(A).then(data => {
    return read(B);
}).then(data => {
    return read(C);
}).then(data => {
    return read(D);
}).catch(reason => {
    console.log(reason);
});

Advantages of Promise:

  1. Once the state changes, it will not change again, and this result can be obtained at any time.
  2. Asynchronous operations can be expressed in the flow of synchronous operations, thus avoiding callback functions nested layer by layer.

Disadvantages:

  1. Promise cannot be cancelled
  2. When in the pending state, it is impossible to know where the current progress is.
  3. Errors cannot betry catch

Suppose there is such a requirement: read the contents of A, B and C files, and output the final result after reading them successfully. Before Promise, we can generally use the publish-subscribe mode to realize:

let pubsub = {
    arry: [],
    emit() {
        this.arry.forEach(fn => fn());
    },
    on(fn) {
        this.arry.push(fn);
    }
}

let data = [];
pubsub.on(() => {
    if(data.length === 3) {
        console.log(data);
    }
});
fs.readFile(A, 'utf-8', (err, value) => {
    data.push(value);
    pubsub.emit();
});
fs.readFile(B, 'utf-8', (err, value) => {
    data.push(value);
    pubsub.emit();
});
fs.readFile(C, 'utf-8', (err, value) => {
    data.push(value);
    pubsub.emit();
});

Promise has provided us withPromise.allFor this requirement, we can usePromise.allTo achieve.

/**
 * 将 fs.readFile 包装成promise接口
 */
function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}

/**
 * 使用 Promise
 * 
 * 通过 Promise.all 可以实现多个异步并行执行,同一时刻获取最终结果的问题
 */
Promise.all([
    read(A),
    read(B),
    read(C)
]).then(data => {
    console.log(data);
}).catch(err => console.log(err));

Executable code can stamp:https://github.com/YvetteLau/ …

3.Generator

Generator function is an asynchronous programming solution provided by ES6. The whole Generator function is an encapsulated asynchronous task, or a container of asynchronous tasks. Where asynchronous operation needs to be paused, it is indicated with yield statement.

The Generator function is generally used with yield or Promise. The Generator function returns an iterator. For students who don’t know about generators and iterators, please make up your own foundation. Let’s look at the simple use of Generator:

function* gen() {
    let a = yield 111;
    console.log(a);
    let b = yield 222;
    console.log(b);
    let c = yield 333;
    console.log(c);
    let d = yield 444;
    console.log(d);
}
let t = gen();
//next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
t.next(1); //第一次调用next函数时,传递的参数无效
t.next(2); //a输出2;
t.next(3); //b输出3; 
t.next(4); //c输出4;
t.next(5); //d输出5;

In order to make everyone better understand how the above code is executed, I drew a diagram, corresponding to each next method call:

Still taking the above readFile (first read A text content, then read B according to A text content, then read C according to B content) as an example, it is implemented by using Generator+co library:

const fs = require('fs');
const co = require('co');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);

function* read() {
    yield readFile(A, 'utf-8');
    yield readFile(B, 'utf-8');
    yield readFile(C, 'utf-8');
    //....
}
co(read()).then(data => {
    //code
}).catch(err => {
    //code
});

I don’t need to say about the disadvantages of Generator, unless it is abuse, it is generally not directly used to solve asynchronous (of course, it is not excluded because I am not skilled) ~ ~ ~

How to implement without co library? Can I write the simplest my_co by myself, which is helpful to understand the implementation principle of async/await? Please stamp:https://github.com/YvetteLau/ …

PS: If you are not familiar with Generator/yield, it is recommended to read ES6 related documents.

4.async/await

Async/await concept is introduced in ES7. Async is actually a syntax sugar. Its implementation is to wrap the Generator function and the Automatic Executor (co) in a function.

Async/await has the advantage of clear code and can handle the problem of callback to hell without writing many then chains like Promise. And errors can be try catch.

Still taking the above readFile (first read A text content, then read B according to A text content, then read C according to B content) as an example, async/await is used to realize:

const fs = require('fs');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);


async function read() {
    await readFile(A, 'utf-8');
    await readFile(B, 'utf-8');
    await readFile(C, 'utf-8');
    //code
}

read().then((data) => {
    //code
}).catch(err => {
    //code
});

Async/await is used to realize this requirement: read the contents of A, B and C files, and output the final result after reading them successfully.

function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}

async function readAsync() {
    let data = await Promise.all([
        read(A),
        read(B),
        read(C)
    ]);
    return data;
}

readAsync().then(data => {
    console.log(data);
});

Therefore, the asynchronous development history of JS can be considered from callback-> promise-> generator-> async/away. Async/await makes asynchronous code look like synchronous code. The development goal of asynchronous programming is to make asynchronous logic code look like synchronous.

Due to my limited level, the content in this article may not be 100% correct. If there is anything wrong, please leave me a message. Thank you.

Reference articles:

[1]The Development of JavaScript Asynchronous Functions

[2]ES6 Promise

[3]ES6 Generator

[4]ES6 async

[5]JavaScript asynchronous programming

Thank you for your friends’ willingness to spend precious time reading this article. If this article gives you some help or inspiration, please don’t be stingy with your praise and Star. Your affirmation is my greatest motivation to move forward.https://github.com/YvetteLau/ …

Recommend to pay attention to my public number:

clipboard.png