preface

1. Advanced WEB interviews will have you hand-write a Promise,Generator PolyFill(a piece of code); 2. Before we start writing, let’s briefly review what they do. 3. For the handwriting module, see PolyFill.

The source code

Source address please stamp, original code word is not easy, welcome star

If you think the article is too verbose, you can directly git Clone, directly look at the code

1.Promise

1.1 role

You’ve probably all used promises, and ajax libraries are wrapped with promises; The main function is to solve the hell callback problem.

1.2 the use of

1.2.1. Method a

New Promise((resolve,reject)=>{resolve(' this is the first resolve value ')}).then((data)=>{console.log(data) // Will print 'this is the first resolve value'. }). The catch (() = > {}) new Promise ((resolve, reject) = > {reject (' this is the first to reject values')}), then ((data) = > {the console. The log (data) }).catch((data)=>{console.log(data) // Prints' This is the first reject value '})Copy the code

1.2.2. Method 2 (Static Method)

Promise.resolve(' This is the second resolve value ').then((data)=>{console.log(data) // will print 'this is the second resolve value'}) promise.resolve (' This is the second Reject ((data)=>{console.log(data)}).catch(data=>{console.log(data) // This is the second reject value})Copy the code

1.2.3. Method 3 (Multiple Promises perform asynchronous operations in parallel)

This will be FulFilled someday. This will be FulFilled someday

const pOne = new Promise((resolve, reject) => { resolve(1); }); const pTwo = new Promise((resolve, reject) => { resolve(2); }); const pThree = new Promise((resolve, reject) => { resolve(3); }); Promise.all([pOne, pTwo, pThree]).then(data => { console.log(data); }, err => {console.log(err);}, err => {console.log(err); // Any error message});Copy the code

1.2.4. Method 4 (one of many works)

This will be FulFilled someday, which means that only one of the promises will be FulFilled

const pOne = new Promise((resolve, reject) => { resolve(1); }); const pTwo = new Promise((resolve, reject) => { resolve(2); }); const pThree = new Promise((resolve, reject) => { // resolve(3); }); Promise.race([pOne, pTwo, pThree]).then(data => { console.log(data); This is very depressing. This is very depressing. This is very depressing. // Any error message});Copy the code

1.3 Function analysis

1.3.1 Parameters and Status

1.Promise accepts a function, handle, as an argument, Handle including resolve and reject the parameters of the two is the function 2. Promise is equivalent to a state machine, there are three status: pending, fulfilled, rejected, the initial state for the pending 3. This is fulfilled fulfilled. The state changes from pending => fulfilled 4. Reject => rejected 5. Change doesn’t change

1.3.2 then method

This function accepts two optional parameters, onFulfilled and onRejected

2. Non-functions must be ignored

3. OnFullfilled: A. Must be called when the Promise state becomes successful. Its first argument is the value passed in the promise’s success state (resolve is the value passed in the execution; B. A promise must not be called before its state changes. C. A promise must not be called more than once

4. OnFullfilled: Similar to onFullfilled, but promise failed

5. The then method can be called in A chain a. Return A new Promise B each time. Execution rules and error catching: The return value of then is directly used as the next new Promise parameter if it is not a Promise, and waits for the Promise to execute if it is

Let promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve()}, Then (res => {console.log(res)}) promise2.then(res => {console.log(res)) // Print after 1 second: Let promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve()}, Return new Promise((resolve, resolve, resolve)) Reject) => {setTimeout(() => {resolve(' return a Promise here ')}, 2000)})}) promise.then (res => {console.log(res) //Copy the code

C. This is fulfilled or onRejected throws an exception e. Promise2 must be Rejected and return the value e

let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, Then (res => {throw new Error(' e')}) then(res => {console.log(res)}) Err => {console.log(err) // Print after 1 second: this throws an exception e})Copy the code

This function is Fulfilled and the state of promise1 is Fulfilled. Promise2 must become Fulfilled and return the value of “Fulfilled”

let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, This is very sad, this is very sad, this is very sad, this is very sad, this is very sad, this is very sad, this is very sad, this is very sad, this is very sad, this is very sad success }, err => { console.log(err) })Copy the code

E.onRejected is not a function and the state of promise1 is Rejected. Promise2 must become Rejected and return the value of promise1 failed

let promise1 = new Promise((resolve, reject) => { setTimeout(() => { reject('fail') }, 1000)}) promise2 = promise.then (promise2 = promise.then (promise2 = promise.then) Then (res => {console.log(res)}, err => {console.log(err) // Print: fail})Copy the code

F.resolve and Reject end a Promise by calling G.catch. The complexity of catching an exception is in the then exception handling, but take your time

1.3.3 method

See 1.3.1 Usage 2 for the static resolve method

2. Refer to 1.3.1 Usage 2 for static reject

3. For static ALL methods, see 1.3.1 Usage 3

See 1.3.1 Usage 4 for static RACE methods

A Promise object’s callback chain, either ending in a THEN or catch method, may not be caught if the last method throws an error (because errors inside a Promise do not bubble globally). Always be at the end of the callback chain and be sure to throw any possible errors

Promise.prototype.done = function (onFulfilled, onRejected) { this .then(onFulfilled, OnRejected).catch(function (rejected) {setTimeout(() => {throw reason}, 0)})}Copy the code

The finally method is used to specify actions that will be performed regardless of the final state of the Promise object. The biggest difference between the finally method and the done method is that it takes a normal callback function as an argument, which must be executed anyway

Promise.prototype.finally = function (callback) {
    let P = this.constructor
    return this.then(
      value => P.resolve(callback()).then(() => value),
      reason => P.resolve(callback()).then(() => {
        throw reason
      })
    )
  }
Copy the code

1.4 PolyFill

1.4.1 primary version

Class MyPromise {constructor (handle) {// determine whether a handle is valid or not. =='function') {throw new Error('MyPromise must accept a function as a parameter')} this._status =' PENDING' // Add state this._value = undefined // Execute handle try {handle(this._resolves.bind (this), } catch (err) {this._reject(err)}} // Add the function _resolve (val) {if (this._status! This is very sad. This is very sad. This is very sad. == 'PENDING') return this. == 'PENDING') return this._status = 'REJECTED' this._value = err } }Copy the code

In retrospect, the primary version of the implementation of 1,2,3 these three functions, how or so-easy!

Intermediate version 1.4.2

1. Since the THEN method supports multiple calls, we can maintain two arrays, add the callback function from each THEN method registration to the array, and wait for execution to add the success callback queue and the failure callback queue and then method on an elementary basis

this._fulfilledQueues = []
this._rejectedQueues = []
Copy the code

2. Then method

This is very depressing. This is very depressing. Then (onFulfilled, onRejected) {const {_value, _status} = this switch (_status) { Add the then method callback to the execution queue for case 'PENDING': This._realized queues. Push (onFulfilled) this._rejectedqueues. Push (onRejected) break // When the state has changed, This will be a pity pity: case 'FULFILLED': onvalue (_value) break case 'REJECTED': Return MyPromise((onRejectedNext, onRejectedNext) => {})}Copy the code

3. The rules of the THEN method are perfect

This is very depressing. This is very depressing. This is very depressing. _status} = this // Return a new Promise object return new MyPromise((onrollednext, This is a pity pity. This is a pity pity. OnRejectedNext => {if (typeof onFulfilled! =='function') { onFulfilledNext(value) } else { let res = onFulfilled(value); If (res instanceof MyPromise) {// If the current callback returns a MyPromise object, Res.then (onledNext, onRejectedNext)} else {// Otherwise, the returned result is passed directly as an argument to the next then callback function. And immediately execute the next then callback function ontabled next (res)}}} catch (err) {// If the function execution goes wrong, OnRejectedNext (err)}} let rejectednext = error => {try {if (typeof onRejected! =='function') { onRejectedNext(error) } else { let res = onRejected(error); If (res instanceof MyPromise) {// If the current callback returns a MyPromise object, Res.then (onledNext, onRejectedNext)} else {// Otherwise, the returned result is passed directly as an argument to the next then callback function. And immediately execute the next then callback function ontabled next (res)}}} catch (err) {// If the function execution goes wrong, OnRejectedNext (err)}} switch (_status) {// Add the then callback function to the execution queue for case 'pending' when the status is pending: This._realized queues. Push (fulfilled) this._rejectedqueues. Push (rejected) break // When the state has changed, Case 'FULFILLED': FULFILLED (_value) break case 'REJECTED': REJECTED (_value) break}})}Copy the code

4. When the resolve or reject method is executed, we successively extract the functions in the successful or failed task queue to start execution and empty the queue, thus realizing multiple calls of the THEN method

_resolve (val) {if (this._status! Const run = () => {this._status = this._value = val let cb; this is depressing. // I can realize my own memory with my own memory. I can realize my own memory with my own memory. I can realize my own memory with my own memory. SetTimeout (() => run(), 0)} _reject (err) {if (this._status! Const run = () => {this._status = REJECTED this._value = err let cb; While (cb = this._rejectedQueues. Shift ()) {cb(err)}}Copy the code

5. When a Promise object is passed as an argument to the resolve method, the state of the Promise object determines the state of the current Promise object

_resolve (val) {const run = () => {if (this._status! This is FULFILLED FULFILLED // this is FULFILLED FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED // this is FULFILLED / (cb = this._capacity. shift()) {cb(value)}} // Execute the functions in the failure queue in order, Const runRejected = (error) => {let cb; While (cb = this._rejectedQueues. Shift ()) {cb(error)}} /* If the value of resolve is a Promise object, The state of Promsie will not change until the Promise object's state changes. This depends on the Promsie state */ if (val instanceof MyPromise) {val.then(value => {this._value = value runFulfilled(value)}, Var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var SetTimeout (run, 0)}Copy the code

6. The catch

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

1.4.3 premium

1. Static resolve method

Static resolve (value) {// If the argument is a MyPromise instance, If (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value))}Copy the code

2. Static Reject

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

3. Static ALL method

Static all (list) {return new MyPromise((resolve, Reject) => {/** * Collection of return values */ let values = [] let count = 0 for (let [I, p] of list.entries()) {// Array arguments if not MyPromise instances, Mypromise.resolve this.resolve(p).then(res => {values[I] = res count++ //) This will be fulfilled fulfilled if (count === list.length) resolve(values)}, Reject (err)}}) {reject(err)}}) {reject(err)}}Copy the code

4. Static RACE methods

// Add static race (list) {return new MyPromise((resolve, reject) => {for (let p of list) {// add static race (list) {return new MyPromise((resolve, reject) => {for (let p of list) { This.resolve (p).then(res => {resolve(res)}, err => {reject(err)})})}Copy the code

5. The done method works: if the last method throws an error, either then or catch, it may not be caught (because errors within a Promise do not bubble globally). At the end of the callback chain, to ensure that any possible error is thrown there is currently no done on the Promise, we can customize one

Promise.prototype.done = function (onFulfilled, onRejected) { console.log('done') this.then(onFulfilled, SetTimeout (() => {throw reason}, setTimeout(() => {throw reason}, 0)})} promise.resolve (' This is the first resolve value of the static method ').then(()=>{return 'this is the second resolve value of the static method'}).then(()=>{throw(' This is the first resolve value of the static method Reject value ') return 'this is the second resolve value of the static method'}).done()Copy the code

6. Finally: An operation that will be performed regardless of the final state of the Promise object. The biggest difference between the done method and the finally method is that it takes an ordinary callback function as an argument, which must be executed anyway

finally (cb) {
  return this.then(
    value  => MyPromise.resolve(cb()).then(() => value),
    reason => MyPromise.resolve(cb()).then(() => { throw reason })
  );
};
Copy the code

7. Complete code please stamp, source address welcome star!

2.Generator

2.1 define

1. A Generator can be understood as a state machine that encapsulates many states internally and returns an Iterator object; Iterator objects: Define a standard way to produce a finite or infinite sequence of values. An Iterator has the next() object; 3. Multiple returns can be called by next multiple times, the most important feature is that you can control the execution sequence;

2.2 Declaration Methods

2. Is a special function

function* gen(x){ const y = yield x + 6; return y; Function * genOne(x){const y = 'this is the first yield :${yield x + 1}'; return y; }Copy the code

The entire Generator function is an encapsulated asynchronous task, or a container for asynchronous tasks. Where an asynchronous operation needs to be paused, the yield statement is used to indicate this

2.3 perform

1. Normal execution

const g = gen(1); // Executing the Generator returns an Object, instead of a normal function that returns the value after return g.next() // {value: 7, done: False} // Call the next method of the pointer, starting at the head of the function or where it stopped last time, Until the next yield expression or return statement is met, the yield line is executed. // {value: undefined, done: {value: undefined, done:}}} } // The last line of return y is completed, so done is trueCopy the code

2. Pass parameters to the next method

const g = gen(1); G.ext () // {value: 7, done: false} g.ext (2) // {value: 2, done: true} // The next argument is as the result of the last asynchronous task 3. Nested execution must use the yield* keywordCopy the code

Function * genTwo(x){yield* gen(1) yield* genOne(1) const y = ${yield x + 2}; return y; } const iterator=genTwo(1) const iterator=genTwo(1); For (let value of iterator) {console.log(value)}

Copy the code

2.4 Difference between yield and return

Differences: a function can have multiple yields, but only one yield has a location memory, and return does not

2.5 throw

Throw an error, which can be tried… catch… To capture

G.hash (' this is an error thrown '); // This is an error thrownCopy the code

2.6 applications

Function * someTask(){try{const valOne=yield 1 const valTwo=yield 2 const valThree=yield 3 }catch(e){ } } scheduler(someTask()); function scheduler(task) { const taskObj = task.next(task.value); Console.log (taskObj) // Continue calling if (! taskObj.done) { task.value = taskObj.value scheduler(task); }}Copy the code

2.7 PolyFill

Schematic diagram

Primary version 2.7.1

Implementing an Iterator

Function createIterator(items) {var I = 0 return {next: function() { var done = (i >= items.length) var value = ! done ? items[i++] : undefined return { done: done, value: Value}}}} // Apply const iterator = createIterator([1, 2, 3]) console.log(iterator.next()) // {value: 1, done: false} console.log(iterator.next()) // {value: 2, done: false} console.log(iterator.next()) // {value: 3, done: false} console.log(iterator.next()) // {value: undefined, done: true}Copy the code

2.7.2 intermediate version

To implement Iterable (Iterable) 1. of… Iterator: The iterated object has a Symbol. Iterator property on the prototype chain; 2.Symbol. Iterator: A function that returns an object with no arguments that conforms to the iterator protocol. 3.for… Of takes an Iterable, or a value that can be cast/wrapped into an Iterable (such as’ ABC ‘), traversal time, for… “Of” gets the ‘Symbol. Iterator ‘of the iterable and calls next() repeatedly until the iterator returns the done property of the object to true, leaving the value unprocessed.

const a = ['a', 'b', 'c', 'd', 'e') for (let val of a) {the console. The log (val)} / / 'a' 'b' 'c' 'e' / / 'd' equivalent to the const a = [" a ", "b", "c", "d", "e"] for (let val, ret, it = a[Symbol.iterator](); (ret = it.next()) && ! ret.done; ) { let val= ret.value console.log(val) } // "a" "b" "c" "d" "e"Copy the code

Yield * returns an Iterable object; 5. Source code transformation

function createIterator(items) { let i = 0 return { next: function () { let done = (i >= items.length) let value = ! done ? items[i++] : undefined return { done: done, value: value } } [Symbol.iterator]: function () { return this } } } let iterator = createIterator([1, 2, 3]) ... iterator // 1, 2, 3Copy the code

2.7.3 premium

1.for… of… Accepts an iterable and can cast or wrap its value; 2. “Of” gets the ‘Symbol. Iterator ‘of the iterable and calls next() repeatedly until the iterator returns the done property of the object to true, leaving the value unprocessed. 3. So we can use for… of… Encapsulate into the prototype chain.

Object.prototype[Symbol.iterator] = function* () { for (const key in this) { if (this.hasOwnProperty(key)) { yield [key,  this[key]] } } }Copy the code

3. The async and await

3.1 async role

1. Async returns a Promise object by returning a direct value in the function. Async wraps the Promise object with promise.resolve ()

async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result); / / Promise objectCopy the code

Async returns a Promise, so you can get the value via THEN

testAsync().then(v => { console.log(v); // Output hello async});Copy the code

So the functions in async are immediately executed, which is like the ‘*’ of the Generator.

3.2 await role

1. Await can be followed by a Promise object or other expression

function getSomething() { return "something"; } async function testAsync() { return Promise.resolve("hello async"); } async function test() { const v1 = await getSomething(); const v2 = await testAsync(); console.log(v1, v2); // Something and hello async} test();Copy the code

2. “await” is not followed by a Promise object; execute directly

3. The code that follows the await object blocks, the Promise object resolve, and then obtains the value of resolve as the result of the computation of the await expression

4. So this is why await must be used in async, which happens to return a Promise object and can execute blocking asynchronously

3.3 Combination of async and await

1. The main function is to handle the Promise chain callback or the function hell callback back to the Generator to require the function valOne, valTwo, and valThree to execute in sequence

function valOne(){}
function valTwo(){}
function valThree(){}

async ()=>{
  await valOne()
  await valTwo()
  await valThree()
}
Copy the code

2. Handle exception try… catch… Or await. The catch ()

3.4 Differences with Generator

1. Async is a built-in actuator, and the execution of Generator functions must depend on the actuator. Next () 2 is not required. Wider applicability. The co module convention is that yield can only be followed by a Thunk function or a Promise object, while await can be followed by any expression that returns a Promise object

Function f(m) {return m * 2; } f(x + 5); Var thunk = function () {return x + 5; }; function f(thunk) { return thunk() * 2; }Copy the code

4. Async function is the syntax of Generator functions. Async is the * of Generator, and await is equivalent to yield

3.5 Actuator PolyFill

There are two ways to implement the executor: the callback function (Thunk function) Promise object

3.5.1 track of primary version

async function fn(args) { // ... } function fn(args) {return spawn(function* () {//... }); } function spawn(gen){ let g = gen(); function next(data){ let result = g.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next(); }Copy the code

3.5.2 premium

Function spawn(genF) {// the spawn function is the auto-executor. Return new Promise(function(resolve, reject) {const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }Copy the code

4. Contrast Promise, Generator, async and await

4.1 code

1. Code comparison: Scenario: Assume that a series of animations are deployed on a DOM element. The first animation ends before the next one begins. If one of the animations fails, it stops executing and returns the value of the last successful animation. A.Promise

Function chainAnimationsPromise(elem, animations) {// let ret = null; // Create an empty Promise. Let p = promise.resolve (); For (let anim of animations) {p = p.hen (function(val) {ret = val; return anim(elem); }); } // Return a Promise return with error catch deployed p.catch(function(e) {/* ignore error, */}).then(function() {return ret; }); }Copy the code

B.Generator

function chainAnimationsGenerator(elem, animations) { return spawn(function*() { let ret = null; try { for(let anim of animations) { ret = yield anim(elem); }} catch(e) {/* ignore error, */} return ret; }); }Copy the code

C.async

async function chainAnimationsAsync(elem, animations) { let ret = null; try { for(let anim of animations) { ret = await anim(elem); }} catch(e) {/* ignore error, */} return ret; }Copy the code

Comparison shows that async… await… More elegant code

4.2 the principle

Async and await functions encapsulate self-executing functions and features based on Generator. See PolyFill for each API

4.3 Execution Sequence

Here’s the headline interview

console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2()  { console.log('async2 end') } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() {console.log('promise2')}) console.log('promise2') => Promise1 => promise2 => async1 end => setTimeout // New Chrome print // script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout "Promise2" is a new version of "await", and "await" is faster than "await". "Promise2" is a new version of "await"Copy the code

After the language

Original code word is not easy, welcome star!