This is the 20th day of my participation in the August Text Challenge.More challenges in August

preface

As we all know, JS is a single-threaded language, and the browser uses the asynchronous non-blocking event cycle model to conduct JS task scheduling, therefore, JS asynchronous programming can be said to be in the daily front-end business development often appear, we have used in the daily development of what asynchronous programming way? There are just a few: callback functions, event listeners, Promise, Generator, async/await. Callbacks are a common way of asynchronous programming, and with the development of the ES standard Promise, Generator, and async/await came along.

What is synchronous execution

Synchronous execution means that when executing a certain piece of code, the execution of other code will be blocked until the execution result is obtained, but once the execution is complete, other code can be executed. In other words, subsequent code execution will be blocked until the result is returned.

What is asynchronous execution

Asynchronous execution is when a particular code execution after the issue of the asynchronous procedure call this code is not immediately get back the result, but in an asynchronous invocation, the engine will this task into the task queue, and then continue to execute that code, when after the task has been completed, the engine to inform the main thread, and then the main thread to perform the task of the callback function. Asynchronous execution does not block subsequent code.

Asynchronous programming in JS

On the browser side, asynchronous execution of JS tasks can help the browser process various network requests and user interactions at the same time when rendering pages. On the server side, the asynchronous non-blocking model based on event loops is inherently capable of high concurrency. So asynchronous programming is a very important part of JS programming. Let’s look at what asynchronous programming means there are, and what pain points are solved and what deficiencies there are.

The callback function

The callback function is the most basic and primitive way of JS asynchronous programming, such as event callback, setTimeout/setInterval, Ajax, etc. However, there is a very tricky problem with using the callback function, that is, the callback hell, it is nothing to write at the beginning, and after a period of time, whether you look at it or others look at it, I think the code is disgusting.

fs.readFile(A, 'utf-8'.function(err, dataA) {
    fs.readFile(B, 'utf-8'.function(err, dataB) {
        fs.readFile(C, 'utf-8'.function(err, dataC) {
            fs.readFile(D, 'utf-8'.function(err, dataD) {
                fs.readFile(E, 'utf-8'.function(err, dataE) {
                    //....
                });
            });
        });
    });
});
Copy the code

In the early days of asynchronous programming, including various library implementations, callback functions were the only way to do asynchronous programming, and the community suffered from callback hell for a long time.

Promise

Promise is a solution to asynchronous programming that is more reasonable and powerful than the traditional “callback functions and events” solution. It was first proposed and implemented by the community, and ES6 has written it into the language standard, unifying usage, providing Promise objects natively, and solving callback hell to some extent.

Promise is a finite state machine with three states (pending, fulfilled, and rejected). The name of the state can well explain why it is called a Promise. A Promise instance holds a future state of an asynchronous operation, one of the three states mentioned earlier, and provides a unified API for retrieving the execution results of an asynchronous operation.

Rewrite the code above using the then chain call provided by Promise:

function read(path) {
    return new Promise((resolve, reject) = > {
        fs.readFile(path, 'utf8'.(err, data) = > {
            if(err) reject(err);
            resolve(data);
        });
    });
}
read(A).then(dataA= > {
    return read(dataA);
}).then(dataB= > {
    return read(dataB);
}).then(dataC= > {
    return read(dataC);
}).then(dataD= > {
    return read(dataD);
}).then(dataE= > {
    console.log(dataE)
}).catch(reason= > {
    console.log(reason);
});
Copy the code

Can be seen from the above code, the callback hell for such improvement, readability do have certain ascend, advantage is asynchronous operations can be in the process of synchronous operation, to avoid the layers of nested callback function, and use the Promise to provide the catch methods can be unified treatment then call possible exception chain. However, the existing problems are also obvious. Excessive use of then chain calls does not fundamentally solve the problem of callback hell, but just change a way of writing, although the readability has improved, but it is still difficult to maintain.

For asynchronous requests that do not depend on each other, Promise provides the All method to help us process them centrally. It takes an iterator, each of which is a Promise instance, and returns an array of execution results when all instances are in fulfilled state, otherwise an exception is raised.

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

Promise.all allows multiple asynchronous operations to be executed in parallel
Promise.all([read(A), read(B), read(C)]).then(result= > {
    console.log(result);
}).catch(reason= > 
    console.log(reason)
);
Copy the code

Generator

Generator is also one of the new ES6 features and is an asynchronous programming solution. Its most important feature is the ability to surrender execution of functions, which is very similar to coroutines and can be thought of as an ES6 implementation of coroutines. Generator functions, which can be seen as containers for asynchronous tasks, are usually used with yield, and where pauses are required, the yield keyword is used. The Generator finally returns an iterator object.

Create a random number Generator function using Generator:

function * randomFrom(base) {
  while (true)
    yield arr[Math.floor(Math.random() * base];
}

const getRandom = randomFrom(10);
getRandom.next().value; // Return a random number
Copy the code

What about using generators for asynchronous operations?

Continue with the file reading code above as an example:

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

function* readGenerator(path){
  let dataA = yield readFile(path);
  let dataB = yield readFile(dataA);
  let result = yield readFile(dataB);
  yield result
}

let gen = readGenerator(A)

gen.value.then(function(dataA){
  return gen.next(dataA);
}).then(function(dataB){
  return gen.next(dataB);
}).then(function(resuly){
  console.log(result);
});
Copy the code

Generator is used more for asynchronous process control.

async/await

Async /await is an asynchronous solution proposed in ES7. It is equivalent to the syntactic sugar of Generator + executor. At present, it is the best asynchronous solution and truly realizes asynchronous code and synchronous representation.

First async is used to identify a function, and any return result of this function is wrapped as a Promise instance, while Promsie instance is parsed with await.

It is also very simple to use. Create an async identified function to indicate that an asynchronous operation will be performed inside the function. For asynchronous operations inside the function, identify with await, which can be regarded as a Promise state resolver, that is, call the THEN method of the Promise instance automatically and retrieve the result.

Improve the Generator code above with async/await:

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

const asyncReadFile = async function (path) {
  let dataA = await readFile(path);
  let dataB = await readFile(dataA);
  let result = await readFile(dataB);
  console.log(result);
};
Copy the code

As you can see, the code is written exactly like synchronous code, which is very elegant, not only readable, but also maintainable.

conclusion

The above describes the various asynchronous programming solutions in JS. Each of them has its own unique characteristics and plays a role in different scenarios. If you are not familiar with a solution, you can check out the detailed usage on MDN.