To know what is and why, first understand three concepts:
1. What is synchronization?
Synchronization is when a “call” is made, and the “call” does not return until the result is received. But once the call returns, you get the return value. In other words, the “caller” actively waits for the result of the “call”. Block subsequent code execution until this call completes.
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 made, the caller does not get the result immediately. Instead, after the “call” is issued, the “called” notifies the caller through a status, notification, or callback function. After an asynchronous call is made, it does not affect the execution of subsequent code.
3. Why asynchrony in JavaScript?
First we know that JavaScript is single-threaded (even with the addition of webworkers, JS is still single-threaded in nature). What does it mean to synchronize code? This means that it is possible to block, and when we have a task that takes a long time, if we use synchronization, it will block subsequent code execution. Async doesn’t, we don’t wait for asynchronous code to execute code after an asynchronous task.
More articles can be read: github.com/YvetteLau/B…
Now that we know the concept, we’re ready to move on to the topic of today. First of all, let’s think about: what are the main asynchronous solutions used in our daily work, and what are the advantages and disadvantages of these asynchronous solutions?
The earliest solutions to asynchrony are callback functions, such as callbacks to events, and callbacks in setInterval/setTimeout. But a very common problem with callback functions is the problem of callback hell (illustrated later);
To solve the problem of callback hell, the community came up with the Promise solution, which ES6 wrote into the language standard. Promise solves the problem of callback hell to some extent, but promises also have some problems. For example, errors cannot be tried and caught. Moreover, using the chain invocation of Promise does not solve the problem of callback hell fundamentally, but just changes the way it is written.
The Generator function is introduced in ES6, which is an asynchronous programming solution. The Generator function is the implementation of coroutines in ES6. The biggest feature of the Generator function is that it can hand over the execution right of functions. Use the yield statement. However, Generator is more complex to use.
ES7 also proposed a new asynchronous solution :async/await, async is the syntactic sugar of Generator functions, async/await makes asynchronous code look like synchronous code, the goal of asynchronous programming development is to make asynchronous logic code look like synchronous code.
Callback functions –> Promise –> Generator –> async/await.
1.callback
//node reads the file
fs.readFile(xxx, 'utf-8'.function(err, data) {
//code
});
Copy the code
Scenarios for using callback functions (including but not limited to):
- Event callback
- Node API
- A callback function in setTimeout/setInterval
- An ajax request
Advantages of the callback function: simplicity.
Disadvantages of the callback function:
Asynchronous callback nesting makes code difficult to maintain, and makes it difficult to uniformly handle errors, try catch and callback hell (such as reading A text, then reading B from A text, then reading C from 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) {
//....
});
});
});
});
Copy the code
2.Promise
Promises solve the problem of callback hell to some extent. Promises were first proposed and implemented by the community, and ES6 has written them into the language standard, unifying usage, and providing Promise objects natively.
So let’s take A look at how Promise solves the callback hell problem, again using readFile as an example (read text A, read B from text A, read C from B).
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);
});
Copy the code
Advantages of Promises:
- Once the state changes, it never changes again, and you can get this result at any time
- Asynchronous operations can be expressed as a flow of synchronous operations, avoiding layers of nested callback functions
Disadvantages:
- Unable to cancel Promise
- When you are in a pending state, you cannot tell what stage you are currently in
- Errors cannot be
try catch
Assume that there is A requirement to read the contents of A,B, and C files, and output the final result after successful reading. Before promises, we could usually implement them using a publish-subscribe model:
let pubsub = {
arry: [],
emit() {
this.arry.forEach(fn= > fn());
},
on(fn) {
this.arry.push(fn); }}let data = [];
pubsub.on((a)= > {
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();
});
Copy the code
Promise gives us the promise.all method, which we can implement for this requirement using promise.all.
/** * wrap fs.readfile as the Promise interface */
function read(url) {
return new Promise((resolve, reject) = > {
fs.readFile(url, 'utf8', (err, data) => {
if(err) reject(err);
resolve(data);
});
});
}
/** * Use Promise ** Multiple asynchronous parallel executions can be implemented through promise.all, the problem of getting the final result at the same time */
Promise.all([
read(A),
read(B),
read(C)
]).then(data= > {
console.log(data);
}).catch(err= > console.log(err));
Copy the code
Executable code: github.com/YvetteLau/B…
3.Generator
The Generator function is an asynchronous programming solution provided by ES6. The Generator function is a encapsulated asynchronous task, or a container for asynchronous tasks. Where asynchronous operations need to be paused, use the yield statement.
Generator functions are usually used in conjunction with yield or Promise. Generator functions return iterators. For those unfamiliar with generators and iterators, please take a refresher on the basics. Let’s look at a 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();
// The next method can take an argument that is treated as the return value of the previous yield expression
t.next(1); // The first time the next function was called, the argument passed was invalid
t.next(2); / / a output 2;
t.next(3); / / b output 3;
t.next(4); / / c output 4;
t.next(5); / / d output 5;
Copy the code
To give you a better idea of how this code works, I’ve drawn a diagram for each next method call:
Take the above readFile as an example and use Generator + CO library to implement:
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
});
Copy the code
I don’t need to mention the drawbacks of generators, except that I don’t use generators directly to solve asynchronous problems (although I’m not very skilled)
How to achieve this without using the CO library? Can you write a simple my_co to understand the implementation principle of async/await? Please stamp: github.com/YvetteLau/B…
PS: If you are not familiar with Generator/ Yield, it is recommended to read the ES6 documentation.
4.async/await
The concept of async/await was introduced in ES7. Async is a syntactic sugar that is implemented by wrapping Generator functions and auto-actuators (CO) in a single function.
Async /await has the advantage of clean code and handling callback hell without writing as many then chains as Promise. And errors can be try caught.
As an example of the above readFile (first read A text content, then read B from A text content, then read C from B), async/await is used to implement:
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
});
Copy the code
Use async/await to achieve 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);
});
Copy the code
So the asynchronous history of JS can be thought of as coming from callback -> promise -> generator -> async/await. Async /await makes asynchronous code look like synchronous code. The goal of asynchronous programming development is to make asynchronous logic code look like synchronous code.
Due to my limited level, the content of the article may not be 100% correct, if there is a wrong place, please give me a message, thank you.
Reference article:
[1] Explains the evolution of asynchronous JavaScript functions
[2] ES6 Promise
[3] ES6 Generator
[4] ES6 async
[5] Asynchronous programming in JavaScript
Thank you for your precious time to read this article. If this article gives you some help or inspiration, please do not spare your praise and Star. Your praise is definitely the biggest motivation for me to move forward. Github.com/YvetteLau/B…