JS asynchronous

Time:2021-7-28

JS asynchronous

1、 Causes of asynchrony

JS is single threaded and can only do one thing at a time. Therefore, all things are waiting to be executed like queuing. However, if one of the things takes a long time in the queue, the next event will be waiting all the time. For example, when we open a web page, the interface request time is 5 seconds. After waiting for 5 seconds, the page will be left blank for a long time, affecting the user experience. Therefore, JS designed asynchronous mode under the defect of synchronization mechanism

for instance

  • 1. Whether you cut vegetables or cook in an electric cooker first, you have to wait until one thing is finished before you can proceed to the next item. This is an example of synchronization
  • 2. When cutting vegetables, you can also cook in an electric rice cooker (you don’t have to wait until you finish cutting vegetables). This is an asynchronous example. Here’s a code demonstration of asynchrony
function a() {
  console.log("a");
  setTimeout(() => {
    console.log("a1");
  }, 1000);
}
function b() {
  console.log("b");
}
a();
b();
//Print results a, B, A1
//Here B, you don't have to wait for A1 to print after one second. You can see it in combination with the event loop mechanism of JS for more details
//The event loop mechanism article of JS is attached: https://segmentfault.com/a/1190000039866826

2、 Asynchronous solution

1. Callback function

The concept of callback function: it is passed into another function as an argument and called in the external function. The function used to complete some tasks is called callback function

function b(value) {
  var bb = "everyone";
  console.log(value + bb);
}
function a(callback) {
  let value = "hello ";
  setTimeout(() => {
    callback(value);
  }, 1000);
}
a(b);
//This is an asynchronous callback that will not execute the B function until 1 second later

Disadvantages of callback function: it is easy to write callback hell

  • Nested functions are coupled. Once they are changed, they will affect the whole body
  • When there are many nested functions, it is difficult to handle errors (try catch cannot be used, and return cannot be used directly)
let a = setTimeout(function a() {
  var name1 = "hello ";
  try {
    setTimeout(function b() {
      var name2 = "everyone " + name1; // If the B function and Name2 here are changed, the printed value of the following C function will be affected and affect the whole body
      setTimeout(function c() {
        var name3 = "yeah!";
        return name3; //  Return here is only for demonstration. Return name3 cannot be received
        console.log(name2 + name3);
      }, 1000);
    }, 1000);
  } catch (e) {
    Console.log (E, "cannot catch errors")// This try catch is just for demonstration. No error can be caught here because try catch cannot catch asynchronous errors. When a try is executed, the code in the try is placed in the asynchronous task queue. There is no try to find any content, so the catch is not printed. When the synchronous code is executed to execute the asynchronous task queue, an error will be reported.
  }
}, 1000);
console.log(a); // A here is not name3, so you can't return directly

It is precisely because the callback function is defective that promise was born in es2015

2.Promise

Promise concept: promise object is used to represent the final completion (or failure) of an asynchronous operation and its result value

Handwritten promise (will expand around the method in the figure below)

JS asynchronous

Promise usage

JS asynchronous

Promise is always in the pending state. The state will change only after the resolve or reject method is called. At this time, the corresponding callback function will be executed according to the success or failure state
new Promise((resolve, reject) => {
  setTimeout(() => {
    Resolve ("success");
    //Reject ("failed");
  }, 1000);
}).then(
  (value) => {
    Console.log (value, "this is the value returned successfully");
  },
  (reason) => {
    Console.log (reason, "this is the reason for failure");
  }
);

The following is the implementation of the core logic of the handwritten promise class

  • Promise is a class (or constructor, only class here). When executing this class, an executor needs to be passed in, and the executor will execute immediately
  • There are two parameters in the executor, one called resolve and the other called reject
  • Since resolve and reject are executable, they are both functions

    Class promise {// class
    Constructor (executor) {// constructor
      //Success
      let resolve = () => { };
      //Fail
      let reject = () => { };
      //An error may be reported when executing the executor. The error is captured and passed to reject
      try {
        executor(resolve, reject);
      } catch (err) {
        reject(err);
      }
    }
    }
  • Promise has three statuses: successfully completed, failed rejected and waiting.

    • Pending—>Fulfilled
    • Pending—>Rejected
    • Once the status is determined, it cannot be changed
  • Resolve and reject are used to change the state

    • resolve()—>Fulfilled
    • reject()—>Rejected
const PENDING = "pending"; // wait for
const FULFILLED = "fulfilled"; //  success
const REJECTED = "rejected"; //  fail
class Promise {
  constructor(exector) {
    const resolve = (value) => {
    //Once the status is determined, it cannot be changed
      if (this.status !== PENDING) return;
      //Change the status and the status becomes successful
      this.status = FUlFILLED;
       //Keep the value of success
      this.value = value;
    };
   const reject = (reason) => {
      if (this.status !== PENDING) return;
      this.status = REJECTED;
      this.reason = reason;
    };
    try {
      exector(resolve, reject); // Execute now, resolve
    } catch (e) {
      reject(e);
    }
  }
  status = PENDING;
  value = undefined;
  reason = undefined;
  fulfilledCallBack = undefined;
  rejectedCallBack = undefined;
}
  • What the then method does internally is to judge the state. If the state is successful, the successful callback function will be called; If the status fails, the failed callback function is called.
class Promise{
  constructor(executor){...}
  status = PENDING;
  value = undefined;
  reason = undefined;
  //The then method has two parameters onfulfilled onrejected
  then(successCallback, failCallback) {
    //The status is completed, successcallback, and the value of success is passed in
    if (this.status === FULFILLED) {
    //Use try and catch to catch the error thrown in the then method and pass it into reject
      try {
        successCallback(this.value);
      } catch (error) {
        reject(error);
      }
    }
    //The status is rejected, the failcallback is executed, and the reason for the incoming failure
    if (this.status === REJECTED) {
      try {
        failCallback(this.reason);
      } catch (error) {
        this.reject(error);
      }
    }
}
  • Now you can basically implement simple synchronization code, but when resolve is executed in setTimeout, then the status is still pending, so you should store the successcallback and failcallback, and call it once resolve or reject
  • Now you can basically implement simple synchronization code, but the same promsie calls then multiple times. If resolve or reject is written and executed in settomeout, only the last then method will be executed, because failcallback or failcallback is saved as a variable and should be saved as an array

JS asynchronous

const PENDING = "pending"; 
const FULFILLED = "fulfilled"; 
const REJECTED = "rejected"; 
class MyPromise {
constructor(exector) {
    const resolve = (value) => {
    //Once the status is determined, it cannot be changed
      if (this.status !== PENDING) return;
      //Change the status and the status becomes successful
      this.status = FUlFILLED;
       //Keep the value of success
      this.value = value;
      // if (this.fulfilledCallBack) this.fulfilledCallBack(value);
      if (this.fulfilledCallBack) {
        this.fulfilledCallBack.map((item) => {
          item(value);
          return;
        });
      }
    };
   const reject = (reason) => {
      if (this.status !== PENDING) return;
      this.status = REJECTED;
      this.reason = reason;
      // if (this.rejectedCallBack) this.rejectedCallBack(reason);
      if (this.rejectedCallBack) {
        this.rejectedCallBack.map((item) => {
          item(reason);
          return;
        });
      }
    };
    try {
      exector(resolve, reject); 
    } catch (e) {
      reject(e);
    }
  }
  status = PENDING;
  value = undefined;
  reason = undefined;
  fulfilledCallBack = [];
  rejectedCallBack = [];
  then(successCallback, failCallback) {
    if (this.status === FULFILLED) {
      successCallback(this.value);
    } else if (this.status === REJECTED) {
      failCallback(this.reason); 
    } else {  
      // this.fulfilledCallBack=successCallback;
      // this.rejectedCallBack=failCallback;
       //This is the pending status
      this.fulfilledCallBack.push(successCallback);  
      //Use an array to store multiple successful or failed callbacks. Wait until resolve or reject, and execute them in turn
      this.rejectedCallBack.push(failCallback);
    }
  }
}
  • The parameters of the then method are optional

    • Then () is equivalent to —- > then (value = > value, (reason) = > {throw reason})

JS asynchronous

const PENDING = "pending"; 
const FULFILLED = "fulfilled"; 
const REJECTED = "rejected"; 
class MyPromise {
  constructor(executor){...}
  status = PENDING;
  value = undefined;
  reason = undefined;
  fulfilledCallBack = [];
  rejectedCallBack = [];
  then(successCallback, failCallback) {
  //Here is the corresponding if then has no parameters
  //Then () is equivalent to ---- > then (value = > value, (reason) = > {throw reason})
  const successCallback = successCallback?successCallback:(value)=>value
  const failCallback = failCallback?failCallback:(error)=>{throw error}
  
    if (this.status === FULFILLED) {
      successCallback(this.value);
    } else if (this.status === REJECTED) {
      failCallback(this.reason); 
    } else {  
      this.fulfilledCallBack.push(successCallback);  
      this.rejectedCallBack.push(failCallback);
    }
  }
  • Chain call of then method

    • Then () also returns a promise object
    • Pass the returned value of the previous then method to the next then.
    • There are two types of results returned by then (). One is the promise object. At this time, resolve (value) or reject (reason) in the success or failure callback of promise
    • One is the normal value of a non promise object, which directly returns resolve (value)
  • The then method cannot return itself and reports a “circular reference” error

JS asynchronous

const PENDING = "pending"; 
const FULFILLED = "fulfilled"; 
const REJECTED = "rejected"; 
class MyPromise {
  constructor(executor){...}
  status = PENDING;
  value = undefined;
  reason = undefined;
  fulfilledCallBack = [];
  rejectedCallBack = [];
  then(successCallback, failCallback) {
    successCallback = successCallback ? successCallback : (value) => value;
    failCallback = failCallback
      ? failCallback
      : (reason) => {
          throw reason;
        };
    let p = new MyPromise((resolve, reject) => {
      //Then is called in a chain, 1. So the promsie object is returned
      //2. Pass down the value returned by the previous then (resolve (value) or reject (reason)). If it is promise, judge the status of paromsie, success or failure, and call resolve or reject to inform the next then method of the status
      if (this.status === FULFILLED) {
        setTimeout(() => {
        //The timer here is to get P, because P is not obtained until new promise() is executed. Now, it cannot be obtained during execution. Use the timer to become asynchronous and get P
          try {
           //Try catch is used here to catch the errors in then () and pass them to reject
            const x = successCallback(this.value);
            getValueType(p, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      } else if (this.status === REJECTED) {
      //Callback similar to success
        setTimeout(() => {      
          try {
            const x = failCallback(this.reason);
            getValueType(p, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        });
      } else {
          this.fulfilledCallBack.push((value) =>
          setTimeout(() => {
            try {
              getValueType(p, successCallback(value), resolve, reject);
            } catch (e) {
              reject(e);
            }
          })
        );
        this.rejectedCallBack.push((reason) =>
          setTimeout(() => {
            try {
              getValueType(p, failCallback(reason), resolve, reject);
            } catch (e) {
              reject(e);
            }
          })
        );
      }
    });
    return p;
  }
}
//Extract a method separately to judge the return of then;
//If the returned value is a normal value, directly resolve (value);
//If a promise object is returned, first execute the then method to determine the status, resolve (value) in the successful callback and reject (reason) in the failed callback;

const getValueType = (p, x, resolve, reject) => {
//Judge whether X and P are equal. If they are equal, they call themselves and report a "circular reference" error
  if (p === x) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")
    );
  }
  //Use instanceof to judge whether x is an instance object of mypromise
  if (x instanceof MyPromise) {
    return x.then(resolve, reject); // Abbreviation
  } else {
    return resolve(x);
  }
};
  • Catch catch error

    • A promise error was returned
    • Accept a callback function to catch errors
    ⚠️ Note: here, the chain then failure callback and catch can capture errors, but then failure callback can only capture errors in the current promises, not errors in the current then success callback function. But catch can catch all errors, so catch errors when calling in a chain
class MyPromise {
  constructor(executor){...}
  status = PENDING;
  value = undefined;
  reason = undefined;
  fulfilledCallBack = [];
  rejectedCallBack = [];
  then(){...}
  catch(failCallBack) { 
  //Equivalent to then (), the first parameter is undefined
    return this.then(undefined, failCallBack); 
  }
}
const getValueType =()=>{...}
  • Promise. All(), promise. Race method

    • Promise. All () allows us to get the results of asynchronous code execution in the order of asynchronous code calls
    • Promise. Race () asynchronous code calls which result gets faster and returns that result, regardless of whether the result itself is in the success state or the failure state
    class MyPromise {
    constructor(executor){...}
    status = PENDING;
    value = undefined;
    reason = undefined;
    fulfilledCallBack = [];
    rejectedCallBack = [];
    then(){...}
    catch() {...}
    static all(array) {
      let result = [];
      let key = 0;
      return new MyPromise((resolve, reject) => {
        function addData(i, value) {   
          result[i] = value;
          key++;
          //Judge key = = = array.length. At this time, all asynchronous promises are executed
          if (key === array.length) {   
          //The reason why resolve is not executed under for is that if there is an asynchronous promise object in the array, it must wait until it returns
            resolve(result); 
          }
        }
        for (let i = 0; i < array.length; i++) {
          if (array[i] instanceof MyPromise) { 
          //If it is a promise object, push the successful value into the array and reject the failed value
            array[i].then(
              (value) => addData(i, value),
              (reason) => reject(reason)
            );
          } else {
            addData(i, array[i]); // If it is a normal value, push it into the array
          }
        }
        //   resolve(result); //  If you execute resolve here, you won't wait for the promise object to finish executing
      });
    }
    static race(array) {
      return new MyPromise((resolve, reject) => {
        for (let i = 0; i < array.length; i++) {
          if (array[i] instanceof MyPromise) {
          //The first resolve is returned if successful, and the first reject is returned if failed
            array[i].then(resolve, reject); 
          } else {
          //The first resolve will be returned because the state has changed and the subsequent resolve will not be executed
            resolve(array[i]); 
          }
        }
      });
    }
    }
    const getValueType =()=>{...}
  • Promise. Resolve(), promise. Reject() method

    • If the promise object is passed in, it will be returned intact
    • If a normal value is passed in, it is wrapped into a promise object and returned
    class MyPromise {
    constructor(executor){...}
    status = PENDING;
    value = undefined;
    reason = undefined;
    fulfilledCallBack = [];
    rejectedCallBack = [];
    then(){...}
    catch(){...}
    static resolve(value) {
      if (value instanceof MyPromise) return value;
      return new MyPromise((resolve, reject) => {
        resolve(value);
      });
    }
    static reject(value) {
      if (value instanceof MyPromise) return value;
      return new MyPromise((resolve, reject) => {
        reject(value);
      });
    }
    }
    const getValueType =()=>{...}
  • Finally, it will be executed regardless of failure or success

    • Finally, whether the result is success or failure, it will be executed once
    • Finally, you can chain call the then method to get the final result returned by the current promise
    • ⚠️ Note: here, the finally callback function may return a promsie object, and the subsequent then () will be executed after finally execution. Here, we need to use the resolve method
class MyPromise {
  constructor(executor){...}
  status = PENDING;
  value = undefined;
  reason = undefined;
  fulfilledCallBack = [];
  rejectedCallBack = [];
  then(){...}
  catch(){...}
  static resolve(value){...}
  finally(callBack) {
    //1. First of all, you need to know the status. Then you can call then () to know the status
    return this.then(
      //2. Return goes out because you want to return a promsie to chain call
      (value) => {
        // callBack();
        // return value; //  3. Return the value so that the next then () can receive it
        return MyPromise.resolve(callBack()).then(() => value); //  Here is the finally callback function, which may return a promsie object. After the execution of the promsie object, execute the following then method. Therefore, whether it returns a normal value or a promsie object, directly call resolve() to turn it into a promise object
      },
      (reason) => {
        // callBack();
        // throw reason;
        return MyPromise.resolve(callBack()).then(() => {
          throw reason;
        });
      }
    );
  }
}
const getValueType =()=>{...}

So far, the basic functions of promise have been roughly realized. In fact, promise.then is similar to the idea of callback function, but it distinguishes successful and failed callback functions, and the chain call of then method solves the nesting hell of callback functions and flattens them. However, the readability of our traditional synchronization code is still not achieved, so the generator appears.

3.Generator

  • The generator function has two features, one(usually written after function, function), a yield.
  • The generator function returns a generator object. The function body of this function will not execute until we call. Next.
  • You can take advantage of the yield pause generator function. Use the generator function to achieve a better asynchronous experience.
function* fun1() {
  console.log("start");
  try{
      //Inside the function, you can return a value through yield at any time
      //The yield keyword will not end the execution of the generator function like return, but just pause the execution of the generator function. Until the outside world executes the yield method again, continue to execute from the yield position
      let aa = yield "foo";   
      //Here "bar" will be used as the return value of yield "foo", that is, AA = "foo"
  }catch(error){
      console.log(error,"error")
  }
}
const generator = fun1();
//Calling the fun1 function does not immediately execute the function, but gets a generator object
console.log(generator,"generator") 
const result = generator.next();
//The function body of this function will not start executing until we manually call the next method of this object
//In the object {value: "foo", done: false} returned by the next method, get the value returned by yeild
//The done attribute in the object returned by the next method indicates whether the generator has been fully executed
console.log(result,"result")
//If the parameter is passed in by calling next, the passed parameter will be used as the return value of the yield statement
 generator.next("bar") 
//The throw method also allows the function to continue to execute downward, but an exception is thrown inside the generator function, which needs to be caught by try and catch
Generator.throw ("false alarm")

Let’s look at an example

//ajax(" http://www.baidu.com ") function here is a pseudo code, assuming that a promise object is returned
function ajax(){
  return new Promise(...)
}
function* main(){
  const data1 = yeild ajax("http://www.baidu1.com")  
  console.log(data1)
  const data2 = yeild ajax("http://www.baidu2.com")  
  console.log(data2)
}
const g = main()
Const result = g.next() // here result is a generator object, and the value value is yeild Ajax(“ http://www.baidu.com ") returned promise object
result.value.then((value)=>{
  Const result1 = g.next (value) // pass the value returned after promise execution to data1
  if(result1.done) return
  result1.value.then((value)=>{
      Let result2 = g.next (value) // pass the returned value after promise execution to data2
      if(result3.done) return
       //... so back and forth, you can use recursion
  })
})

Therefore, the result can be obtained in the generator function body, so that the asynchronous problem can be solved by writing synchronously

Encapsulates a generator function executor(https://github.com/tj/co

function co(generator){
    const g = generator()
    function handleResult(result){
        if(result.done) return
        result.value.then((data)=>{
            handleResult(g.next(data))
        },(error)=>{
            g. Throw (error) // try catch externally
        })
    }
    handleResult(g.next())
}
//Call CO (main)

async await

  • Async await is the syntax sugar of generator. Compared with generator, async awaitunwantedWhen cooperating with an actuator like Co, the internal execution process is exactly the same as that of the generator
  • The async function returns a promise object
  • Await can only be used in async functions

    function* fun() {
    let a = yield setTimeout(() => console.log("111", 0));
    let b = yield setTimeout(() => console.log("222", 0));
    }
    /**
     *Equivalent to
     */
    async function fun() {
    let a = await setTimeout(() => console.log("111", 0));
    let b = await setTimeout(() => console.log("222", 0));
    }

    So now we will use async await in most cases, which implements asynchrony in an almost synchronous way

Recommended Today

Implementation example of go operation etcd

etcdIt is an open-source, distributed key value pair data storage system, which provides shared configuration, service registration and discovery. This paper mainly introduces the installation and use of etcd. Etcdetcd introduction etcdIt is an open source and highly available distributed key value storage system developed with go language, which can be used to configure sharing […]