About asynchronous

“Asynchronous” simply means that a task is not completed consecutively. It can be understood that the task is artificially divided into two parts, the first part is performed, then the other part is performed, and then the second part is performed when it is ready.

For example, if you have a task that reads a file for processing, the first step of the task is to make a request to the operating system to read the file. The program then performs other tasks, waits until the operating system returns the file, and then proceeds to the second part of the task (processing the file). This discontinuous execution is called asynchrony.

Accordingly, continuous execution is called synchronization. Because the execution is continuous, no other tasks can be inserted, so the program has to wait while the operating system reads the file from the hard disk.

Simply speaking, synchronous means that everyone works in line, and asynchronous means that everyone works at the same time. If you don’t understand asynchrony and synchronization, read the JS basics article.

History of asynchrony

1. The CallBack method

CallBack refers to a function that triggers the execution of an asynchronous operation, such as:

$.get("http://api.xxxx.com/xxx",callback);
Copy the code

The callback function is fired when the request completes.

Callback can be done asynchronously, but anyone who has lived through the JQuery era has probably struggled with one or the other. For example, when a project asks for a front-end Ajax request for a back-end interface list type name, then an Ajax request for a list ID using the type name, then a list id using the id, the code looks something like this

$.ajax({
    url: "type",
    data:1,
    success: function (a) {
        $.ajax({
            url: "list",
            data:a,
            success: function (b) {
                $.ajax({
                    url: "content",
                    data:b,
                    success: function (c) {
                        console.log(c)
                    }
                })
            }
        })
    }
})
Copy the code

This is pure nested code, and if the business code is added, the code readability can be imagined. If it is developed, it is ok, but the difficulty of maintenance and modification in the later stage is enough to drive people crazy. This was the “callback hell” problem of the JQuery era.

2.Promise

The Promise object, introduced and later added to the ES6 standard to solve the “callback hell” problem, is simply a container that holds the result of some event (usually an asynchronous operation) that will end in the future. Syntactically, a Promise is an object from which to get messages for asynchronous operations. Promise provides a uniform API, and all kinds of asynchronous operations can be handled in the same way.

const promise = new Promise(function(resolve, reject) {
  // ... some code

  ifResolve (value); }else{ reject(error); }});Copy the code

The Promise constructor takes a function as an argument, resolve and reject. They are two functions that are provided by the JavaScript engine and do not need to be deployed themselves.

The resolve function changes the state of the Promise object from “unfinished” to “successful.” It will be called when the asynchronous operation succeeds and will pass the result of the asynchronous operation as an argument. The Reject function changes the state of the Promise object from “unfinished” to “failed” (i.e., from Pending to Rejected). It is called when the asynchronous operation fails and passes the error reported by the asynchronous operation as a parameter.

After the Promise instance is generated, you can use the THEN method to specify the resolved and Rejected state callback functions, respectively.

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
Copy the code

The then method can take two callback functions as arguments. The first callback is called when the Promise object’s state changes to Resolved. The second callback is called when the Promise object’s state changes to Rejected. The second function is optional and does not have to be provided. Both of these functions accept as arguments a value passed from the Promise object.

This uses Promise to solve “callback hell” problems, such as reading multiple files in succession, written as follows.

var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function (data) {
  console.log(data.toString());
})
.then(function () {
  return readFile(fileB);
})
.then(function (data) {
  console.log(data.toString());
})
.catch(function (err) {
  console.log(err);
});
Copy the code

This is much more intuitive than the CallBack method. But is there a better way to write it?

3. The function of the Generator

Genrator functions are identified by *, and the yield keyword indicates pause. Split the function into several parts and call next once to continue execution. The return result is an iterator that has a next method.

function* read() {
    console.log(1);
    let a = yield '123';
    console.log(a);
    let b = yield 9
    console.log(b);
    return b;
}
let it = read(a); console.log(it.next('213')); // {value:'123'.done:false}
console.log(it.next('100')); // {value:9,done:false}
console.log(it.next('200')); // {value:200,done:true}
console.log(it.next('200')); // {value:200,done:true}
Copy the code

The yield is followed by the value, and the yield equals the value passed in by our current call to next, and the first next pass is invalid.

When dealing with asynchrony, Generator and Promise are used together

let bluebird = require('bluebird');
let fs = require('fs');
let read= bluebird.promisify(fs.readFile); / / will bereadFile becomes an instance of the Promise objectfunction* r() {
    let content1 = yield read('./2.promise/1.txt'.'utf8');
    let content2 = yield read(content1, 'utf8');
    return content2;
}
Copy the code

That’s what we want it to look like, but it’s not enough to just write it like that, because you have to do something to get r of alpha

function co(it) {
    return new Promise(function (resolve, reject) {
        function next(d) {
            let { value, done } = it.next(d);
            if (!done) {
                value.then(function (data) { // 2,txt
                    next(data)
                }, reject)
            } else {
                resolve(value);
            }
        }
        next();
    });
}
co(r()).then(function(data) {console.log(data)// get r() execution result})Copy the code

This is obviously cumbersome, we don’t want to, we want to write like synchronous functions intuitively, and handle asynchrony in a simple way. Is there a way to do that?

4. The async – await function

The ES2017 standard introduces async functions to make asynchronous operations more convenient.

What is async function? In short, it is the syntactic sugar of Generator functions.

let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);

async function r(){
    try{
        let content1 = await read('./2.promise/100.txt'.'utf8');
        let content2 = await read(content1,'utf8');
        returncontent2; }catch(e){// If something goes wrong, catch console.log('err',e)
    }
}
Copy the code

A comparison shows that an async function simply replaces the asterisk (*) of a Generator function with async, yields with await, and nothing more.

Async returns promises

r().then(function(data){
    console.log(data);
},function(err){

})
Copy the code

So far, we are satisfied with the async-await function. Will there be a better scheme in the future? With the creativity of our broad program group, I believe there will be.

Analysis of Promise principle

The async-await function is actually just the syntactic sugar of the Generator function, and the implementation of the Generator function is also based on Promise, so we analyze the implementation principle of Promise.

Promise objects have the following states:

  • “Pending” is a pity and rejected.
  • This is a big pity.
  • Rejected: Indicates the failed operation.

After understanding the basic use of Promise above, let’s put together the framework of Promise

functionPromise(executor) {// Executor is an execution functionlet self = this;
    self.status = 'pending'; self.value = undefined; // Default success value self.reason = undefined; Self.onresolvedcallbacks = []; self.onresolvedCallbacks = []; / / storethenSuccessful callback self.onrejectedCallbacks = []; / / storethenFailed callbackfunctionResolve (value) {// Success status}functionReject (reason) {// Reject (reason) {try {executor(resolve, reject)} catch (e) {// Reject (reason) {// Reject (reason) {try {executor(resolve, reject)} catch (e) { } } Promise.prototype.then =function (onFulfilled, onRjected) {
//thenMethods})Copy the code

Then, when the successful resolve state is called, the state changes and the callback is executed:

functionResolve (value) {// Success statusif (self.status === 'pending') {
            self.status = 'resolved';
            self.value = value;
            self.onResolvedCallbacks.forEach(function(fn) { fn(); }); }}Copy the code

Reject function same thing

functionReject (reason) {// Failure statusif (self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function(fn) { fn(); }}})Copy the code

Next we complete the then function

Promise.prototype.then = function (onFulfilled, onRjected) {
    let self = this;
    letpromise2; // Return promiseif (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            
        })
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function(resolve, reject) {})} // when calledthenIt may not succeed or failif (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {

        })
    }
    return promise2;
}
Copy the code

Promises allow chained calls, so return a new Promise object, promise2

Promise.prototype.then = function(ondepressing, onRjected) {// Success and failure are not worn by default to a function ondepressing = typeof ondepressing ==='function' ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    letpromise2; // Return promiseif (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    letx = onFulfilled(self.value); ResolvePromise (promise2, x, resolve, reject); } catch (e) { reject(e); }})})}if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    letx = onRjected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }})})} // When calledthenIt may not succeed or failif (self.status === 'pending') {
        promise2 = new Promise(function(resolve, reject) {/ / at this point no resolve no reject self. OnResolvedCallbacks. Push (function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        letx = onRjected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }})}); })}return promise2;
}
Copy the code

Define a variable x as the return value of the callback function within promise2. Since the return value can be handled in many different ways, we define a resolvePromise function to handle it

The return value can be divided into

  • Promise returns itself (error loop reference)
  • Return a Promise object (calling a success or failure callback based on the Promise object)
  • Return normal value (calling the success callback passed the return value)
  • Error (call failed back to incoming error)
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { 
        return reject(new TypeError('Circular reference'} // If x is a promise,promise should be an objectletcalled; // indicates whether the call succeeded or failedif(x ! == null && (typeof x ==='object' || typeof x === 'function')) {
        try { // {then: 1}let then = x.then;
            if (typeof then= = ='function') {// success then.call(x,function (y) {
                    if (called) return
                    called = trueResolvePromise (promise2, y, resolve, reject)}function(err) {// Failedif (called) return
                    called = truereject(err); })}else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true; reject(e); }}else{// specify a common value resolve(x); // Call successful callback}}Copy the code

If the return value is an object or function and there is a then method, we consider it a Promise object and call the promise recursively until the callback returns a normal value and succeeds.

Finally, add a catch method, which is easy

Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}
Copy the code

These are the main features of Promise, with the complete code attached

functionPromise(executor) {// Executor is an execution functionlet self = this;
    self.status = 'pending'; self.value = undefined; // Default success value self.reason = undefined; Self.onresolvedcallbacks = []; self.onresolvedCallbacks = []; / / storethenSuccessful callback self.onrejectedCallbacks = []; / / storethenFailed callbackfunctionResolve (value) {// Success statusif (self.status === 'pending') {
            self.status = 'resolved';
            self.value = value;
            self.onResolvedCallbacks.forEach(function(fn) { fn(); }); }}functionReject (reason) {// Failure statusif (self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
            self.onRejectedCallbacks.forEach(function(fn) { fn(); }) } } try { executor(resolve, reject) } catch (e) { reject(e); }}function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) { 
        return reject(new TypeError('Circular reference'))}let called; 
    if(x ! == null && (typeof x ==='object' || typeof x === 'function')) {
        try { 
            let then = x.then;
            if (typeof then= = ='function') {
                then.call(x, function (y) {
                    if (called) return
                    called = true
                    resolvePromise(promise2, y, resolve, reject)
                }, function(err) {// Failedif (called) return
                    called = truereject(err); })}else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true; reject(e); }}else { 
        resolve(x); 
    }
}
Promise.prototype.then = function (onFulfilled, onRjected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value;
    }
    onRjected = typeof onRjected === 'function' ? onRjected : function (err) {
        throw err;
    }
    let self = this;
    let promise2; 
    if (self.status === 'resolved') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    letx = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }})})}if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
            setTimeout(function () {
                try {
                    letx = onRjected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }})})}if (self.status === 'pending') {
        promise2 = new Promise(function (resolve, reject) {
            self.onResolvedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            });
            self.onRejectedCallbacks.push(function () {
                setTimeout(function () {
                    try {
                        letx = onRjected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }})}); })}return promise2;
}

Promise.prototype.catch = function (callback) {
    return this.then(null, callback)
}
Copy the code