JavaScript asynchronous programming: callback, promise, generator


Synchronous and asynchronous

Students who understand JavaScript must be familiar with the concepts of synchronization and asynchrony. If there are still unfamiliar students, I’ll give you an example. For example, we have to do three things after getting up in the morning: boiling water, washing face and eating breakfast. Synchronization is equivalent to boiling water first, then washing face after boiling water, then eating breakfast after washing face. Three things are executed in sequence, one is done and then dried Next thing; asynchrony is equivalent to having breakfast while boiling water (it’s not very sanitary to eat breakfast without washing your face), and washing your face after breakfast. Obviously, asynchrony is more efficient than synchronization, which saves a lot of waiting time. The execution time of synchronization 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:

JavaScript asynchronous programming: callback, promise, generator

Because JavaScript is single threaded, it can only handle one thing at the same time. In the above example, this single thread is “me”. For example, I can’t wash my face and eat breakfast at the same time. So in order to improve the execution efficiency, we should try our best to keep this thread busy instead of idle. For example, we can do other things at the same time without waiting for water to be burned, which is handled by other threads of the system (this thread does not belong to 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 mainstream asynchronous process control methods and discuss the best practice of asynchronous programming.

1. Callback


First of all, there is no inevitable relationship between callback and 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 often used for callback of asynchronous methods, there are many synchronous methods that can also be passed into callback, such as the most common array’sforEachMethod:

var arr = [1, 2, 3];
arr.forEach(function (val) {

//Print results: 1, 2, 3, finish

Similarly, there are arraysmap, filter, reduceAnd so on.

Asynchronous callback

Common asynchronous callbacks aresetTimeoutCallback in:

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

//Print result: finish, time's up

If we change the delay time to0, the result will still befinish, time's upBecause the asynchronous callback will wait for the synchronous methods in the function to finish executing.

Callback Hell

In practical projects, we often encounter the following problems: the next operation depends on the result of the previous operation, and the previous operation depends on the previous operation, and each operation is asynchronous.. In this way, when there are more layers, many layers of callback nesting will be formed, resulting in poor code readability and maintainability, 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) {

Of course, without giving up the use of callback, the above code still has optimization space. We can reorganize it as follows:

step1(param, callbac1);

function callback1(result1){
  step2(result1, callback2);

function callback2(result2){
  step3(result2, callback3);

function callback3(result3){
  step4(result3, callback4);

function callback4(result4){

It is equivalent to converting the horizontal depth of callback hells 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 looks clearer. The disadvantage is to define additional functions and variables. To further extend this idea, we have the following promise.

2. Promise

Promise is an abstract concept in JavaScript, representing something that is not implemented at present, but will (or may not) be implemented at a certain point in the future. Take an example: in the morning, I’ll give you a promise that the water will boil in ten minutes. If everything is normal, the water will indeed boil in ten minutes, which means that the project has been fully filled, but if there is a power failure in the middle and the water hasn’t been boiled in ten minutes, the project has failed to fulfill. The code can be expressed as:

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


JavaScript asynchronous programming: callback, promise, generator

If you want to improve the browser’s compatibility with promise, you can use Babel or a third-party implementation (refer to GitHub awesome promise)

Promise Chaining

Let’s see how promise improves asynchronous process control. Based on the above example of callback hell, what would happen if promise was used?

First, we need tostep1 ~ doneThe function of is implemented by promise (i.e. return a promise), and then a series of chain calls can be made:

  .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 a lot easier!


If you are not used to the call mode of promise, we can use async / await to convert it to a more synchronous call mode:

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


3. Generator

Generator is a more abstract concept. To understand what a generator is, you need to understand several other concepts: iterative protocol, iterator protocol and iterator.

Iterable Protocol

The characteristics of 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 nameSymbol.iteratorMethod
  3. This method takes no parameters and returns an iterator
  4. Iterable objects can be usedfor...ofergodic

JavaScript array implements the iterative protocol. In addition to the normal value taking method, we can also use array’sSymbol.iterator

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

We can also modify the default iteration method of array, 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]){
//Output: 2, 4, 6

Iterator Protocol

The features of iterator protocol can be summarized as follows:

  1. A standard way to generate 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. valueIs the return value,donebytrueTimevalueIt can be omitted.
  5. donebytrueIndicates the end of the iteration, whenvalueIndicates the final return value
  6. donebyfalse, you can continue iterating to produce the next value


Obviously, iterator is the object that implements iterator protocol.


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

  1. At the same time, implement iterative protocol and iterator protocol, so generator is both an Iterable object and an iterator
  2. Generator generated by generator function

The simplest generator functions are 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:; // { value: 11, done: false }

But this timexIt is not assigned. It can be imagined that JavaScript is finishedyield 5 + 6It stops. In order to continue the assignment, we need to call againnext, and return the obtained value:

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

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

After all, what does generator have to do with asynchrony? Let’s look at the 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(;

  return handle(;

Similar to async / await, this implementation also converts asynchronous methods into synchronous ones. In fact, this is the implementation principle of async / await in ES7 (replacing genwrap with async and yield with await).


I hope this article can help you to understand JavaScript asynchronous programming more deeply and write more elegant and efficient code. You are welcome to correct any mistakes. Happy New Year!