introduce

This article is more than written, not interested partners can directly jump to the corresponding part according to the title, read this article can understand ~

  • PromiseComply with A+ standard implementation
  • GeneratorInternal principles and simple implementation
  • async/awaitWhat is Generator syntax sugar

The history of asynchronous writing

1. The CallBack callbacks

  • Cons: Dependent requests can form multiple layers of nesting (callback hell) and can be difficult to maintain.
// Request: read the contents of A, read the contents of B, read the contents of C...
fs.readFile(A, "utf-8".function (err, data) {
  / /...
  fs.readFile(B, "utf-8".function (err, data) {
    / /...
    fs.readFile(C, "utf-8".function (err, data) {
      //....
      fs.readFile(D, "utf-8".function (err, data) {
        //....
      });
    });
  });
});
Copy the code

2. Promise chain call

  • Advantages: Chained calls solve some of the problem of callback hell, uniform error handling
  • Disadvantages: Promises cannot be aborted, only by returning a promise in a pending state
// Request: read the contents of B based on A, read the contents of C based on B
// Wrap the readFile callback as a promise
const promisify = (fn) = > (. args) = > {
  return new Promise((resolve, reject) = >{ fn(... args,function (err, data) {
      if (err) reject(err);
      resolve(data);
    });
  });
}
let read = promisify(fs.readFile);
// The nested code is converted to chain notation. The return value of A is passed through then to B and the return value of B is passed through then to C
read(A)
  .then((data) = > {
    / /...
    return read(B);
  })
  .then((data) = > {
    / /...
    return read(C);
  })
  .then((data) = > {
    / /...
    return read(D);
  })
  .catch((err) = > {
    console.error("Read failed", err);
  });
Copy the code

3. Generator + Co

  • Advantages: Synchronous writing method of asynchronous request is realized with CO module
  • Disadvantages: some learning costs, must be combined with additional modules (CO, etc.)
// Request: read the contents of B based on A, read the contents of C based on B
// A simple co (generator self-executing function)
const co = (it) = >
  new Promise((resolve, reject) = > {
    function next(data) {
      const { value, done } = it.next(data);
      if (done) {
        resolve(value);
      } else {
        Promise.resolve(value).then(next, reject);
      }
    }
    next();
  });
// Convert to generator with co library
function* exec() {
  const A = yield read(A);
  / /...
  const B = yield read(B);
  / /...
  const C = yield read(C);
  / /...
  const D = yield read(D);
  / /...
}
co(exec())
Copy the code

4. Async/await

  • Advantages: Writing asynchronous code in synchronous mode vs. Generator advantages: 1. Built-in actuators. 2. Better semantics. 3. Wider applicability: Yield commands can only be followed by Thunk functions or Promise objects. The return value is Promise.
  • Disadvantages: Error handling is complex
// Conversion to async writing method does not need to cooperate with other library writing method simple logic clear
async function exec() {
  try {
    const A = await read(A);
    / /...
    const B = await read(B);
    / /...
    const C = await read(C);
    / /...
    const D = await read(D);
    / /...
  } catch (error) {
    console.error("Read failed", error);
  }
}
exec()
Copy the code

Promise to realize

The front is more simple space, the first iron can directly see the final complete version, see notes can also see about

1. Implement a synchronous version of the base promise

  1. Promise is a class that has three states: Successful Resolved failed Rejected pending
  2. Promise is executed immediately when the executor defaults
  3. Once you succeed, you can’t fail, and vice versa
  4. Use the resolve and reject functions to change the state
  5. Failure logic is also executed if an exception occurs while executing a function
  6. Each instance of a Promise has a THEN method, with the first argument being a successful callback and another failed callback
    • Success status call, with success callback
    • Failed state, call failed callback
class Mypromise {
  constructor(exector) {
    this.states = {
      PENDING: "PENDING".REJECTED: "REJECTED".RESOVED: "RESOLVED"};this.status = this.states.PENDING;
    // Successful results
    this.value = undefined;
    // Result of failure
    this.reason = undefined;
    
    This is a big pity. 1. (depressing) 2. Change success value
    let resolve = (value) = > {
      if (this.status === this.states.PENDING) {
        this.value = value;
        this.status = this.states.RESOVED; }};// Call reject 1. Change the state to reject 2. Changing the failure value
    let reject = (reason) = > {
      if (this.status === this.states.PENDING) {
        this.reason = reason;
        this.status = this.states.REJECTED; }};try {
      If an error is reported, go to the error logic immediately
      exector(resolve, reject);
    } catch(e) { reject(e); }}// The prototype has then methods
  1. Execute the corresponding callback function on success/failure
  then(onFulfilled, onRejected) {
    if (this.status === this.states.RESOVED) {
      onFulfilled(this.value);
    } else if (this.status === this.states.REJECTED) {
      onRejected(this.reason); }}}// External exposure
module.exports = Mypromise;
Copy the code

Run tests on our code (synchronous version)

let promise = new MyPromise((resolve, reject) = > {
  resolve("success");
});
console.log(1);
promise.then(
  (data) = > {
    console.log(`data: ${data}`);
  },
  (err) = > {
    console.log(`err: ${err}`); });console.log(2)

/ / analysis:
// 1. Instantiate MyPromise(initialize the state result, etc.) and execute the incoming callback
// Perform the resolve state change (pending -> depressing) and assign the incoming value (success) to this.value
Run console.log(1) to print: 1
// 4. Execute the then method, which is gradually fulfilled and pass value. Print: data: success
Run console.log(2) to print: 2
Copy the code

Results:

1 
data: success 
2
Copy the code

2. Promise calls processing asynchronously

The synchronous version of promise we implemented above, but if resolve were called asynchronously, the state would be pending when the THEN method was executed, and the callback would not fire. So continue to transform ~

  1. When a promise calls the then method, it is possible that the current promise did not succeed — pending state
  2. Publish/subscribe: If the current state is pending, we need to store successful and failed callbacks and reexecute them later when resolve and Reject are called
constructor(exector) {
  this.states = {
    PENDING: "PENDING".REJECTED: "REJECTED".RESOVED: "RESOLVED"};this.status = this.states.PENDING;
  this.value = undefined;
  this.reason = undefined;
  // Add successful/failed callback queue (add callback to queue pending)
  // The same promise can have more than one THEN, so write it as a queue
+  this.onResolvedCallbacks = [];
+  this.onRejectedCallbacks = [];
  let resolve = (value) = > {
    if (this.status === this.states.PENDING) {
      this.value = value;
      this.status = this.states.RESOVED;
      // After then is added to the queue, execute resolve asynchronously
      Change the status and result. 2. Execute the callback in the success queue
+     this.onResolvedCallbacks.forEach((cb) = >cb()); }};let reject = (reason) = > {
    if (this.status === this.states.PENDING) {
      this.reason = reason;
      this.status = this.states.REJECTED;
      // After then is added to the queue, execute resolve asynchronously
      Change the status and result. 2. Execute the callback in the failed queue
+     this.onRejectedCallbacks.forEach((cb) = >cb()); }};try {
    exector(resolve, reject);
  } catch(e) { reject(e); }}then(onFulfilled, onRejected) {
  if (this.status === this.states.RESOVED) {
    onFulfilled(this.value);
  } else if (this.status === this.states.REJECTED) {
    onRejected(this.reason);
    // If resolve is executed asynchronously, the state does not change when the then method is executed
    // Pending adds the success/failure callback to the queue+}else if (this.status === this.states.PENDING) {
+    this.onResolvedCallbacks.push(() = > {
+      onFulfilled(this.value); +}); +this.onRejectedCallbacks.push(() = >{+console.log(this.status);
+      onRejected(this.reason); +}); }}Copy the code

Test asynchronous ~

let promise = new MyPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve("success");
  });
});
promise.then(
  (data) = > {
    console.log(`data1: ${data}`);
  },
  (err) = > {
    console.log(`err1: ${err}`); }); promise.then((data) = > {
    console.log(`data2: ${data}`);
  },
  (err) = > {
    console.log(`err3: ${err}`); });/ / analysis:
// 1. Instantiate MyPromise(initialize state result, etc.), execute executor(execute executor immediately)
// 2. Run setTimeout --> macro task 1
// 3. Execute the first THEN method, current state pending, to add the incoming success/failure callback to the corresponding success/failure queue
// 4. Execute the second then method, current state pending, to add the incoming success/failure callback to the corresponding success/failure queue.
// 5. After the synchronization task is complete, no microtask queue exists, but some tasks are waiting to be executed in the macro task queue (macro task 1: added in step 2).
// Start executing macro task callback.
This. Value value will be modified, and the callback queue will be executed successfully.
// 7. The trigger queue prints data1: success data2: success
Copy the code

Results:

data1: success  
data2: success
Copy the code

3. Chain calls to the then method

The promsie implementation above still has the problem of callback hell (the result of the previous promise can only be obtained in its own THEN), so let’s implement the chain call to then ~

  1. The return value of the then method is a new Promise
  2. The then method returns a value (a normal value, which is not a promise) that is passed to the success callback of the next THEN (if it is a normal value, either the success or failure of the previous THEN will be followed by the success callback of the next THEN! Leave the error case alone)
then(onFulfilled, onRejected) {
    // The return value is a new Promise, so wrap the methods in the new Promise executor
+  let promise2 = new Mypromise((resolve, reject) = > {
     // When resolve/reject is called synchronously, the state has changed to succeed or fail
     if (this.status === this.states.RESOVED) {
       This is a big pity. // This is a big pity
+      let x = onFulfilled(this.value);
       // The successful callback to then returns the value x, which is passed to the then of the new function via the resolve of the new promise2
       // The then function returns this promise2 to implement the chain call
+      resolve(x);
     } else if (this.status === this.states.REJECTED) {
       // Rejected executes the success callback of THEN directly and passes in the value
+      let x = onRejected(this.reason);
       // Same logic as above
+      resolve(x);
     } else if (this.status === this.states.PENDING) {
       // Execute success/failure callbacks to the then methods in sequence (possibly multiple)
       this.onResolvedCallbacks.push(() = > {
         // The same logic as success judgment
+        let x = onFulfilled(this.value);
+        resolve(x);
       });
       this.onRejectedCallbacks.push(() = > {
         // Same logic as failure judgment
+        let x = onRejected(this.reason); + resolve(x); }); +}}); +return promise2;
}
Copy the code

Test the next chain call ~

let promise = new MyPromise((resolve, reject) = > { / / 1
  setTimeout(() = > { / / 2
    reject("failed"); / / 3
  });
});
promise
  .then( / / 4
    (data) = > { / / 5
      console.log(`data1: ${data}`);
      return 123;
    },
    (err) = > {  / / 6
      console.log(`err1: ${err}`);
      return err;
    }
  )
  .then( / / 7
    (data) = > { / / 8
      console.log(`data2: ${data}`);
    },
    (err) = > { / / 9
      console.log(`err2: ${err}`); });/* The first Promise will be called P1, and the next Promise will be 1. Execute 1: instantiate MyPromise(initialize state result, etc.), execute executor(execute executor immediately) 2. Run setTimeout --> macro task 1 3. Execute 4: execute the first THEN method 3.1 instantiate a new P2, execute the executor 3.2 P2 execute: The status of P1 is pending, so click pending and add the success and failure callbacks to the corresponding queue. 4.1 Instantiate a new P3 and execute the executor. 4.2 P3 execute the executor: The status of P2 is pending, so click pending and add the success and failure callbacks to the corresponding queue. The synchronization task is complete, but no microtask exists. A waiting task exists in the macro task queue (macro task 1). Perform macro task callback 6. Run 3: Reject ('failed') and modify the status (pending -> Rejected) this.reason to call back the failed queue. Execute 6: Trigger the failed callback queue for P1 (enter: err1: This is a big pity. This is a big pity. This is a big pity. Change value to failed and execute P2 successfully callback queue (go to 8) (data2: field) */
Copy the code

Results:

err1: failed
data2: failed
Copy the code

3.1 Handling cases where the then method callback returns a PROMISE and errors in the callback

The basic PROMsie chain call is implemented above, but some special cases cannot be handled

  1. The return value of the success/failure callback for then is a new promise, which is determined by the state of the new promise.
  2. When the successful/failed callback methods of THEN execute an error, then returns the state of promise changed to failed (Rejected)
  3. While resolve/ Reject in a promise is not asynchronous,the success/failure callback in then should also be asynchronous.

After realizing this step, this promise is almost done, and then the special cases are improved

then(onFulfilled, onRejected) {
  let promise2 = new Mypromise((resolve, reject) = > {
    // Encapsulate success and failure callbacks as methods
+   const resolveCallback = () = > {
+     queueMicrotask(() = >{+// This is a pity. If there is an error, change the new promise state to fail reject
+       // Add queueMicrotask to the outer layer of the executor of the promise2 so that no error is caught
+       try{+let x = onFulfilled(this.value);
+         // Check whether the success callback returns a Promise, if so, wait for the new Promise state to change to success or failure.
+         // Modify the resolve/reject state of promise2 by invoking the successful/failed callback
+         // If it is normal, call resolve of promise2 directly
+         // Do the same thing below
+         x instanceofMypromise ? x.then(resolve, reject) : resolve(x); +}catch(error) { + reject(error); + +}}); +}; +const rejectedCallback = () = > {
+     queueMicrotask(() = >{+try{+let x = onRejected(this.reason);
+         x instanceofMypromise ? x.then(resolve, reject) : resolve(x); +}catch(error) { + reject(error); + +}}); +};if (this.status === this.states.RESOVED) {
+     resolveCallback();
    } else if (this.status === this.states.REJECTED) {
+     rejectedCallback();
    } else if (this.status === this.states.PENDING) {
      this.onResolvedCallbacks.push(() = > {
+       resolveCallback();
      });
      this.onRejectedCallbacks.push(() = >{ + rejectedCallback(); }); }});return promise2;
}
Copy the code

Test for success or failure of THEN and return a promise

let promise = new MyPromise((resolve, reject) = > { / / 1
  // If queueMicrotask is not used to make the microtask print result synchronized 1 will be between the two THEN
  // reject("failed");
  setTimeout(() = > { / / 2
    reject("failed"); / / 3
  });
});
let p2 = promise.then( / / 4
  (data) = > { / / 5
    console.log(`data1: ${data}`);
  },
  (err) = > { / / 6
    console.log(`err1: ${err}`);
    return new MyPromise((resolve, reject) = > reject(123)); });console.log("1"); / / 7
p2.then( / / 8
  (data) = > { / / 9
    console.log(`data2: ${data}`);
  },
  (err) = > { / / 10
    console.log(`err3: ${err}`); });/* The first Promise will be called P1, and the next Promise will be 1. Execute 1: instantiate MyPromise(initialize state result, etc.), execute executor(execute executor immediately) 2. Run setTimeout --> macro task 1 3. Execute 4: execute the first THEN method 3.1 instantiate a new P2, execute the executor 3.2 P2 execute: The status of P1 is pending, so click pending and add the success and failure callbacks to the corresponding queue. 5. Execute 9: execute second THEN method (P2 then method) 5.1 Instantiate a new P3, execute execute 5.2 P3 execute: The status of P2 is pending, so click pending and add the success and failure callbacks to the corresponding queue. The synchronization task is complete, but no microtask exists. A waiting task exists in the macro task queue (macro task 1). Perform macro task callback 7. Run 3: reject('failed') and change the status (pending -> Rejected) this. Reason to failed, execute the callback queue. The current synchronization task starts microtask 1 <-- 8. Perform 6: Trigger the failed callback queue of P1 (enter: err1: failed) 7.1 Perform 7: P4: reject (reject) {P4: reject (reject) {P4: reject (Reject)} P4 reject (REJECT); reject (REJECT); reject (reject); Change reason to 123, execute P2 failure callback queue (go to 11) --> microtask 2 (err3:123) - No: P4 passes to P2's resolve and executes, changes P2's state to depressing, changes value to P4, and executes P2's success callback queue (perform 10) --> microtask 2 (output datA2: P3) */
Copy the code

Results:

synchronous1
err1: failed
err3: 123
Copy the code

3.2 Defining the resolvePromise function uniformly handles the return result of the THEN callback

Topic analysis write cranial pain 🤦♀, should calculate more detailed this section all processed all special cases!!

  1. Unified handling of then callback returns (e.g., cannot return promise itself, does not handle Thenable, etc.)(complies with promise A+ criteria)
  2. P1.then ().then().then().then(d=>console.log(d))
then(onFulfilled, onRejected){+Function is ignored and reset to v=>v passes the value to the successful callback at the next level
+ onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) = > v;
+ Function ignores and resets to v=>throw v passes the value to the failed callback at the next level
+ onRejected =
+   typeof onRejected === "function"+? onRejected + :(err) = >{+throwerr; +};let promise2 = new Mypromise((resolve, reject) = > {
    // Encapsulate success and failure callbacks as methods
    const resolveCallback = () = > {
      QueueMicrotask turning tasks into microtasks is no longer a synchronous task
      queueMicrotask(() = > {
        // This is a pity. If there is an error, change the new promise state to fail reject
        try {
          let x = onFulfilled(this.value);
          // Check whether the success callback returns a Promise, if so, wait for the new Promise state to change to success or failure.
          // Modify the resolve/reject state of promise2 by invoking the successful/failed callback
          // If it is normal, call resolve of promise2 directly
          // Do the same thing below
          // Perform uniform processing on the return value of the then callback
+         resolvePromise(promise2, x, resolve, reject);
          // Do not consider thenable
          // x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
        } catch(error) { reject(error); }}); };const rejectedCallback = () = > {
      queueMicrotask(() = > {
        try {
          let x = onRejected(this.reason);
+         resolvePromise(promise2, x, resolve, reject);
        } catch(error) { reject(error); }}); };if (this.status === this.states.RESOVED) {
      resolveCallback();
    } else if (this.status === this.states.REJECTED) {
      rejectedCallback();
    } else if (this.status === this.states.PENDING) {
      this.onResolvedCallbacks.push(() = > {
        resolveCallback();
      });
      this.onRejectedCallbacks.push(() = >{ rejectedCallback(); }); }});return promise2;
}

/ / big head!
+const resolvePromise = (promise2, x, resolve, reject) = >{+// Trigger an error when the return value is itself to prevent an infinite loop
+  if (promise2 === x) {
+    // Terminate a promise with a type error
+    return reject(
+      new TypeError("Chaining cycle detected for promise #<Promise>") +); +},// Prevent A failure to re-enter the success/success to re-enter the failure, the normal value does not need to handle (without adding this can not pass A+ standard test ==.)
+  // But I can't think of any scenario that will trigger multiple times (know the partner can reply me ~)
+  let called;
+  // Check whether A+ is thenable or function;
+  if ((typeof x === "object"&& x ! =null) | |typeof x === "function") {+try{+let then = x.then;
+      if (typeof then === "function") {+// It's a promise
+        then.call(
+          x,
+          (y) = >{+if (called) return;
+            called = true;
+            // Success or failure depends on the state of the promise
+            resolvePromise(promise2, y, resolve, reject); // The recursive parsing process+ +}(e) = >{+if (called) return;
+            called = true; + reject(e); +} +); +}else{+{then: '12'}+ resolve(x); + +}}catch (e) {
+      if (called) return;
+      called = true; + reject(e); + +}}else{+/ / common values+ resolve(x); + +}};Copy the code

Final test ~~

let p = new MyPromise((resolve, reject) = > { / / 1
  resolve(1); / / 2
});
let p2 = p
  .then( / / 3
    (data) = > { / / 4
    console.log('date1',data)
    return new MyPromise((resolve, reject) = > { / / 5
      resolve( / / 6
        new MyPromise((resolve, reject) = > { / / 7
          resolve(2); / / 8})); }); }) .then()Then (d=>d,e=>{throw e})
  .then( / / 10
    (data) = > { / / 11
      console.log("data2", data);
    },
    (err) = > { / / 12
      console.log("err", err); });/* The first Promise is called P1, and all subsequent promises are added by 1. Execute 1: instantiate MyPromise(initialize state result, etc.) P1, execute executor(execute executor immediately) 2. Perform 1: Perform RESOLVE to change the status of P1 (pending -> depressing) value(null -> 1), and the queue will be empty. Execution 3: Perform the first then method 3.1 instantiate a new P2 and perform the actuator 3.2 THE ACTUATOR executes: THE state of P1 is depressing, so the successful item is fulfilled --> micro-task 1(P1) 3.3 Return P2 and jump out 4. Execute the second then method 4.1 without successful/failed callback parameters, copy the two parameters to the default values. Successful callback v=>v failed. The status of P2 is pending, so click pending and add the default success and failure callbacks to the corresponding queue. P4: execute (P4, P4, P4); The status of P3 is pending, so click pending and add the success and failure callbacks to the corresponding queue. When the synchronization task is completed, there is [microtask 1(P1)] in the microtask queue. Perform microtask 1 <-- 7. Execute 4: microtask 1 callback, execute P2 successful callback [[print date1 1]] 7.1 Instantiate a new P5, execute actuator 7.2 Perform 7: Instantiate a new P6, execute actuator 7.3 Perform 8: This is a big pity: P6 state (pending -> depressing) value (null -> 2) will be changed gradually. The state of P5 (pending -> depressing) value (NULL -> P6) is gradually changed. The return value of 7.5 P2 THEN is P5(successful state). Type is PROMISE, and then method P5 will be implemented, which is a pity state. Thus, the successful callback of P5 will be triggered directly. --> microtask (P5), microtask (P5) <-- The microtask queue is empty. P6: resolve, P6: resolve, P6: resolve, P6: resolve P6: resolve(3) --> micro task (P2) Microtask (P2) <-- microtask queue empty 9. Execute 11: trigger successful callback P3, [[print: This is a big pity. The return value of this parameter is undefined, and undefined is the normal value. The state of P4 will be fulfilled gradually
Copy the code

Results:

date1 1
data2 2
Copy the code

4. Promsie some native methods are implemented

There’s another way promise can be written, but it’s much simpler than this.

  1. Static methods resolve reject all race
  2. Prototype method Catch finally
// The catch method executes the second callback to then
catch(rejectFn) {
  return this.then(undefined, rejectFn);
}

// Finally method success/failure will return callback does not affect the result of subsequent then
// If it is a successful promise, it will use previous successful results
// But if it is a failed promise, it will pass on its failure reason to the next promise
finally(callback) {
  return this.then(
    // Perform a callback and return value is passed to the following THEN
    (value) = > Mypromise.resolve(callback()).then(() = > value), 
    (reason) = >
      Mypromise.resolve(callback()).then(() = > {
        throw reason;
      }) / / reject again
  );
}

The static resolve method returns a Promise instance if the argument is a Promise instance
static resolve(value) {
  if (value instanceof MyPromise) return value;
  return new MyPromise((resolve) = > resolve(value));
}

Static reject method
static reject(reason) {
  return new MyPromise((resolve, reject) = > reject(reason));
}

// Static all method
static all(promiseArr) {
  let index = 0;
  let result = [];
  return new MyPromise((resolve, reject) = > {
    promiseArr.forEach((p, i) = > {
      // The value that does not convert to a promise is also converted to a promise
      MyPromise.resolve(p).then(
        (val) = > {
          index++;
          result[i] = val;
          // If the length of the incoming queue is the same as the length of the incoming queue, the result will be returned
          if(index === promiseArr.length) { resolve(result); }},(err) = > {
          // Error returns error resultsreject(err); }); }); }); }// Static race method
static race(promiseArr) {
  return new Mypromise((resolve, reject) = > {
    // Execute the Promise, and if one of the Promise states changes, change the new MyPromise state
    for (let p of promiseArr) {
      Mypromise.resolve(p).then(
        // promise.resolve (p) is used to handle cases where the incoming value is not a Promise
        resolve, // Resolve = new MyPromisereject ); }}); }Copy the code

5. (Complete code) Pass Promises – aplus-Tests

Here is the complete version, which has passed the test (872 passing) with some typos corrected

  1. npm i promises-aplus-tests -g
  2. Promises – aplus-tests. /promise.js run the corresponding file
// then callback returns the same value
const resolvePromise = (promise2, x, resolve, reject) = > {
  // Trigger an error when the return value is itself to prevent an infinite loop
  if (promise2 === x) {
    // Terminate a promise with a type error
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")); }// Prevent A failure to re-enter the success/success to re-enter the failure, the normal value does not need to handle (without adding this can not pass A+ standard test ==.)
  // But I can't think of any situation where it will trigger more than once.
  let called;
  // The following conditions should be strictly judged to ensure that substitutes are used with other libraries;
  if ((typeof x === "object"&& x ! =null) | |typeof x === "function") {
    try {
      let then = x.then;
      if (typeof then === "function") {
        // It's a promise
        // Do not write x. teng
        then.call(
          x,
          (y) = > {
            if (called) return;
            called = true;
            // Success or failure depends on the state of the promise
            resolvePromise(promise2, y, resolve, reject); // The recursive parsing process
          },
          (e) = > {
            if (called) return;
            called = true; reject(e); }); }else {
        // {then: '12'}
        / / common valuesresolve(x); }}catch (e) {
      if (called) return;
      called = true; reject(e); }}else {
    / / common valuesresolve(x); }};class MyPromise {
  constructor(executor) {
    this.states = {
      PENDING: "PENDING".REJECTED: "REJECTED".RESOLVED: "RESOLVED"};this.status = this.states.PENDING;
    this.value = undefined; / / success
    this.reason = undefined; / / failure
    this.onResolvedCallbacks = []; // Successful callback
    this.onRejectedCallbacks = []; // Failed callback

    let resolve = (value) = > {
      // It will pass the test whether it is added or not, and will not be consistent with the native performance
      if (value instanceof MyPromise) {
        return value.then(resolve, reject);
      }
      if (this.status === this.states.PENDING) {
        // After then is added to the queue, execute resolve asynchronously
        Change the status and result. 2. Execute the callback in the success queue
        this.value = value;
        this.status = this.states.RESOLVED;
        this.onResolvedCallbacks.forEach((cb) = >cb()); }};let reject = (reason) = > {
      if (this.status === this.states.PENDING) {
        this.reason = reason;
        this.status = this.states.REJECTED;
        this.onRejectedCallbacks.forEach((cb) = >cb()); }};try {
      // Execute the incoming callback immediately
      executor(resolve, reject);
    } catch(e) { reject(e); }}then(onFulfilled, onRejected) {
    Function is ignored and reset to v=>v passes the value to the successful callback at the next level
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) = > v;
    Function ignores and resets to v=>throw v passes the value to the failed callback at the next level
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (err) = > {
            throw err;
          };
    let promise2 = new MyPromise((resolve, reject) = > {
      // Encapsulate success and failure callbacks as methods
      const resolveCallback = () = > {
        QueueMicrotask turning tasks into microtasks is no longer a synchronous task
        queueMicrotask(() = > {
          // This is a pity. If there is an error, change the new promise state to fail reject
          try {
            let x = onFulfilled(this.value);
            // Check whether the success callback returns a Promise, if so, wait for the new Promise state to change to success or failure.
            // Modify the resolve/reject state of promise2 by invoking the successful/failed callback
            // If it is normal, call resolve of promise2 directly
            // Do the same thing below
            resolvePromise(promise2, x, resolve, reject);
            // Do not consider thenable
            // x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
          } catch(error) { reject(error); }}); };const rejectedCallback = () = > {
        queueMicrotask(() = > {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
            // x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
          } catch(error) { reject(error); }}); };if (this.status === this.states.RESOLVED) {
        resolveCallback();
      } else if (this.status === this.states.REJECTED) {
        rejectedCallback();
      } else if (this.status === this.states.PENDING) {
        this.onResolvedCallbacks.push(() = > {
          resolveCallback();
        });
        this.onRejectedCallbacks.push(() = >{ rejectedCallback(); }); }});return promise2;
  }

  // The catch method executes the second callback to then
  catch(rejectFn) {
    return this.then(undefined, rejectFn);
  }
  
  // Finally method success/failure will return callback does not affect the result of subsequent then
  // If it is a successful promise, it will use previous successful results
  // But if it is a failed promise, it will pass on its failure reason to the next promise
  finally(callback) {
    return this.then(
      // Perform a callback and return value is passed to the following THEN
      (value) = > Mypromise.resolve(callback()).then(() = > value),
      (reason) = >
        Mypromise.resolve(callback()).then(() = > {
          throw reason;
        }) / / reject again
    );
  }

  The static resolve method returns a Promise instance if the argument is a Promise instance
  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise((resolve) = > resolve(value));
  }

  Static reject method
  static reject(reason) {
    return new MyPromise((resolve, reject) = > reject(reason));
  }

  // Static all method
  static all(promiseArr) {
    let index = 0;
    let result = [];
    return new MyPromise((resolve, reject) = > {
      promiseArr.forEach((p, i) = > {
        // The value that does not convert to a promise is also converted to a promise
        MyPromise.resolve(p).then(
          (val) = > {
            index++;
            result[i] = val;
            // If the length of the incoming queue is the same as the length of the incoming queue, the result will be returned
            if(index === promiseArr.length) { resolve(result); }},(err) = > {
            // Error returns error resultsreject(err); }); }); }); }// Static race method
  static race(promiseArr) {
    return new MyPromise((resolve, reject) = > {
      // Execute the Promise, and if one of the Promise states changes, change the new MyPromise state
      // The value that does not convert to a promise is also converted to a promise
      promiseArr.forEach((p, i) = > {
        // Execute the promise, and the first one to return the result directly changes the outer promise stateMyPromise.resolve(p).then(resolve, reject); }); }); }}// Test plug-ins need to add this step
MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
};

module.exports = MyPromise;
Copy the code

Promsie has finally come to an end

Generator principle and basic implementation

A brief introduction to use

// Generator returns an iterator object that calls the internal code step by step
function* foo() {
  yield "result1";
  let res2 = yield "result2";
  try {
    console.log('res2',res2)
    yield "result3";
  } catch (e) {
    console.log("e", e);
  }
  yield "result4";
  return "result5";
}
// Each time next executes to the next yield statement
// The next pass becomes the yield return
const gen = foo();
gen.next(); {value: 'result1', done: false}
gen.next(); {value: 'result2', done: false}
gen.next(123); // Output {value: 'result3', done: false} res2 123
gen.next(); {value: 'result4', done: false}
gen.next(); {value: 'result5', done: true}
Copy the code

Analyze the generator compiled by Babel

// This is a generator method
function* foo() {
  console.log(1);
  yield "result1";
  let res2 = yield "result2";
  // throw 134
  try {
    console.log(2, res2);
    yield "result3";
    console.log(2.1);
  } catch (e) {
    console.log("e", e);
  }

  yield "result4";
  return "result5";
}

const gen = foo();
gen.next(); {value: 'result1', done: false}
gen.next(); {value: 'result2', done: false}
gen.next(); {value: 'result3', done: false}
gen.throw(); {value: 'result4', done: false}
gen.next(); {value: 'result5', done:}

// After compiling and converting with Babel
var _marked = /*#__PURE__*/regeneratorRuntime.mark(foo);

function foo() {
  var res2;
  return regeneratorRuntime.wrap(function foo$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          console.log(1);
          _context.next = 3;
          return "result1";

        case 3:
          _context.next = 5;
          return "result2";

        case 5:
          res2 = _context.sent;
          _context.prev = 6;
          console.log(2, res2);
          _context.next = 10;
          return "result3";

        case 10:
          console.log(2.1);
          _context.next = 16;
          break;

        case 13:
          _context.prev = 13;
          _context.t0 = _context["catch"] (6);
          console.log("e", _context.t0);

        case 16:
          _context.next = 18;
          return "result4";

        case 18:
          return _context.abrupt("return"."result5");

        case 19:
        case "end":
          return _context.stop();
      }
    }
  }, _marked, nullThe [[6.13]]);
}

var gen = foo();
gen.next();
gen.next();
gen.next();
gen["throw"] (); gen.next();Copy the code

Based on the generator’s usage nature and the compiled code analysis we’ll start with a few points to observe the compiled code. Each call to foo$will go through a case and change prev and next, ending with _context.stop()

  1. There is a _context object store where it goes and what the next step is (each foo() generates a new generator so a context is generated each time)
  2. There is a regeneratorRuntime.mark method passed to Foo
  3. There is a regeneratorruntime. wrap method that passes in foo$mark and returns null [6,13]
  4. Foo () returns an object with methods like next throw return
  5. The next() call executes the next case
  6. Throw () throws an error where it was previously executed
  7. Saving the return value via _context. abnormal (return has no code so outputs the final result at case end)
  8. Change the done state with _context.step and print the final value

This section describes the principle and simple implementation of the Generator

Based on the above summary, let’s implement a portion of the code first

  1. Each generator has a _context object to maintain internal state, so we write a constructor to generate the _context object
// foo$determines the current and next steps based on prev and next
const Context = function () {
  this.prev = 0; // Which switch is currently in case
  this.next = 0; // Which case to go next
  this.done = false; // Whether to complete
};
Context.prototype = {
  constructor: Context,
  // Finally asynchrony has an end method
  stop() {
    // Case end has changed the completion state in the last step and returns the final value
    this.done = true;
  },
  // Save the value of return
  abrupt(){}}Copy the code
  1. There is a regeneratorRuntime.mark method passed to Foo (forget the internal implementation of the method, just assume that there is no return value)
const regeneratorRuntime = {};
// Return foo itself, regeneratorRuntime.mark mainly changes foo's prototype and __proto__, inheriting methods such as next throw, etc
regeneratorRuntime.mark = function (genFun) {
  // TODO ... Change the prototype and __proto__ of the method passed in
  // genFun.prototype = Object.create(Gp);
  return genFun;
};
Copy the code
  1. There is a regeneratorruntime. wrap method that passes in foo$mark and returns null [6,13]
/* Generator returns an iterator object, so wrap should also return an iterator object. A context is maintained internally during initialization to maintain internal state */

// Declare an object as a prototype for all generators and write methods such as next throw return to this prototype
const Gp = {}; // generator prototype

/ * * * *@param {*} InnerFn outer foo *@param {*} The foo$in the outerFn inner layer is the swich case method *@param {*} This of foo$inside self we don't use null * here@param {*} TryLocsList error corresponds to the case array of prev and next [[tryLoc,catchLoc]] *@returns {object} Its return value is an object with a next throw return method */
regeneratorRuntime.wrap = function (innerFn, outerFn, self, tryLocsList) {
  // Create a return object that inherits methods like next throw
  const generator = Object.create(Gp);
  // Each generator has its own context to store information such as next Prev done
  const _context = new Context();
  // Next throw has more than one method
  // Next requires the innerFn parameter to execute the innerFn
  // innerFn requires the context to determine which step to execute
  generator._invoke = makeInvokeMethod(innerFn, self, _context);
  // Generate
  return generator;
};

// Create an invoke method that returns a method called the next throw Return method
// What happened at next?
// 1. Execute foo$and return {value,done}
function makeInvokeMethod(innerFn, self, context) {
  // To distinguish which of the next throws requires a type parameter called method, the method has an arg that needs to be passed
  return function invoke(method, arg) {
    if (method === "next") {
      1. Execute the current case
      const value = innerFn.call(self, context);
      return { value, done: context.done };
    } else if (method === "throw") {
      / / throw logic
    }
    // Public logic
  };
}

// Define the prototype method for Generator Gp
Next throw return is actually called invoke
function defineIteratorMethods(prototype) {["next"."throw"."return"].forEach(function (method) {
    prototype[method] = function (arg) {
      return this._invoke(method, arg);
    };
  });
}
defineIteratorMethods(Gp);
Copy the code

After the above steps, we can successfully call next to print out the output.

  1. On each generator call, return an object containing the next method, create a context and maintain properties such as prev/next to record what step is currently being taken (case of which switch is executed)
  2. Each time next is called, the switch case method is executed, starting with case 0 by default, and then changing next to point to the next case. Next time foo$is executed, the next case will be executed directly, and the value of case will be returned as value

Let’s test it out

var _marked = /*#__PURE__*/ regeneratorRuntime.mark(foo);

function foo() {
  var res2;
  return regeneratorRuntime.wrap(
    function foo$(_context) {
      while (1) {
        switch ((_context.prev = _context.next)) {
          case 0:
            console.log(1);
            _context.next = 3;
            return "result1";

          case 3:
            _context.next = 5;
            return "result2";

          case 5:
            res2 = _context.sent;
            _context.prev = 6;
            console.log(2, res2);
            _context.next = 10;
            return "result3";

          case 10:
            console.log(2.1);
            _context.next = 16;
            break;

          case 13:
            _context.prev = 13;
            _context.t0 = _context["catch"] (6);
            console.log("e", _context.t0);

          case 16:
            _context.next = 18;
            return "result4";

          case 18:
            return _context.abrupt("return"."result5");

          case 19:
          case "end":
            return _context.stop();
        }
      }
    },
    _marked,
    nullThe [[6.13]]); }var gen = foo();
console.log("next1:", gen.next());
console.log("next1:", gen.next());
console.log("next1:", gen.next());
console.log("next1:", gen.next());
console.log("next1:", gen.next());
console.log("next1:", gen.next());
/* Return to print 1 next1: {value: 'result1', done: false} next1: {value: 'result2', done: false} 2 undefined next1: {value: 'result3', done: false} 2.1 next1: {value: 'result4', done: false} next1: {value: undefined, done: false} next1: {value: undefined, done: false} next1: {value: undefined, done: false} { value: undefined, done: false } */
Copy the code

In this step we learned the basic operation principles of the Generator and wrote a minimalist version

Improved next method, and done state changes

Next is ready to go. But there are several obvious problems

  1. If done is always false, it is not finished
  2. When generator returns,done changes to the finished state (true) and returns {value:returnVal,done: true}.
// You can see that case 18 is finished, because the context. Next is not changed, so the case end item is not executed
// We want him to execute, so we have him change the value of next to end, in case 18 context.abrupt
// Modify the abrupt method
// Invoke while continues
+ const ContinueSentinel = {};

const Context = function () {
  this.prev = 0; // Which switch is currently in case
  this.next = 0; // Which case to go next
  this.done = false; // Whether to complete
+ this.rval = undefined; // Store the return value
};

Context.prototype = {
  constructor: Context,
  // The last case has an end method that modifies the end state and returns a return value
  stop() {
    // The completed state has been changed in the last step
+    this.done = true;
     // Returns the value of return
     return this.rval;
  },
  // Save the result type is the type arg is the result of return
  abrupt(type, arg){+if (type === "return") {+// Return done is true
+      // In order for end to be executed
+      this.next = "end";
+      this.rval = arg; // Use context.rval to store the result of return
+      // Invoke a while to determine whether to proceed with 'continue', invoke (next) and casa end (case 18) to true
+      returnContinueSentinel; +}}};// The next switch case needs to be modified to automatically execute the invoke when it returns
// Returns a method called the next throw return method
function makeInvokeMethod(innerFn, self, context) {
  // To distinguish which of the next throws requires a type parameter called method, the method has an arg that needs to be passed
  return function invoke(method, arg) {
    // TODO adds a loop to make it automatically go to the end of the case
+   while (true) {
      if (method === "next") {
        1. Execute the current case
        const value = innerFn.call(self, context);
        // The abrupt return value is repeated to the innerFn if ContinueSentinel
        if (value === ContinueSentinel) {
          // Execute the method again when the state is complete and the case end is executed
          continue;
        }
        return { value, done: context.done };
      } else if (method === "throw") {
        / / throw logic
      } else if (method === "return") {}// Public logic+}}; }Copy the code

Let’s continue testing

var _marked = /*#__PURE__*/ regeneratorRuntime.mark(foo);

function foo() {
  var res2;
  return regeneratorRuntime.wrap(
    function foo$(_context) {
      while (1) {
        switch ((_context.prev = _context.next)) {
          case 0:
            console.log(1);
            _context.next = 3;
            return "result1";

          case 3:
            _context.next = 5;
            return "result2";

          case 5:
            res2 = _context.sent;
            _context.prev = 6;
            console.log(2, res2);
            _context.next = 10;
            return "result3";

          case 10:
            console.log(2.1);
            _context.next = 16;
            break;

          case 13:
            _context.prev = 13;
            _context.t0 = _context["catch"] (6);
            console.log("e", _context.t0);

          case 16:
            _context.next = 18;
            return "result4";

          case 18:
            return _context.abrupt("return"."result5");

          case 19:
          case "end":
            return _context.stop();
        }
      }
    },
    _marked,
    nullThe [[6.13]]); }var gen = foo();
console.log("next1:", gen.next());
console.log("next2:", gen.next());
console.log("next3:", gen.next());
console.log("next4:", gen.next());
console.log("next5 return:", gen.next());
console.log("next6:", gen.next());


/* Return to print 1 next1: {value: 'result1', done: false} next2: {value: 'result2', done: false} 2 undefined next3: {value: 'result3', done: false} 2.1 nexT4: {value: 'result4', done: false} Next5 Return: {value: 'result5', done: Next6: {value: 'result5', done: true} */
Copy the code

Continue to refine adding methods like throw Return

The problem that still exists ~ this piece does not speak so carefully

  1. The next method takes one argument, and if the yield expression itself returns no value, or if it always returns undefined, that argument will be treated as the return value of the previous yield expression.
  2. The next method is called after the end, and the value is undefined, above which is the last value
  3. Add throw and return methods
// Problem 1. Next add a sent attribute to context to record the value returned last time. Next switch case will fetch the last context.sent
// Pass an argument to store Next in the next item of invoke
context.sent = arg;

// Problem 2. Next method called after completion,value should be undefined, above is the last value
// We add interception to invoke when the done state is changed to true
if (context.done) {
  return { value: undefined.done: true };
}
Copy the code

Let’s look at the characteristics of throws

  1. You can throw errors internally
  2. If it is not intercepted internally, an error can be thrown outside the function
  3. Next is automatically executed and output (see case 10)
// Feature 1: You can throw an error internally
// case 13: tryLoc (prev(6)) is passed to the context with a catch method
[{tryLoc: 'root'}] If tryLoc is root, the error is not caught
// wrap [[tryLog,catchLog]]
const Context = function (tryLocsList) {
  this.prev = 0; // Which switch is currently in case
  this.next = 0; // Which case to go next
  this.done = false; // Whether to complete
  this.rval = undefined; // Store the return value
+ // tryLoc try catchLoc catch case completion:{type: type, arg: error}
+ this.tryEntries = [
+   { tryLoc: "root".completion: { type: "".arg: undefined}} +];// Store the error message
+ tryLocsList.forEach((locs) = >{+/ / [6, 13]
+   var entry = { tryLoc: locs[0]};// Try position
+   if (1 in locs) {
+     // If there is a catch
+     entry.catchLoc = locs[1]; The location of the / / catch
+   }
+   entry.completion = { type: "".arg: undefined };
+   this.tryEntries.push(entry); +}); +//[{ tryLoc: 'root' }, { tryLoc: 6, catchLoc: 13 }]
};

// Invoke invoke
// 1. The innerFn(foo$) method is executed to extract the public part
// Returns a method called the next throw return method
function makeInvokeMethod(innerFn, self, context) {
  // To distinguish which of the next throws requires a type parameter called method, the method has an arg that needs to be passed
  return function invoke(method, arg) {
    if (method === "throw" && context.done) {
      throw arg;
    }
    if (context.done) {
      return { value: undefined.done: true };
    }
    // Add a loop to make it automatically go to the end of the case
    while (true) {
      if (method === "next") {+1. Execute the current case
+       // 2. Select sent from next switch case+ context.sent = arg; +}else if (method === "throw") {+// throw logic saves the error value and modifies the next position+ context.dispatchException(arg); +}else if (method === "return") {
+       context.abrupt("return", arg); +},// Public logic
+     const value = innerFn.call(self, context);
+     if (value === ContinueSentinel) {
+       // Execute the method again when the state is complete and the case end is executed
+       continue; +},return { value, done: context.done }; }}; }// 2. Throw error save error value change next position to catch position
// Add catch and dispatchException methods to the context. prototype prototype
// case 13 if tryLoc is found, an error will be returned
catch(tryLoc) {
  for (var i = this.tryEntries.length - 1; i >= 0; --i) {
    const entry = this.tryEntries[i];
    if (tryLoc === entry.tryLoc) {
      returnentry.completion.arg; }}},// Handle exception arguments: throw method fires with error value
dispatchException(exception) {
  // Storage error reported
  // Change the location of next
  // Start matching from the last try catch position
  const context = this;
  // Change the tryEntries corresponding to trycatch completion to store the wrong value
  // Change the next pointer to the corresponding catchLoc catch case
  function handle(loc, record) {
    record.type = "throw";
    record.arg = exception;
    context.next = loc;
    // return !! caught;
  }
  for (var i = this.tryEntries.length - 1; i >= 0; --i) {
    const entry = this.tryEntries[i];
    const record = entry.completion;
    // Error before first trycatch
    Done: true,value:undefined
    if (entry.tryLoc === "root") {
      return handle("end", record);
    }
    // Verify which try catch is caught
    if (entry.tryLoc <= context.prev) {
      if (context.prev < entry.catchLoc) {
        returnhandle(entry.catchLoc, record); }}}},Copy the code

Go straight to the current complete code

/* function* foo() { console.log(1); yield "result1"; let res2 = yield "result2"; // throw 134 try { console.log(2, res2); yield "result3"; The console. The log (2.1); } catch (e) { console.log("e", e); } yield "result4"; return "result5"; } const gen = foo(); gen.next(); gen.next(); gen.throw(); * /

// We implement a simple generator step by step
// Deduce some rules from the code above
// 1. There is a _context object where the store goes and what the next step is (each foo() generates a new generator so a context is generated each time)
// 2. Foo () returns an object with methods like next throw
// 3. The next() call executes the next case
// 4. throw() throws an error where it was previously executed

// Every generator has a _context object, so we write a constructor to generate the _context object
// try catch position [[6,13]]
const Context = function (tryLocsList) {
  this.prev = 0; // Which switch is currently in case
  this.next = 0; // Which case to go next
  this.done = false; // Whether to complete
  this.rval = undefined; // Store the return value
  // tryLoc try catchLoc catch case completion:{type: type, arg: error}
  this.tryEntries = [
    { tryLoc: "root".completion: { type: "".arg: undefined}},];// Store the error message
  tryLocsList.forEach((locs) = > {
    / / [6, 13]
    var entry = { tryLoc: locs[0]};// Try position
    if (1 in locs) {
      // If there is a catch
      entry.catchLoc = locs[1]; The location of the / / catch
    }
    entry.completion = { type: "".arg: undefined };
    this.tryEntries.push(entry);
  });
  //[{ tryLoc: 'root' }, { tryLoc: 6, catchLoc: 13 }]
};

const regeneratorRuntime = {};
const Gp = {}; // generator prototype
const ContinueSentinel = {}; // Invoke while continues
Context.prototype = {
  constructor: Context,
  // Finally asynchrony has an end method
  stop() {
    // The completed state has been changed in the last step
    this.done = true;
    // Returns the value of return
    return this.rval;
  },
  Type is the type arg is the result of return
  abrupt(type, arg) {
    if (type === "return") {
      // Return done is true
      // In order for end to be executed
      this.next = "end";
      this.rval = arg;
      // Invoke's while determines whether to go continue
      returnContinueSentinel; }},catch(tryLoc) {
    for (var i = this.tryEntries.length - 1; i >= 0; --i) {
      const entry = this.tryEntries[i];
      if (tryLoc === entry.tryLoc) {
        returnentry.completion.arg; }}},// Handle exception arguments: throw method fires with error value
  dispatchException(exception) {
    // Storage error reported
    // Change the location of next
    // Start matching from the last try catch position
    const context = this;
    function handle(loc, record) {
      record.type = "throw";
      record.arg = exception;
      context.next = loc;
      // return !! caught;
    }
    for (var i = this.tryEntries.length - 1; i >= 0; --i) {
      const entry = this.tryEntries[i];
      const record = entry.completion;
      // Error before first trycatch
      Done: true,value:undefined
      if (entry.tryLoc === "root") {
        return handle("end", record);
      }
      // Verify which try catch is caught
      if (entry.tryLoc <= context.prev) {
        if (context.prev < entry.catchLoc) {
          returnhandle(entry.catchLoc, record); }}}},};// Return value foo itself, regeneratorRuntime.mark mainly changes foo's prototype and __proto__, inheriting some methods next
regeneratorRuntime.mark = function (genFun) {
  // TODO ... Change the prototype and __proto__ of the method passed in
  // genFun.prototype = Object.create(Gp);
  return genFun;
};

/ * * * *@param {*} InnerFn outer foo *@param {*} The foo$in the outerFn inner layer is the swich case method *@param {*} This of foo$inside self we don't use null * here@param {*} TryLocsList error corresponds to the case array of prev and next [[tryLoc,catchLoc]] *@returns {object} Its return value is an object with a next throw return method */
regeneratorRuntime.wrap = function (innerFn, outerFn, self, tryLocsList) {
  // Inherit methods such as next throw
  const generator = Object.create(Gp);
  // Each generator has its own context to store information such as next Prev done
  const _context = new Context(tryLocsList || []);
  // Next throw has more than one method
  // Next requires the innerFn parameter to execute the innerFn
  // innerFn requires the context to determine which step to execute
  generator._invoke = makeInvokeMethod(innerFn, self, _context);
  // Generate
  return generator;
};

// unpack the method
function defineIteratorMethods(prototype) {["next"."throw"."return"].forEach(function (method) {
    prototype[method] = function (arg) {
      return this._invoke(method, arg);
    };
  });
}
defineIteratorMethods(Gp);

// Returns a method called the next throw return method
function makeInvokeMethod(innerFn, self, context) {
  // To distinguish which of the next throws requires a type parameter called method, the method has an arg that needs to be passed
  return function invoke(method, arg) {
    // Add a loop to make it automatically go to the end of the case
    if (method === "throw" && context.done) {
      throw arg;
    }
    if (context.done) {
      return { value: undefined.done: true };
    }
    while (true) {
      if (method === "next") {
        1. Execute the current case
        // 2. Select sent from next switch case
        context.sent = arg;
      } else if (method === "throw") {
        // throw logic saves the error value and modifies the next position
        context.dispatchException(arg);
      } else if (method === "return") {
        context.abrupt("return", arg);
      }
      // Public logic
      const value = innerFn.call(self, context);
      if (value === ContinueSentinel) {
        // Execute the method again when the state is complete and the case end is executed
        continue;
      }
      return { value, done: context.done }; }}; }// After Babel transforms ~

// Return value foo itself, regeneratorRuntime.mark mainly changes foo's prototype and __proto__ can be ignored first
var _marked = /*#__PURE__*/ regeneratorRuntime.mark(foo);

function foo() {
  var res2;
  return regeneratorRuntime.wrap(
    function foo$(_context) {
      while (1) {
        switch ((_context.prev = _context.next)) {
          case 0:
            console.log(1);
            _context.next = 3;
            return "result1";

          case 3:
            _context.next = 5;
            return "result2";

          case 5:
            res2 = _context.sent;
            _context.prev = 6;
            console.log(2, res2);
            _context.next = 10;
            return "result3";

          case 10:
            console.log(2.1);
            _context.next = 16;
            break;

          case 13:
            _context.prev = 13;
            _context.t0 = _context["catch"] (6);
            console.log("e", _context.t0);

          case 16:
            _context.next = 18;
            return "result4";

          case 18:
            return _context.abrupt("return"."result5");

          case 19:
          case "end":
            return _context.stop();
        }
      }
    },
    _marked,
    nullThe [[6.13]]); }var gen = foo();
console.log("next1:", gen.next());
console.log("next2:", gen.next());
console.log("next3:", gen.next());
console.log("return:", gen.return(234234));
// console.log("throw4:", gen.throw(123));
console.log("next5 return:", gen.next());
console.log("next6:", gen.next());

/ / print
/*
1
next1: { value: 'result1', done: false }
next2: { value: 'result2', done: false }
2 undefined
next3: { value: 'result3', done: false }
return: { value: 234234, done: true }
next5 return: { value: undefined, done: true }
next6: { value: undefined, done: true }
*/
Copy the code

Lazy… Meet the needs of over~ source code is roughly the implementation of the simple version of the above interested can go to see the source code, with the above impression, look at the faster

How to implement async await syntax sugar

Babel is a generator syntax. Async is not much different from generator

How is generator used in asynchrony

On 🐴

// simulate the request
const db = {
  post(a, b = true) { // The default value is success a is returned b Control success failure
    return new Promise((resolve, reject) = > {
      setTimeout(() = > {
        if (b) resolve(a);
        reject(a);
      }, 1000); }); }};// db.post What does this request pass in and what does it return
// Make three requests. The value returned by each request is passed to the next request plus 1
function* dbFuc() {
  const res = [];
  res[0] = yield db.post(1);
  res[1] = yield db.post(res[0] + 1);
  res[2] = yield db.post(res[1] + 1);
  return res;
}

// If you do not use generator to invoke asynchrony with the CO module, the following occurs
// gen.next() {value: Promise 
      
       ,done:false}
      
// The second request relies on the result of the first request to form a nested scalp (callback hell)
// The value of the first request is passed to the following code as an argument to the second next, replacing the return value of the first yield
const gen = dbFuc();
gen.next().value.then((res) = > {
  console.log("res1", res); //{ a: 1 }
  gen.next(res).value.then((res1) = > {
    console.log("res2", res1); //{ a: 2 }
    gen.next(res1).value.then((res2) = > {
      console.log("res3", res); //{ a: 3 }
      console.log("return", gen.next(res2)); //{ value: [ 1, 2, 3 ], done: true }
    });
  });
});


// We write a simple co module (i.e. automatic execution generator).
let co = (it) = >
  new Promise((resolve, reject) = > {
    // Async is a callback function;
    function next(data) {
      const { value, done } = it.next(data);
      if (done) {
        resolve(value);
      } else {
        Promise.resolve(value).then(next, reject);
      }
    }
    next();
  });
co(dbFuc()).then((d) = > console.log("d", d));
// Print d [1, 2, 3]
Copy the code

After adding a CO module, generator can be used in async much better. You don’t need to call the value itself, but there is still some trouble. Then you have async/await. Async /await == generator + co

Async /await syntax sugar implementation

General description of async characteristics ~

  1. The async method returns a promise
  2. The returned promise will not change state to success until all code has been executed (with no errors)
  3. Await returns a promise in a failed state, and if the error is not caught, the entire function execution is interrupted
  4. The normal value returned after await is converted to promise.resolve (value) (microtask execution)
  5. Await returns the following value

Directly on the code to steal a lazy ~ line by line explanation

async function dbFuc() {
  const res = [];
  res[0] = await db.post(1);
  res[1] = await db.post(res[0] + 1);
  res[2] = await db.post(res[1] + 1);
  return res;
}

// Convert to generator
function dbFuc1(db) {
  return spawn(function* () {
    const res = [];
    res[0] = yield db.post(1);
    res[1] = yield db.post(res[0] + 1);
    res[2] = yield db.post(res[1] + 1);
    return res;
  });
}

function spawn(genF) {
  // Return a Promise
  return new Promise(function (resolve, reject) {
    const gen = genF(); // Get the traverser object for automatic execution

    function step(nextF) {
      let next;
      try {
        next = nextF(); // Execute gen.next() with method wrap to make it easier to unify the try catch here
      } catch (e) {
        return reject(e); // Return the promise state to Rejected if next fails
      }
      if (next.done) { // If this is true, it means that the code is executed at the end, and the return value will be directly passed to the return promise, and the state will change without depressing
        return resolve(next.value);
      }
      // Use the promise.resolve conversion value to ensure that subsequent (microtask execution)
      Promise.resolve(next.value).then(
        function (v) { // get the argument passed to gen.next and return the value before await(yeild);
          // Repeat the call for the next step
          step(function () {
            return gen.next(v);
          });
        },
        function (e) { If the generator has a trycatch, it will be caught internally. If it does not, it will be caught by a trycatch on the generator
          step(function () {
            // Generator's throw method can throw an error outside a function and then catch it inside a generator function
            When the throw method is caught, an error is reported and the next yield is executed
            // Interrupts the generator function if no error is caught internally
            returngen.throw(e); }); }); }// Execute the first next directly when the method is called
    step(function () {
      return gen.next(undefined);
    });
  });
}

Copy the code

Good luck finally finished, referred to the article of some big guy, invade delete.