Promise to realize

In traditional asynchronous programming, if asynchronous dependencies between, we need to meet this dependence, through layers of nested callbacks if nested layers is overmuch, have become very poor readability and maintainability, produce the so-called “callback hell”, and Promise will change for chain embedded callback invocation, readability and maintainability. Let’s take a step-by-step approach to fulfilling a Promise

1. Observer mode

Let’s start with the simplest Promise to use:

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('result')
    },
    1000);
})
 
p1.then(res => console.log(res), err => console.log(err))
Copy the code

Looking at this example, let’s examine the Promise invocation flow:

  • PromiseThe constructor of theexecutor()In thenew Promise()Execute the executor callback immediately
  • executor()Internal asynchronous tasks are put into macro/micro task queues, waiting to execute
  • then()Is executed, the success/failure callback is collected, and placed in the success/failure queue
  • executor()The asynchronous task is executed and triggeredresolve/reject, fetching the callback from the success/failure queue and executing it in sequence

In fact, those familiar with design patterns can easily realize that this is the “observer pattern”. This method of collecting dependencies -> triggering notification -> extracting dependency execution is widely used in the implementation of the observer pattern. In the Promise, The order of execution is then collect dependencies -> asynchronously trigger resolve -> resolve execute dependencies. From there, we can outline the shape of promises:

Class MyPromise {// The constructor receives a callback constructor(executor) {this._resolveQueue = [] // then the collection of successfully executed callback queues this._rejectQueue = [] // Then collection of failed callback queue // Since resolve/reject is called inside executor, we need to fix this with the arrow function. This._resolvequeue let _resolve = (val) => {while(this._resolvequeue.length) {const callback = Resolve let _reject = (val) => {while(this._rejectQueue.length) {this._rejectQueue.length) { Const callback = this._rejectQueue.shift() callback(val)}} // New Promise() immediately executes executor and passes resolve and reject Executor (_resolve, _reject)} // then method that accepts a successful callback and a failed callback, Then (resolveFn, rejectFn) {this._resolveQueue.push(resolveFn) this._rejectQueue.push(rejectFn)}}Copy the code

After writing the code, we can test it:

const p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve('result') }, 1000); }) p1.then(res => console.log(res)) // Output result after one secondCopy the code

We implemented then and resolve in a simple way using the observer pattern, allowing us to retrieve the return value of an asynchronous operation in a callback to the THEN method. However, we are still a long way from implementing this Promise.

2. Promise A+ specification

We’ve simply implemented an ultra-low-spec Promise above, but we’ll see A lot of articles that are different from what we’ve written. They also introduce various state controls into their Promise implementations, because ES6 Promise implementations need to follow the Promise/A+ specification. It is the specification that requires state control for promises. The Promise/A+ specification is long, and there are only two core rules summarized here:

  1. Promise is essentially a state machine, and the states can only be the following three:Pending,This is a big pity.,Rejected (= Rejected), the state change is one-way, and can only be irreversible from Pending -> depressing or Pending -> Rejected
  2. Then methodReceives two optional parameters, one for the callback triggered when the state changes. The then method returns a promise. The then method can be called multiple times by the same promise.

According to the spec, we’ll add the Promise code:

Const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'FULFILLED' class MyPromise Constructor (executor) {this._status = PENDING // Promise state this._resolveQueue = [] // Success queue, This._rejectQueue = [] // reject // This._rejectQueue = [] // Reject // This. This._resolveQueue let _resolve = (val) => {if(this._status! This._status = depressing // The state can only be fulfilled PENDING or rejected. This The reason for using a queue to store callbacks is to implement the specification's requirement that "then methods can be called more than once by the same promise "// If a variable is used instead of a queue to store callbacks, only one callback will be executed, even if multiple p1.then() callbacks are stored While (this._resolvequeue.length) {const callback = this._resolvequeue.shift () callback(val)} _reject = (val) => { if(this._status ! == PENDING) return // "This._status = rejected // This while(this._rejectQueue.length) { const callback = this._rejectQueue.shift() callback(val) } } // new Promise() executes executor immediately, passing resolve and reject executor(_resolve, _reject)} // THEN methods, receiving a successful callback and a failed callback then(resolveFn, rejectFn) { this._resolveQueue.push(resolveFn) this._rejectQueue.push(rejectFn) } }Copy the code

3. Chain calls to THEN

After completing the specification, we will implement the chain invocation. This is the key and difficult part of the Promise implementation. Let’s first look at how the then chain invocation works:

const p1 = new Promise((resolve, Reject) => {resolve(1)}) p1. Then (res => {console.log(res)) reject) => { setTimeout(() => { resolve(2) }, 1000); })}). Then (res => {console.log(res) //then callback can return a value return 3}). Then (res => {console.log(res)}).Copy the code

The output

1
2
3
Copy the code

Let’s think about how to implement this chain call:

  1. clearly.then()We need to return a Promise in order to find the THEN method, so we wrap the return value of the THEN method as a Promise.
  2. .then()Return Promise (1->2->3); return Promise (2->3) We wait for the current Promise state to change before executing the next THEN collection callback, which requires a categorized discussion of the return value of the THEN
Then (resolveFn, rejectFn) {return new promise (resolveFn, rejectFn); Reject) => {// resolveFn (reject) => {// resolveFn (reject) => {// resolveFn (reject) => {// resolveFn (reject) => { // Execute the success callback for the first (current)Promise and get the return value let x = resolveFn(value). If it is a Promise, wait for the Promise state to change, otherwise resolve x instanceof Promise ? x.then(resolve, reject) : Resolve (x)} catch (error) {reject(error)}} // Push subsequent dependencies into the success callback queue of the current Promise (_rejectQueue), //reject const rejectedFn = error => {try {let x = rejectFn(error) x  instanceof Promise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } this._rejectQueue.push(rejectedFn) }) }Copy the code

Then we can test the chain call:

const p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 500); }) p1 .then(res => { console.log(res) return 2 }) .then(res => { console.log(res) return 3 }) .then(res => { Console. log(res)}) // Outputs 1, 2, and 3Copy the code

4. Value penetration & condition where the state has changed

We’ve done the initial chain call, but there are two more details we need to work out for the then() method

  1. Value penetration: According to the specification, if then() receives a parameter other than function, then we should ignore it. If not ignored, an exception will be thrown when the then() callback is not function, causing the chain call to break
  2. Process the resolve/ Reject statusThen (); then()paddingIn some cases, resolve/reject is executed before then() (e.gPromise.resolve().then()If the then() callback is pushed into the resolve/reject queue, then the callback will not be executed, so the state has becomefulfilledorrejectedIn this case, we execute the THEN callback directly:
Then (resolveFn, rejectFn) {// If the then argument is not function, We need to ignore it and let the chained call proceed to typeof resolveFn! == 'function' ? resolveFn = value => value : null typeof rejectFn ! == 'function' ? rejectFn = error => error : Null // return a new promise return new promise (resolve, Reject) => {// Select resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject) Execute the success callback for the first (current)Promise and get the return value let x = resolveFn(value) // class discuss the return value, if it is a Promise, wait for the Promise state to change, otherwise resolve x instanceof Promise ? x.then(resolve, reject) : Resolve (x)} catch (error) {reject(error)}} // Reject Likewise const rejectedFn = error => {try {let x = rejectFn(error) x  instanceof Promise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } switch (this._status) { // When the state is PENDING, push the THEN callback into the resolve/ REJECT queue and wait for case Pending: this._resolveQueue.push(fulfilledFn) this._rejectQueue.push(rejectedFn) break; // This will be a pity: // This will be a pity. This._value is the value of the last then callback return (see the full version of the code) break; case REJECTED: rejectedFn(this._value) break; }})}Copy the code

5. Compatible with synchronization tasks

Once we’ve done the chain call to THEN, we’ll work on one of the preceding details and then release the full code. As we said earlier, promises are executed in the order new Promise -> then() collect callbacks -> resolve/reject callbacks. This order is based on the premise that executors are asynchronous tasks. If executors are synchronous tasks, New Promise -> resolve/reject callback -> then() collect the callback. Resolve runs before THEN. We give the resolve/ Reject callback package a setTimeout to execute asynchronously.

In addition, there is a lot of knowledge about setTimeout. While the specification doesn’t say whether callbacks should be placed in a macro or microtask queue, the default implementation of Promise is actually put in a microtask queue, Our implementation (including most Promise manual implementations and polyfill transformations) uses setTimeout to queue macro tasks (of course we can also simulate microtasks using MutationObserver)

Const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'FULFILLED' class MyPromise Constructor (executor) {this._status = PENDING // Promise state this._value = undefined // Stores the value of then callback return This. _resolveQueue = []; resolve = []; Since resolve/reject is called inside the executor, we need to fix this with the arrow function, This. _resolveQueue let _resolve = (val) => {// Encapsulate the resolve callback into a function and put it in setTimeout to be compatible with synchronous executor code const run =  () => { if (this._status ! This._value = val // this. // This // If a variable is used instead of a queue to store a callback, p1.then() will only perform one callback while (this._resolveQueue.length) { const callback = this._resolveQueue.shift() callback(val) } } setTimeout(run) } // Resolve let _reject = (val) => {const run = () => {if (this._status! This._status = rejected // This._value = val // this While (this._rejectQueue.length) {const callback = this._rejectQueue.shift() callback(val)}} SetTimeout (run)} // New Promise() executes executor immediately and passes resolve and reject executor(_resolve, Reject) {// Then (resolveFn, rejectFn) {// If the then argument is not function, We need to ignore it and let the chained call proceed to typeof resolveFn! == 'function' ? resolveFn = value => value : null typeof rejectFn ! == 'function' ? rejectFn = reason => { throw new Error(reason instanceof Error ? reason.message : reason); } : Null // return a new promise return MyPromise((resolve, Reject) => {// Select resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject) Execute the success callback for the first (current)Promise and get the return value let x = resolveFn(value) // class discuss the return value, if it is a Promise, wait for the Promise state to change, otherwise resolve x instanceof MyPromise ? x.then(resolve, reject) : Resolve (x)} catch (error) {reject(error)}} // Reject Likewise const rejectedFn = error => {try {let x = rejectFn(error) x  instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } switch (this._status) { // When the state is PENDING, push the THEN callback into the resolve/ REJECT queue and wait for case Pending: this._resolveQueue.push(fulfilledFn) this._rejectQueue.push(rejectedFn) break; // This will be a pity: // This will be a pity. This._value is the value of the last then callback return (see the full version of the code) break; case REJECTED: rejectedFn(this._value) break; }}}})Copy the code

Then we can test this Promise:

const p1 = new MyPromise((resolve, Reject) => {resolve(1) // Synchronize Executor tests}) p1.then (res => {console.log(res) return 2 // chain call tests}).then() // Value pass test .then(res => { console.log(res) return new MyPromise((resolve, Reject) => {resolve(3) // return Promise test})}). Then (res => {console.log(res) throw new Error('reject test ') //reject test}) . Then (,) = > {}, err = > {the console. The log (err)}) / / output / / 1 / / 2/3 / / Error: reject testCopy the code

By now, we’ve implemented the main functionality of Promise (‘ ∀´) and the other few methods are pretty simple, and we just cleaned them up:

Promise.prototype.catch()

The catch() method returns a Promise and handles the rejection. It behaves the same as calling promise.prototype. then(undefined, onRejected).

{return this.then(undefined, rejectFn)}}Copy the code

Promise.prototype.finally()

The finally() method returns a Promise. At the end of the promise, the specified callback function will be executed, whether the result is fulfilled or Rejected. After finally, you can continue then. And passes the value exactly as it should to the subsequent then

// Finally method finally(callback) {return this.then(value => mypromise.resolve (callback())).then(() => value), Resolve => myPromise.resolve (callback()). Then () => {throw Reason }) // rejectCopy the code

Promise.resolve()

The promise.resolve (value) method returns a Promise object resolved with the given value. If the value is promise, return the promise; If the value is thenable (that is, with the “then” method), the returned promise “follows “the thenable object and adopts its final state; Otherwise the returned promise will be fulfilled with this value. This function flattens out the multiple layers of nesting of promise-like objects.

Static resolve(value) {if(value instanceof MyPromise) return value Return new MyPromise(resolve => resolve(value))}Copy the code

Promise.reject()

The promise.reject () method returns a Promise object with a reason for the rejection.

Reject (reject) {return new MyPromise((resolve, reject) => reject(reason))}Copy the code

Promise.all()

The promise.all (iterable) method returns an instance of a Promise that is resolved when all promises in iterable arguments are “resolved” or when the arguments do not contain the Promise; If a promise fails (Rejected), the instance calls back (reject) because of the result of the first failed promise.

Static all(promiseArr) {let index = 0 let result = [] return new MyPromise((resolve, resolve) reject) => { promiseArr.forEach((p, Resolve (p) mypromise.resolve (p). Then (val => {index++ result[I] = val) Resolve if(index === promisear.length) {resolve(result)}}, err => { The state of MyPromise changes to Reject reject(err)})})})}Copy the code

Promise.race()

The promise.race (iterable) method returns a Promise that is resolved or rejected once a Promise in the iterator is resolved or rejected.

static race(promiseArr) { return new MyPromise((resolve, Reject) => {// Implement a new state for (let p of promiseArr) {promise.resolve (p).then() Resolve (p) => {resolve(value) => {resolve(value)}; err => { reject(err) } ) } }) }Copy the code

The complete code

Const PENDING = 'PENDING' const FULFILLED = 'FULFILLED' const REJECTED = 'FULFILLED' class MyPromise Constructor (executor) {this._status = PENDING // Promise state this._value = undefined // Stores the value of then callback return This. _resolveQueue = []; resolve = []; Since resolve/reject is called inside the executor, we need to fix this with the arrow function, This. _resolveQueue let _resolve = (val) => {// Encapsulate the resolve callback into a function and put it in setTimeout to be compatible with synchronous executor code const run =  () => { if(this._status ! This._value = val // this. // This // If a variable is used instead of a queue to store a callback, p1.then() will only perform one callback while(this._resolveQueue.length) { const callback = this._resolveQueue.shift() callback(val) } } setTimeout(run) } // Resolve let _reject = (val) => {const run = () => {if(this._status! This._status = rejected // This._value = val // this While (this._rejectQueue.length) {const callback = this._rejectQueue.shift() callback(val)}} setTimeout(run) } // New Promise() executes executor immediately and passes resolve and reject executor(_resolve, Reject) {// Then (resolveFn, rejectFn) {// If the then argument is not function, We need to ignore it and let the chained call proceed to typeof resolveFn! == 'function' ? resolveFn = value => value : null typeof rejectFn ! == 'function' ? rejectFn = error => error : Null // return a new promise return new promise (resolve, Reject) => {// Select resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject), resolveFn (reject) Execute the success callback for the first (current)Promise and get the return value let x = resolveFn(value) // class discuss the return value, if it is a Promise, wait for the Promise state to change, otherwise resolve x instanceof Promise ? x.then(resolve, reject) : Resolve (x)} catch (error) {reject(error)}} // Reject Likewise const rejectedFn = error => {try {let x = rejectFn(error) x  instanceof Promise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } switch (this._status) { // When the state is PENDING, push the THEN callback into the resolve/ REJECT queue and wait for case Pending: this._resolveQueue.push(fulfilledFn) this._rejectQueue.push(rejectedFn) break; // This will be a pity: // This will be a pity. This._value is the value of the last then callback return (see the full version of the code) break; case REJECTED: rejectedFn(this._value) break; Catch (rejectFn) {return this.then(undefined, Return this.then(value => myPromise.resolve (callback()).then(() => value), Resolve (callback()). Then (() => {throw reason}) //reject)} Static resolve(value) {if(value instanceof MyPromise) return value Return new MyPromise(resolve => resolve(value))} static reject(reason) {return new MyPromise((resolve, Reject) => reject(reason))} static all(promiseArr) {let index = 0 let result = [] return new MyPromise((resolve, reject) => { promiseArr.forEach((p, Resolve (p) mypromise.resolve (p). Then (val => {index++ result[I] = val if(index ===)  promiseArr.length) { resolve(result) } }, Err => {reject(err)})})} static race(promiseArr) {return new MyPromise((resolve, Reject) => {// Implement a new state for (let p of promiseArr) {promise.resolve (p).then() Resolve (p) => {resolve(value) => {resolve(value)}; err => { reject(err) } ) } }) } }Copy the code

Over 150 lines of code, and at this point we’re finally ready to wrap up the Promise implementation. We start with A simple Promise use example, through the analysis of the call flow, implement the rough skeleton of the Promise according to the observer pattern, then fill in the code according to the Promise/A+ specification, focus on the implementation of the then chain call, and finally complete the static/instance method of the Promise. In fact, the Promise implementation on the whole is not too complex ideas, but we often ignore a lot of Promise details in daily use, so it is difficult to write a standardized Promise implementation, source code implementation process, in fact, is the process of relearning the use of Promise details.

Async/await

While we’ve spent a lot of time talking about Promise implementation, exploring the mechanism for suspending async/await execution is what we started off with, so let’s get into that. Again, we start with the async/await meaning. In multiple callback dependent scenarios, although Promise replaced callback nesting by chain invocation, excessive chain invocation is still not readable and the flow control is not convenient. The async function proposed in ES7 finally provides JS with the ultimate solution for asynchronous operation, which solves the above two problems in a concise and elegant way.

Imagine a scenario where there are dependencies between asynchronous tasks A -> B -> C. If we handle these relationships through then chain calls, the readability is not very good. If we want to control one of these processes, for example, under certain conditions, B does not go down to C, then it is not very easy to control either

Promise.resolve(a)
  .then(b => {
    // do something
  })
  .then(c => {
    // do something
  })
Copy the code

But if this scenario is implemented with async/await, readability and flow control will be much easier.

async () => {
  const a = await Promise.resolve(a);
  const b = await Promise.resolve(b);
  const c = await Promise.resolve(c);
}
Copy the code

While we’ve spent a lot of time talking about Promise implementation, exploring the mechanism for suspending async/await execution is what we started off with, so let’s get into that. Again, we start with the async/await meaning. In multiple callback dependent scenarios, although Promise replaced callback nesting by chain invocation, excessive chain invocation is still not readable and the flow control is not convenient. The async function proposed in ES7 finally provides JS with the ultimate solution for asynchronous operation, which solves the above two problems in a concise and elegant way.

Imagine a scenario where there are dependencies between asynchronous tasks A -> B -> C. If we handle these relationships through then chain calls, the readability is not very good. If we want to control one of these processes, for example, under certain conditions, B does not go down to C, then it is not very easy to control either

Promise.resolve(a)
  .then(b => {
    // do something
  })
  .then(c => {
    // do something
  })
Copy the code

But if this scenario is implemented with async/await, readability and flow control will be much easier.

async () => {
  const a = await Promise.resolve(a);
  const b = await Promise.resolve(b);
  const c = await Promise.resolve(c);
}
Copy the code

How do we implement an async/await? First we need to know that “async/await is actually a wrapper around a Generator”, which is a syntactic sugar. As Generator has been replaced by async/await soon, many students are unfamiliar with Generator, so let’s take a look at its usage:

ES6 introduces Generator functions that suspend the execution flow of functions using the yield keyword and switch to the next state using the next() method, providing the possibility to change the execution flow and thus providing a solution for asynchronous programming.

function* myGenerator() { yield '1' yield '2' return '3' } const gen = myGenerator(); / / get the iterator gen. Next () / / {value: "1", done: false} gen. The next () / / {value: "2", done: false} gen. The next () / / {value: "3", done: true}Copy the code

Yield can also be given a return value by passing an argument to next()

function* myGenerator() { console.log(yield '1') //test1 console.log(yield '2') //test2 console.log(yield '3') //test3 } Const gen = myGenerator(); // Get iterator const gen = myGenerator(); gen.next() gen.next('test1') gen.next('test2') gen.next('test3')Copy the code

️ should be familiar with the use of Generator. */yield and async/await look very similar in that they both provide the ability to pause execution, but there are three differences:

  • async/awaitThe built-in actuator automatically executes the next step without manually calling next()
  • asyncThe function returns a Promise object and the Generator returns a Generator object
  • awaitCan return the resolve/ Reject value of Promise

Our implementation of async/await corresponds to the above three encapsulation generators

1. Automatic execution

Let’s take a look at the process of manual execution for such a Generator

function* myGenerator() { yield Promise.resolve(1); yield Promise.resolve(2); yield Promise.resolve(3); } const gen = myGenerator() gen.next().value.then(val => { console.log(val) gen.next().value.then(val => { Console.log (val) gen.next().value.then(val => {console.log(val)})})}) // Output 1, 2, 3Copy the code

Yield can also return resolve by passing a value to Gen.next ()

function* myGenerator() { console.log(yield Promise.resolve(1)) //1 console.log(yield Promise.resolve(2)) //2 console.log(yield Promise.resolve(3)) //3 } const gen = myGenerator() gen.next().value.then(val => { // console.log(val)  gen.next(val).value.then(val => { // console.log(val) gen.next(val).value.then(val => { // console.log(val) gen.next(val) }) }) })Copy the code

Obviously, manual execution looks awkward and ugly. We want generator functions to automatically execute down and yield to return resolve. Based on these two requirements, we do a basic wrapper where async/await is the keyword and cannot be overridden.

Function run(gen) {var g = gen(); function run(gen) {var g = gen(); Next () var res = g.ext (val) If (res.done) return res.value. Then (val => {//Promise's then method is the precondition for automatic iteration step(val) // Wait for the Promise to complete, automatically execute the next, and pass in the resolve value})} step()Copy the code

For our previous example, we could do this:

function* myGenerator() {
  console.log(yield Promise.resolve(1))   //1
  console.log(yield Promise.resolve(2))   //2
  console.log(yield Promise.resolve(3))   //3
}
 
run(myGenerator)
Copy the code

This gives us an initial implementation of async/await

The above code is only five or six lines long, but it is not easy to understand. We have used four examples to help readers understand the code better. Simply put, we encapsulate a run method that encapsulates the next step as step() and executes step() each time promise.then () to achieve automatic iteration. During the iteration, we also pass the value of resolve to gen.next(), allowing yield to return the Promise’s value of resolve

By the way, is it only the.then method that does what we do automatically? The thunk function is nothing new. The thunk function is a single-parameter function that accepts only callbacks. At its core, both the Promise and thunk functions implement automatic execution of the Generator by “passing in a callback” **. Thunk function only as an extension of knowledge, students who have difficulty in understanding can also skip this, does not affect the subsequent understanding.

2. Return Promise & exception handling

While we have implemented automatic execution of Generator and yield returning resolve, there are a few problems with the above code:

  1. “Need to be compatible with base types”: This code can be executed automatically only ifyieldThe Promise is followed by the Promise. In order to be compatible with cases where a value of a primitive type is followed by a value, we need to combine yield with the content (gen().next.value) withPromise.resolve()Translate it again
  2. “Lack of error handling”If the Promise in the code above fails to execute, it will interrupt the subsequent execution directlyGenerator.prototype.throw()Throw the error to be caught by the outer try-catch
  3. “Return value is Promise”:async/awaitThe return value is a Promise, and we need to be consistent here by giving the return value package a Promise

Let’s modify the run method:

Function run(gen) {return new promise ((resolve, resolve); Reject) => {var g = gen() function step(val) {var res = g.ext (val)} catch(err) {return reject(err); } if(res.done) { return resolve(res.value); Promise. Resolve (res.value). Then (val => {step(val); }, err => {// throw error g.row (err)}); } step(); }); }Copy the code

Then we can test it:

function* myGenerator() { try { console.log(yield Promise.resolve(1)) console.log(yield 2) //2 console.log(yield Promise.reject('error'))} Catch (error) {console.log(error)}} const result = run(myGenerator) // Result is a Promise // Output 1, 2 errorCopy the code

At this point, an async/await implementation is almost complete. Async /await = async/await = async/await = async/await = async/await

Function () {return function() {var self = this var args = arguments return new Promise(function(resolve, reject) { var gen = fn.apply(self, args); // step() function _next(value) {asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value); } function _throw(err) {asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err); } _next(undefined); }); }; } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); }}Copy the code

Usage:

const foo = _asyncToGenerator(function* () {
  try {
    console.log(yield Promise.resolve(1))   //1
    console.log(yield 2)                    //2
    return '3'
  } catch (error) {
    console.log(error)
  }
})
 
foo().then(res => {
  console.log(res)                          //3
})
Copy the code

So much for the async/await implementation. However, we do not know how await is suspended until the end, and we have to look into the implementation of Generator to find out the secret of await

The Generator to realize

Let’s start with a simple example and explore the implementation of Generator step by step:

function* foo() {
    yield 'result1'
    yield 'result2'
    yield 'result3'
}

const gen = foo()
console.log(gen.next().value)
console.log(gen.next().value)
console.log(gen.next().value)
Copy the code

We can translate this code online at Babel to see how Generator is implemented in ES5:

"use strict";

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

function foo() {
    return regeneratorRuntime.wrap(function foo$(_context) {
        while (1) {
            switch (_context.prev = _context.next) {
                case 0:
                    _context.next = 2;
                    return 'result1';

                case 2:
                    _context.next = 4;
                    return 'result2';

                case 4:
                    _context.next = 6;
                    return 'result3';

                case 6:
                case "end":
                    return _context.stop();
            }
        }
    }, _marked);
}

var gen = foo();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
Copy the code

The code doesn’t look very long at first glance, but if you look closely you’ll notice that there are two things you don’t recognize: RegeneratorRuntime. mark and RegeneratorRuntime. wrap, which are two methods in the Regenerator-Runtime module. The regenerator module comes from the Facebook Regenerator module. The complete code is in runtime.js. This Runtime has more than 700 lines… -_ – | |, so we can’t speak, don’t too important part of the us is simply too, focuses on suspended related part of the code

I think the effect of gnawing source code is not very good, suggest that readers pull to the end of the first look at the conclusion and brief version of the implementation, the source code do not do too much explanation

Low profile implementation & call flow analysis

The source code is packed with many concepts and packages that may not be fully understood for a while, so let’s jump out of the source code and implement a simple Generator

// The generator function splits the code into switch-case blocks based on the yield statement, Case function gen$(_context) {while (1) {switch (_context.prev = _context.next) { case 0: _context.next = 2; return 'result1'; case 2: _context.next = 4; return 'result2'; case 4: _context.next = 6; return 'result3'; case 6: case "end": return _context.stop(); }}} let context = {next: 0, prev: 0, done: false, stop: Function stop() {this.done = true}} // invoke let gen = function () {return {next: function () { value = context.done ? Undefined: gen$(context) done = context.done return {value, done}}}} let g = gen() g.ext () // {value: "result1", done: false} g.next() // {value: "result2", done: false} g.next() // {value: "result3", done: false} g.next() // {value: undefined, done: true}Copy the code

This code is not difficult to understand, let’s examine the call flow:

  1. We definedfunction*The generator function is converted to the above code
  2. The transformed code is divided into three chunks:
  • gen$(_context)The yield splits the generator function code
  • The context objectUsed to store the execution context of a function
  • Invoke () methodDefine next() to execute gen$(_context) to skip to the next step
  • When we callg.next(), is equivalent to callingInvoke () method, the implementation ofgen$(_context), enter the switch statement, switch executes the corresponding case block according to the context identifier, return corresponding result
  • When the generator function runs to the end (there is no next yield or has already returned) and the switch does not match the corresponding code block, the return is nullg.next()return{value: undefined, done: true}
  • The function is not actually suspended. Each yield executes the incoming Generator function, but a context object is used to store the context so that each time a Generator function is executed, Can be executed from the last execution result, as if the function was suspended.”

Simple implementation of Axios

Finally, to implement the most common ajax solution today, Axiox, Promise, passes in a function that takes resolve and reject.

  1. This is a big pity. When the Promise state becomes fulfilled, resolve is called
  2. Reject is called when the Promise state changes to Rejected
function axios(ajaxObj) { const {url, method, data} = ajaxObj return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open(method, url) xhr.onreadystatechange = () => { if (xhr.readyState ! == 4) { return } if (xhr.status === 200) { resolve(xhr.response) } else { reject(xhr.statusText) } } xhr.send(data) }) }Copy the code

You can use.then() instead.

function sendRequest() {
    const ajaxObj = {
        url: 'https://cnodejs.org/api/v1/topics',
        method: 'GET',
        data: ''
    }
    axios(ajaxObj)
        .then((response) => {
            console.log('resolve')
            console.log(response)
        })
        .catch((error) => {
            console.error(error)
        })
}
sendRequest();
Copy the code

Summary & Acknowledgements

Juejin. Cn/post / 684490…