Asynchronous programming is a nightmare for new developers, but it’s important enough to learn. In fact, asynchronous calls are not so scary.

The traditional method

  • The callback function
  • Event listeners
  • Publish/subscribe
  • Promise object

The basic concept

asynchronous

The so-called asynchronous, in simple terms, is that the task is not completed consecutively at one time, other procedures are added in the middle, and the data is ready for the first stage, and then return to the calculation.

The callback function

The name of the callback function, callback, is familiar to anyone who has done front-end development. The javaScript language’s implementation of asynchronous programming is the callback function. Here’s an example:

fs.readFile('etc/passwd'.'utf-8'.function(err,data){
    if(err) throw err;
    console.log(data);
});
Copy the code

Promise

There is no problem with the callback function itself. The problem is that there are multiple nested callback functions, which is commonly known as callback hell.

    let url1 = 'http://xxx.xxx.1';
    let url2 = 'http://xxx.xxx.2';
    let url3 = 'http://xxx.xxx.3';
    $.ajax({
        url:url1,
        error:function (error) {},
        success:function (data1) {
            console.log(data1);
            $.ajax({
                url:url2,
                data:data1,
                error:function (error) {},
                success:function (data2) {
                    console.log(data2);
                    $.ajax({
                        url:url3,
                        data,
                        error:function (error) {},
                        success:function (data3) {
                            console.log(data3); }}); }}); }});Copy the code

This code itself can be implemented logically, the problem lies in the following aspects:

  • Code bloat.
  • Poor readability.
  • The coupling degree is too high and the maintainability is poor.
  • Poor code reuse.
  • Bug prone.
  • Exceptions can only be handled in callbacks.

In previous chapters, we talked about the Promis function, which is a typical solution to the function async problem. It’s a new way of writing the callback function.

Here’s an example:

function request(url,data = {}){
    return new Promise((resolve,reject) = >{
        $.ajax({
            url,
            data,
            success:function (data) {
                resolve(data);
            },
            error:function (error) { reject(error); }})}); }let url1 = 'http://xxx.xxx.1';
let url2 = 'http://xxx.xxx.2';
let url3 = 'http://xxx.xxx.3';
request(url1)
    .then((data) = >{
        console.log(data);
        return request(url2,data)
    })
    .then((data) = >{
        console.log(data);
        return request(url3,data)
    })
    .then((data) = >{
        console.log(data)
    })
    .catch((error) = >{
        console.log(error);
    });

Copy the code

Using the Promis object makes the code structure much clearer than it was above. But the problem is, at first glance, there are so many “then” s, and without a comment, it’s hard to understand what they mean.

The Generator function

Definition:

Generator functions are an asynchronous programming solution provided by ES6 with a completely different syntax from traditional functions. Executing Generator returns an traverser object. The returned iterator object traverses each state within the Generator in turn

In the previous section, we mentioned that the most important feature of the Genertor function is that it can surrender execution of the function. That is, you can pause the function.

function * gen(x) {
  var y  = yield  x + 1;
  return y;
}
var g = gen(1);
g.next(); // {value:2,done:false}
g.next(); // {value:undefined.done:true}
Copy the code

The next method that calls the gen function, which returns a pointer to the function, moves the pointer to the inner function to the first yield statement encountered.

Asynchronous encapsulation

Let’s look at a real asynchronous task using a Generator function.

  var fetch = require('node-fetch);
  
  function * gen() {
    var url = 'http://api.github.com';
    var result = yield fetch(url);
    console.log(result.bio);
  }
  
  var g = gen();
  var result = g.next();
  
  result.value.then(function(data) {
    return data.JSON();
  }).then(function(data) {
    g.next(data);
  })
  
Copy the code

The above code will be asynchronous and clear, but the process management is not convenient, and it calls the next method itself.

The Generator function automatically manages the process

Example:

function * gen() {
  / /...
}

var g = gen();
var res = g.next();
while(! res.done){console.log(res.value);
    g.next()
}
Copy the code

The above code can complete automatically if it is not suitable for asynchrony, but not if the previous step must be completed before the next step can be executed.

Thunk function

How do you automate asynchronous functions? The thunk function is our hero.

var fs = requier('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);

var gen = function* () {
  var r1 = yield readFileThunk('/etc/fstab');
  console.log(r1.toString());
  var r2 = yield readFileThunk('/etc/shells');
  console.log(r2.toString());
} 
Copy the code

The above code, after performing an asynchronous operation (the expression after yield), needs to return the execution to the Generator, which we will do manually.

var g = gen();

var r1 = g.next();
r1.value(function(err,data) {
  if(err) throw err;
  var r2 = g.next(data);
  r2.value(function(err,data) {
    if(err) throwerr; g.next(data); })})Copy the code

If you look closely at the code above, the generator is simply passing the same callback function repeatedly to the value property of the next method. How do you do this automatically?

function  run (fn) {
  var gen = fn();
  function next(err,data) {
    var result = gen.next(data);
    if(result.done) return ;
    result.value(next)
  }
  next();
}

function* g() {
  / /...
}

run(g);
Copy the code

The run function in the above code is an automatic executor of a Generator function. The internal next function is the Thunk callback. Next moves the pointer to the next step of the Generator, determines whether the Generator is finished, passes next to the Thunk function if it is not, or exits.

That’s a lot more convenient. Simply place the run() function in the function to be executed. Mom doesn’t worry about my asynchronous calls anymore.

CO

Do I write run every time? It’s very frustrating. Then try CO. The CO module is a small tool released in June 2013 by well-known programmer TJ Holowaychuk for automatic execution of Generator functions. How do you use it? Pretty simple.

var co = require('co');
co(gen);
Copy the code

Pretty easy. Furthermore, the CO function returns a Promise object that can be added by calling the then method.

co(gen).then(function() {
  console.log('At this point, the function is done. ')});Copy the code

Why does CO work automatically?

The generator function is an asynchronous execution container. Its automatic execution requires a mechanism that automatically cedes execution rights when an asynchronous operation has a result. Explain:

function * gen(x) {
  var y = yield x = 1;
  var z = yield 1+1;
  return y, z;
}
Copy the code

When the first yield is executed, the next method is called to get the value. Call next again, continue, and call this step again without having to do it manually. The CO module is essentially a module that wraps the Thunk function and the Promise object. If used, the expression following yield must be either a thunk function or a promise object. Co after 4.0 only supports Promise objects.

Have time students can look at the source of CO, also very simple, here will not do too much introduction.