First, the reasons for asynchronous emergence

Js is single-threaded and can only do one thing at a time, so everything is queued up waiting to be executed, but if one thing takes a long time in the queue, the next event is waiting forever. For example, when we open a web page and the interface request time is 5 seconds, the page will wait for 5 seconds and then render the page, which will leave the page blank for a long time, affecting the user experience. Therefore, JS design asynchronous mode under the defect of synchronous mechanism

For example

  • 1. Whether you chop vegetables first or use a rice cooker to cook, wait until one task is finished before moving on to the next. This is an example of synchronization
  • 2. You can also use a rice cooker while chopping (without waiting for you to finish chopping) as an example of asynchrony. The following code demonstrates asynchrony
function a() {
  console.log("a");
  setTimeout(() = > {
    console.log("a1");
  }, 1000);
}
function b() {
  console.log("b");
}
a();
b();
// Print results a,b,a1
// B does not need to wait a second for a1 to finish printing before printing
/ / submit event loop mechanism of js: https://segmentfault.com/a/1190000039866826

Copy the code

2. Asynchronous solutions

1. Callback function

The concept of a callback function: a function that is passed as an argument to another function and called from within that function to accomplish something is called a 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 and b is executed after 1 second
Copy the code

Disadvantages of Callback functions: Easy to write Callback hell

  • Nested functions are coupled and can be affected by changes
  • Multiple nested functions make it hard to handle errors (no try catch, no return).
let a = setTimeout(function a() {
  var name1 = "hello ";
  try {
    setTimeout(function b() {
      var name2 = "everyone " + name1; // If the function b, name2, is changed, the value of the function c below will be affected
      setTimeout(function c() {
        var name3 = "yeah!";
        return name3; // In this case return is just for demonstration, return name3 cannot be received
        console.log(name2 + name3);
      }, 1000);
    }, 1000);
  } catch (e) {
    console.log(e, "Cannot catch errors");  // This is just for demonstration purposes. We can't catch an error here because a try catch can't catch an asynchronous error. When a try is executed, the code inside the try is placed in an asynchronous task queue. An error is reported when the asynchronous task queue is executed after the synchronous code has finished executing.}},1000);
console.log(a); // a is not name3, so it cannot return directly
Copy the code

It was the flawed callback function that gave birth to PROMISE in ES2015

2.Promise

Promise concept: Promise objects are used to represent the final completion (or failure) of an asynchronous operation and its resulting value

Handwritten promises (will be structured around the approach shown below)

The use of the Promise

The Promise state is always pending, and only changes when either resolve or Reject is called, and the corresponding callback function is executed based on the success or failure state
new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve("Success");
    // reject(" reject ");
  }, 1000);
}).then(
  (value) = > {
    console.log(value, "This is the value returned on success.");
  },
  (reason) = > {
    console.log(reason, "This is the cause of failure."); });Copy the code

The following is an implementation of the core logic of the handwritten Promise class

  • A Promise is a class (or constructor, class only) that needs to be executed by passing an executor, which will execute immediately
  • Executor takes two arguments, resolve and reject
  • Resolve and Reject are functions because they are executable
class Promise {/ / class
  constructor(executor) { // constructor
    / / success
    let resolve = () = >{};/ / fail
    let reject = () = >{};// The executor may report an error, which is passed in reject
    try {
      executor(resolve, reject);
    } catch(err) { reject(err); }}}Copy the code
  • Promise has three states, which are Fulfilled, failed, and Pending.
    • Pending—>Fulfilled
    • Pending—>Rejected
    • Once the state is determined, it cannot be changed
  • Resolve and Reject are used to change 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 to success
      this.status = FUlFILLED;
       // Keep the success value
      this.value = value;
    };
   const reject = (reason) = > {
      if (this.status ! == PENDING)return;
      this.status = REJECTED;
      this.reason = reason;
    };
    try {
      exector(resolve, reject); // Execute resolve immediately
    } catch (e) {
      reject(e);
    }
  }
  status = PENDING;
  value = undefined;
  reason = undefined;
  fulfilledCallBack = undefined;
  rejectedCallBack = undefined;
}

Copy the code
  • What the then method does internally is determine the state, and if the state is success, it calls the success callback; When the state fails, the failed callback function is called.
class Promise{
  constructor(executor){... } status = PENDING; value =undefined;
  reason = undefined;
  // Then method has two parameters ondepressing onRejected
  then(successCallback, failCallback) {
    // The state is fulfilled, successCallback, and the value is passed in
    if (this.status === FULFILLED) {
    // Use a try,catch, reject method to catch any errors thrown in the then method
      try {
        successCallback(this.value);
      } catch(error) { reject(error); }}// If the status is Rejected, failCallback is executed and the failure cause is passed in
    if (this.status === REJECTED) {
      try {
        failCallback(this.reason);
      } catch (error) {
        this.reject(error); }}}Copy the code
  • Now you can implement simple synchronization code, but if resolve is executed in setTomeout, then status is still pending, so store successCallback, failCallback, It is called once resolve or Reject
  • It is now possible to implement simple synchronization code, but if the promsie calls then multiple times, resolve or reject in setTomeout will execute only the last THEN method. Because failCallback or failCallback is stored as a variable, it should be stored as an array

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 to success
      this.status = FUlFILLED;
       // Keep the success value
      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 a pending state
      this.fulfilledCallBack.push(successCallback);  
      // Store multiple success or failure callbacks in an array and wait until resolve or reject is executed
      this.rejectedCallBack.push(failCallback); }}}Copy the code
  • Arguments to the THEN method are optional
    • Then () is equivalent to — — — – > then (value = > value, (reason) = > {throw reason})

const PENDING = "pending"; 
const FULFILLED = "fulfilled"; 
const REJECTED = "rejected"; 
class MyPromise {
  constructor(executor){... } status = PENDING; value =undefined;
  reason = undefined;
  fulfilledCallBack = [];
  rejectedCallBack = [];
  then(successCallback, failCallback) {
  // If then does not pass an argument, then
  / / then () is equivalent to -- -- -- - > then (value = > value, (reason) = > {throw reason})
  constsuccessCallback = successCallback? successCallback:(value) = >value
  constfailCallback = 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); }}Copy the code
  • Chain calls to the then method
    • Then () also returns a Promise object
    • Passes the value returned by the previous THEN method to the next THEN.
    • Then () returns a promise object, resolve(value) or reject(reason) in the promise success or failure callback.
    • One is a plain non-Promise object that returns resolve(value)
  • The then method cannot return itself, reporting a circular reference error

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 chained, 1. So return the promsie object
      Resolve (value) or reject(reason); // 2. Pass the value returned by the previous THEN (resolve(value) or reject(reason)). If it is a promise, determine whether paromsie succeeds or fails, and call resolve or reject to inform the next THEN
      if (this.status === FULFILLED) {
        setTimeout(() = > {
        // The timer is used to get p, because p is not available until the new promise() is executed
          try {
           // We use a try catch to pass the error in then() to a reject
            const x = successCallback(this.value);
            getValueType(p, x, resolve, reject);
          } catch(e) { reject(e); }}); }else if (this.status === REJECTED) {
      // similar to successful callback
        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) } ) ); }});returnp; }}// Select a single method, then return;
Resolve (value);
Resolve (value) if a promise object is returned, reject(reason) if a promise object is returned.

const getValueType = (p, x, resolve, reject) = > {
// check whether x and p are equal
  if (p === x) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")); }// Use instanceof to determine if x is an instance object of MyPromise
  if (x instanceof MyPromise) {
    return x.then(resolve, reject); / / short
  } else {
    returnresolve(x); }};Copy the code
  • Catch error
    • Return a promise error
    • Accept a callback function to catch the error
⚠️ note: the failure callback of a chained THEN can catch errors, but the failure callback of a THEN can only catch errors in promises, not in the successful callback of a then. But catch catches all errors, so when you do chain calls, catch them
class MyPromise {
  constructor(executor){... } status = PENDING; value =undefined;
  reason = undefined;
  fulfilledCallBack = [];
  rejectedCallBack = [];
  then(){... }catch(failCallBack) { 
  The first argument to then() is undefined
    return this.then(undefined, failCallBack); }}const getValueType =() = >{... }Copy the code
  • Promise. All (), Promise. Race method
    • Promise.all() allows us to get the results of asynchronous code execution in the order in which it is called
    • Promise.race() the asynchronous code returns whichever result it calls faster, regardless of whether the result itself is a success or 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++;
        // Determine key === array.length, at which point all asynchronous promises are completed
        if (key === array.length) {   
        Resolv (resolve, promise, promise, promise, promise, promise, promise, promise)resolve(result); }}for (let i = 0; i < array.length; i++) {
        if (array[i] instanceof MyPromise) { 
        // Promise objects push successful values into the array, reject failed values out
          array[i].then(
            (value) = > addData(i, value),
            (reason) = > reject(reason)
          );
        } else {
          addData(i, array[i]); // Push an array if it is a normal value}}// resolve(result); // If you execute resolve here, you will not wait for the promise object to complete
    });
  }
  static race(array) {
    return new MyPromise((resolve, reject) = > {
      for (let i = 0; i < array.length; i++) {
        if (array[i] instanceof MyPromise) {
        Resolve is returned on success, reject is returned on failure
          array[i].then(resolve, reject); 
        } else {
        // The first resolve will be returned because the state has changed and resolve will not be executed laterresolve(array[i]); }}}); }}const getValueType =() = >{... }Copy the code
  • Promise. Resolve (), Promise. Reject () method
    • If a Promise object is passed in, return it exactly as it was
    • If a normal value is passed in, return it wrapped as a Promise object
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 =() = >{... }Copy the code
  • Finally executes whether it succeeds or fails
    • Finally is executed once regardless of whether the result is success or failure
    • Finally is followed by a chain call to the then method to retrieve the final result of the current promise
    • ⚠️ Note: the finally callback may return a promsie object, and then() will wait until finally completes, using the resolve method
class MyPromise {
  constructor(executor){... } status = PENDING; value =undefined;
  reason = undefined;
  fulfilledCallBack = [];
  rejectedCallBack = [];
  then(){... }catch() {... }static resolve(value){... }finally(callBack) {
    // 1. Call then() to know the state
    return this.then(
      // 2. Return because a promsie is to be returned to the chain call
      (value) = > {
        // callBack();
        // return value; // 3. Return the value for the next then() to receive
        return MyPromise.resolve(callBack()).then(() = > value); // The finally callback may return a PROMsie object and wait for the promsie object to finish executing before executing the then methods. Therefore, whether the promsie object is returned as a normal value or a promsie object, call resolve() directly to the promise object
      },
      (reason) = > {
        // callBack();
        // throw reason;
        return MyPromise.resolve(callBack()).then(() = > {
          throwreason; }); }); }}const getValueType =() = >{... }Copy the code

At this point, the basic functionality of Promise has been largely implemented. In fact, promise.then is also similar to the idea of callback function, but, the difference between successful and failed callback function, and then method chain call, the callback function nested hell solved, flat. However, it was still not readable enough for our traditional synchronous code, so the Generator appeared.

3.Generator

  • Generator functions have two features, one *(usually written after function, function*) and one yield.
  • The generator function returns a generator object, and the body of the function is not executed until we call it.next.
  • You can use yield to suspend generator functions. Use generator functions for a better asynchronous experience.
function* fun1() {
  console.log("start");
  try{
      // Inside a function, a value can be returned at any time by yield
      // The yield keyword does not end the execution of the generator function as return does, but merely suspends the execution of the generator function. Until the outside world executes the yield method again, the execution continues from the yield position
      let aa = yield "foo";   
      // where "bar" is returned as yield "foo", aa = "foo"
  }catch(error){
      console.log(error,"error")}}const generator = fun1();
// Calling fun1 does not immediately execute the function, but instead gets a generator object
console.log(generator,"generator") 
const result = generator.next();
The body of this function will not start executing until we manually call the next method on this object
// In the next method return object {value: "foo", done: false}, to get the value returned by yeild
// The done attribute in the object returned by the next method indicates whether the generator is fully executed
console.log(result,"result")
// If the next call takes an argument, the argument will be returned as yield
 generator.next("bar") 
// The throw method allows the function to continue executing, except that the generator function throws an exception inside it that needs to be caught by a try or catch
generator.throw("Error reported")
Copy the code

Let’s look at an example

// The Ajax ("http://www.baidu.com") function here is pseudocode, assuming 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()  // Result is a generator object and value is the promise object returned by Yeild Ajax ("http://www.baidu.com")
result.value.then((value) = >{
  const result1 = g.next(value)  // Pass the value returned after the promise execution to data1
  if(result1.done) return
  result1.value.then((value) = >{
      let result2 = g.next(value)  // Pass the value returned after the promise execution to data2
      if(result3.done) return
       / /... You can do this recursively})})Copy the code

Therefore, result can be retrieved in the body of the Generator so that asynchronous problems can be solved using synchronous writing

Encapsulate a generator function executor (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) // Use an external try catch
        })
    }
    handleResult(g.next())
}
/ / call co (main)


Copy the code

async await

  • Async await is the syntactic sugar of generator. Compared with generator, async await does not need to cooperate with an executor like CO. The internal execution process is exactly the same as generator
  • The async function returns a Promise object
  • Await can only be used inside async functions
function* fun() {
  let a = yield setTimeout(() = > console.log("111".0));
  let b = yield setTimeout(() = > console.log("222".0));
}
/** * is equivalent to */
async function fun() {
  let a = await setTimeout(() = > console.log("111".0));
  let b = await setTimeout(() = > console.log("222".0));
}
Copy the code

So now we mostly use async await, which is asynchronous in an almost synchronous way