Javascript asynchronous programming: Callback, Promise, Generator

Synchronous and Asynchronous

Students who know javascript must be familiar with the concepts of synchronization and asynchrony. If there are any unfamiliar students, let me give you an image example. For example, when we get up in the morning, we have to do three things: boil water, wash face and eat breakfast. Synchronization is equivalent to boiling water first, washing face after boiling water, washing face and eating breakfast after washing face. The three things are executed in sequence, one is finished and the other is finished. Asynchronous is equivalent to eating breakfast while boiling water (it is not very hygienic to eat breakfast without washing face), and washing face after breakfast. Obviously asynchronous is more efficient than synchronous, which saves a lot of waiting time. The execution time of synchronous process depends on the sum of all behaviors, while the execution time of asynchronous process only depends on the longest behavior, as shown in the following figure:

image

Since Javascript is single-threaded and can only handle one thing at a time, in the above example, this single thread is “I”, for example, I cannot wash my face and eat breakfast at the same time. Therefore, in order to improve the execution efficiency, we should try our best to keep this thread busy instead of idle, just like we don’t have to wait to boil water, we can do other things at the same time, and boil water is handled by other threads of the system (this thread is not Javascript). In the computer world, many I/O intensive operations need to wait, such as network requests, file reads and writes, etc., so asynchronous methods will be more handy in handling these operations.

Asynchronous process control

After understanding the meaning of asynchrony, let’s compare several current mainstream asynchronous process control methods and discuss the best practices of asynchronous programming.

1. Callback

Misunderstanding

First of all, callback is not necessarily related to asynchrony. The essence of callback is a function parameter of type Function. Whether the callback is executed synchronously or asynchronously depends on the function itself. Although callback is commonly used for callback of asynchronous methods, in fact, many synchronous methods can also be passed into callback, such as the most common arrayforEachMethods:

var arr = [1, 2, 3];
 arr.forEach(function (val) {
 console.log(val);
 });
 console.log('finish');
 
 //Print Results: 1, 2, 3, finish

Similarly, there are arrays ofmap,filter,reduceWaiting for many methods.

Asynchronous Callback

Common asynchronous callback aresetTimeoutCallback in:

setTimeout(function () {
console.log("time's up");
}, 1000);
console.log('finish');

//print results: finish, time's up

If we change the delay time to0, the print result will still befinish, time's upBecause asynchronous callback waits until all synchronous methods in the function are executed.

Callback Hell

In actual projects, we often encounter such problems: the next operation depends on the result of the previous operation, the previous operation depends on the previous operation, and each operation is asynchronous. . Such progressive levels will form many layers of callback nesting, resulting in poor readability and maintainability of the code, forming the so-called Callback Hell, similar to this:

step1(param, function (result1) {
 step2(result1, function (result2) {
 step3(result2, function (result3) {
 step4(result3, function (result4) {
 done(result4);
 })
 })
 })
 })

Of course, on the premise of not giving up using callback, the above code still has room for optimization, and we can reorganize it:

step1(param, callbac1);
 
 function callback1(result1){
 step2(result1, callback2);
 }
 
 function callback2(result2){
 step3(result2, callback3);
 }
 
 function callback3(result3){
 step4(result3, callback4);
 }
 
 function callback4(result4){
 done(result4);
 }

It is equivalent to converting the horizontal depth of Callback Hell into the vertical height of the code, which is closer to the top-down synchronous call we are used to. the complexity has not changed, but it just looks clearer. the disadvantage is to define additional functions and variables. Further extension of this idea leads to the following Promise.

2. Promise

Promise, translated into Chinese as “promise”, is an abstract concept in Javascript, representing something that has not been realized at present but will (or may not) be realized at a certain point in the future. For example, if you boil water in the morning, I will give you a Promise that water will boil in 10 minutes. If everything is normal, the water will boil in 10 minutes, which means that the promise has been fully filled. However, if the power is cut off midway and the water is not boiled in 10 minutes, then the promise has failed to be fullfilled. Code can be expressed as:

const boilWaterInTenMins = new Promise(function (resolve, reject) {
boiler.work(function (timeSpent) {
if (timeSpent <= 10) {
resolve();
} else {
reject();
}
});
});

Compatibility

image

If you want to improve the compatibility of the browser with Promise, you can use babel or a third-party implementation (refer togithub awesome promise)

Promise Chaining

Let’s look at how Promise has improved asynchronous process control. Based on the example of Callback Hell above, what will happen if Promise is used?

First of all, we need tostep1~doneThe function of is implemented with Promise (i.e. returns a Promise), and then a series of chain calls are sufficient:

stepOne(param)
.then((result1) => { return step2(result1) })
.then((result2) => { return step3(result2) })
.then((result3) => { return step4(result3) })
.then((result4) => { return done(result4) })
.catch(err => handleError(err));

Is it much simpler?

Async/Await

If you are not used to the call method of Promise, we can use async/await to convert it into a method closer to synchronous call:

async function main() {
try {
var result1 = await step1(param);
var result2 = await step2(result1);
var result3 = await step3(result2);
var result4 = await step4(result3);
done(result4);
} catch (err) {
handleError(err);
}
}

main();

3. Generator

Generator is a more abstract concept. To understand what a Generator is, we must first understand several other concepts: Iterable Protocol, Iterator Protocol and Iterator.

Iterable Protocol

The characteristics of the Iterable Protocol can be summarized as follows:

  1. Used to define the iterative behavior of javascript objects
  2. The object itself or the prototype chain needs to have a name ofSymbol.iteratorMethod of
  3. This method does not receive any parameters and returns an Iterator
  4. Iterable objects can be usedfor...ofTraversal

Javascript array implements the Iterable Protocol. In addition to the conventional value-taking methods, we can also use Array’sSymbol.iterator

var arr = [1, 2, 3];
 var iterator = arr[Symbol.iterator]();
 iterator.next();  // {value: 1, done: false}

We can also modify Array’s default iteration method, such as returning twice the value:

Array.prototype[Symbol.iterator] = function () {
 var nextIndex = 0;
 var self = this;
 return {
 next: function () {
 return nextIndex < self.length ?
 { value: self[nextIndex++] * 2, done: false } :
 { done: true }
 }
 };
 }
 
 for(let el of [1, 2, 3]){
 console.log(el);
 }
 //Output: 2,4,6

Iterator Protocol

The characteristics of the Iterator Protocol can be summarized as follows:

  1. A standard way of generating a sequence value (finite or infinite)
  2. Implement a next method
  3. The object returned by the next method is{value: any, done: boolean}
  4. valueTo return a value,doneFortrueWhenvalueCan be omitted
  5. doneFortrueIndicates the end of the iteration, at which pointvalueIndicates the final return value
  6. doneForfalse, you can continue the iteration to produce the next value

Iterator

Obviously, Iterator is the object that implements Iterator Protocol.

Generator

After understanding the above concepts, it is much easier to understand the Generator. The characteristics of the generator can be summarized as follows:

  1. At the same time to implement the iterable Protocol and iterator Protocol, so Genrator is both an Iterable object and an Iterator.
  2. Generator consists ofgenerator functiongenerate

The simplest generator function is as follows:

function* gen() {
 var x = yield 5 + 6;
 }
 
 var myGen = gen();  // myGen is a generator.

We can call the next method to getyieldValue of expression:

myGen.next();  // { value: 11, done: false }

But at this timexIt has not been assigned any value, so it can be imagined that javascript has been executed completely.yield 5 + 6It stopped, we need to call again in order to continue the assignment operation.nextAnd returns the resulting value to:

function* gen() {
var x = yield 5 + 6;
console.log(x);  // 11
}

var myGen = gen();
console.log(myGen.next());  // { value: 11, done: false }
console.log(myGen.next(11));  // { value: undefined, done: true }

Having said so much, what does generator have to do with asynchrony? Let’s look at asynchronous control implemented by Promise+Generator (step1 ~ done returns Promise):

genWrap(function* () {
 var result1 = yield step1(param);
 var result2 = yield step2(result1);
 var result3 = yield step3(result2);
 var result4 = yield step4(result3);
 var result5 = yield done(result4);
 });
 
 function genWrap(genFunc) {
 var generator = genFunc();
 
 function handle(yielded) {
 if (!  yielded.done) {
 yielded.value.then(function (result) {
 return handle(generator.next(result));
 });
 }
 }
 
 return handle(generator.next());
 }

Similar to async/await, this implementation also converts asynchronous method into synchronous writing. in fact, this is the implementation principle of async/await in ES7 (replace genWrap with async and yield with await).

Conclusion

I hope this article is of some help to all of you, can have a deeper understanding of javascript asynchronous programming, and can write more elegant and efficient code. Please correct any mistakes. Happy new year!