What pre-knowledge is required for Q1 Promise

A:

S1 High-order function: a function that takes a function as an argument/return value, commonly used to implement decorator/function currification, etc

// S1 high order function - decorator
function say(a, b) {
  console.log(a, b)
}
Function.prototype.before = function(beforeFn) {
  return (. args) = > {  // Residual operator
    beforeFn()
    this(... args)// Expand the operator}}let cb = say.before(function() {
  console.log('before say')
})
cb('hello'.'world')

// The result is:
// before say
// hello world
Copy the code

Q2 Promise what are the characteristics of A:

1 Executor (resolve, reject) executes immediately + Reject is triggered if the executor reports an error

  • Injector mode: pass B to A, and then, within A’s scope, pass A variable parameter to B

2.1 Promise has three states: successful resolve, failed Reject, and pending

2.2 The executor internally calls resolve/reject to inject

  • Once a promise’s state is established, it cannot be changed (in other words, it can only be changed while waiting).
  • Change the state of a promise (Only in resolve/ Reject can you change the promise state)
  • Outgoing executor processing results

3 An example of a promise has the then method, with one argument for a successful callback and the other for a failed callback

  • Promise.then (a1, b1) is an immediate execution of + A1 /b1 after a call to resolve/reject
  • A promise instance can have multiple THEN callbacks handled
  • When the executor internally synchronizes resolve/reject, a1/ B1 is executed immediately
  • When the executor internally executes resolve/ Reject ==> publish-subscribe asynchronously: Then callbacks are suspended until resove/ Reject is called, and then callbacks are executed in sequence

Publish-subscribe: A1 / B1 decides whether to execute immediately or join the subscription queue based on the promise status

4 Executor1 results can be received by A1 / B1 in P1. Then (A1, B1)

  • Injector mode: resolve/reject value/reason + a1/b1 value/reason is injected during execution

5 p1. Then (a1, b1). Then (a2, b2)

  • Recursion: in the class method p1.then(), recursively create + returns a new class instance P2
  • P2. reolve(a1/ B1 result) ==> a1/b1 result is injected into A2 / B2: Either two global class attributes are defined, or the A1 / B1 result is processed internally in P2, but the second approach is used because the A1 / B1 result can have multiple cases

6.1 the return value of A1 /b1, which determines the state of P2 ==> the specialized handler resolvePromise

  • If a1/b1 returns a normal value (non-promise/non-error), it will be accepted by A2 in P2. then(A2, B2).

  • If an error occurs within a1/ B1, it will be processed by B2 in P2. then(A2, B2).

  • If a1/b1 internally returns a p1_1, resolve/reject is used to determine whether a2 or B2 will be accepted by P2. then(A2, b2)

  • In particular, a circular reference occurs when a1/b2 returns p1_1 === p2, so a reject(new TypeError) + TypeError is thrown, and it is to determine and handle this that the p2 object is passed inside the resolvePromise

  • Processing function method: Since a1/ B1 returns may have multiple types + success/failure /pending states all need to handle returns, So define a function to handle the effect of a1/ B1 return value on the state of promise2 ==> resolvePromise(promise2, x, resolve, reject)

  • Asynchronously fetching reference objects: Since the resolvePromise reads promise2 internally and the promise2 object has not been defined before the constructor logic has been executed, setTimeout is used to delay reading promise2

  • Because p2 try… Catch cannot catch asynchronous errors in executor, so setTimeout must handle the error itself

6.2 When X is a promise object, comprehensive consideration should be given to the following points:

  • How to tell if x is a Promise object: x is an object & has then attributes & then attributes are of type function/x is a function, considering x. Teng will report an error

7 If p1. Then () does not define a1/b1, p1 is passed to A2 / B2 in p2. Then (A2, B2)

  • Default assignment: If a1/b1 has no incoming value, define the default function

To summarize a typical promise implementation:

S1 p1 = new Promise(ex1)

  • Ex1 () ==> Call resolve/ Reject to update p1.status + p1.value/ p1.Reason

S2.1 p2= p1. Then (a1, b1) ==> (p2=) new Promise(then_ex2)

  • Then_ex2 is an internal anonymous function of THEN, which encapsulates A1 / B1 and is passed in as exec
  • NewPromise (then_ex2) ==> ex2() calls perform ==> depending on the state of P1, performing branching logic
  • Execute x = a1()/b1() ==> x = new Promise()/ ordinary value, etc. The ultimate goal is to get x
  • ResolvePromise (p2, x, p2.resolve, p2.reject) ==> Finally calls P2’s resolve/reject

S2.2 resolvePromise(p2, x, p2.resolve, p2.reject

  • Handling p2 and X circular references: Reject (New TypeError)
  • Handle cases where x is a normal value: simply call resolve
  • Handle the case where x= p1_1 is a promise: Then. Call (p1_1, y, e) ==> p1_2 is new instance + recursive call resolvePromise to prevent y from being a promise object (i.e. P1_1 value is a promise object instead of a regular value)
  • Implement the logic that p2’s state can only change once: called lock

Q3 Promise in addition to then, what other methods

A:

S1 promise.catch(errCb)

  • Must receive an error between the chain calls of promise().then().then()
  • Is a syntactic sugar for promise.then(NULL, errCb), which takes advantage of the pass-through feature of promises

S2 Promise.resolve(data)

  • Return the Promise object P1
  • If the thenable object (promise object) P2 is passed in, then(p1. Resolve, p1. Reject) is called, and p2’s state determines whether P1 will succeed or fail
  • If a normal value is passed in, just call resolve(data)

S3 Promise.reject(data)

  • Return a promise object in a failed state
  • The difference between promise.resolve and promise.reject is that resolve waits for an asynchronous promise to complete, while Reject does not

S4 promise.finally()

  • The callback function does not take any arguments
  • Promise. Resolve Is used to ensure that if cb is asynchronous, value is returned only after CB execution
  • The return value is the previous Promise object value, but if an exception is thrown, the exception’s Promise object is returned
  • See the code implementation section for detailed parsing

S5 Promise. All methods

  • Receive an iterable whose elements (normal values/Promise objects) are executed in parallel
  • An execution result is returned only after all incoming promise states have been executed
  • Keep the order of the input array the same as the order of the output array
  • If one fails, the entire promsie.all immediately fails, and the results of the other elements are ignored

S6 Promise. Race method

  • If the incoming array is empty, the state is always pending
  • Promises array iterates through and executes promises array contents. After obtaining the fastest promises result, terminate the entire promise
  • Promise.race, through encapsulation, can be used to interrupt Promise execution (the asynchronous request is still issued, but no longer cares/uses its return result)
  • Interrupt the promise chain call: Return a promise in a pending state

S7 Promise. AllSettled method

  • The elements are not dependent on each other, and any of them is rejected, and the others are not affected
  • This method returns the execution result of each promise

How does Q4 implement a Promise

A:

const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'

const resolvePromise = (promise2, x, resolve, reject) = > {
  if (promise2 === x) { 
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}// Subsequent conditions are strictly judged to ensure that the code can be used with other libraries
  let called
  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, because x. teng will be evaluated again, which is likely to cause errors again
        then.call( 
          x, 
          y= > {
            if (called) return
            called = true;
            resolvePromise(promise2, y, resolve, reject);  // The recursive parsing process
          }, 
          e= > {
            if (called) return;
            called = true;
            reject(e); // If you fail, you fail})}else {   // {then:'23'}resolve(x); }}catch (e) {   // Prevent failure from re-entering success
      if (called) return;
      called = true;
      reject(e); // The value is incorrect}}else {
     resolve(x)
 }

}

class Promise {
  constructor(executor) {
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.onResolvedCallbacks = []   // Used to store successful callbacks
    this.onRejectedCallbacks = []   // Used to store failed callbacks

    const resolve = (value) = > {
      if (this.status === PENDING) {
        this.value = value
        this.status = RESOLVED
        this.onResolvedCallbacks.forEach( fn= >fn() ) 
      }
    }
    const reject = (reason) = > {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED
        this.onRejectedCallbacks.forEach( fn= >fn() )
      }
    }

    try {
      executor(resolve, reject)
    } catch(err) {
      console.log('execuor的err', err)
      reject(err)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v= > v;
    onRejected = typeof onRejected === 'function' ? onRejected : err= > { throw err };

    let promise2 = new Promise((resolve, reject) = > {
      if (this.status === RESOLVED) {
        setTimeout(() = > {
          try {
              let x = onFulfilled(this.value)
              // x may be a proimise
              resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
              reject(e)
          }
        }, 0)}if (this.status === REJECTED) {
        setTimeout(() = > {
            try {
                let x = onRejected(this.reason)
                resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
                reject(e)
            }
        }, 0)}if (this.status === PENDING) {
        this.onResolvedCallbacks.push( () = >{
          setTimeout(() = > {
            try {
                let x = onFulfilled(this.value);
                resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0)})this.onRejectedCallbacks.push( () = >{
          setTimeout(() = > {
            try {
                let x = onRejected(this.reason);
                resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0)})}})return promise2
  }

  catch(errCb){
    return this.then(null, errCb)
  }

  finally(cb) { 
    return this.then(
      (value) = > Promise.resolve( cb() ).then( () = >  value ),
      (err) = > Promise.resolve( cb() ).then( () = > { throw err } )
    )
  }

  // Prototype method/static method
  static resolve(data) {
    return new Promise( (resolve, reject) = > {
      // Data is a THEN object
      if(data? .then &&typeof data.then === 'function') {
        data.then(resolve, reject)
      } else {
        resolve(data)
      }
    })
  }

  static reject(reason){
    return new Promise((resolve,reject) = >{
      reject(reason)
    })
  }

  static all_1 (promises) {
    return new Promise( (resolve, reject) = > {
      let result = []   // Return the result value
      let index = 0   // The counter that gets the result of the asynchronous operation
      if (promises.length === 0) {  // An empty array is returned synchronously
        resolve(result)
      }

      for (let i=0; i<promises.length; i++) {
        // Because Promises may be synchronous data /promsie objects,
        Resolve becomes a Promise for the corresponding state
        Promise.resolve(promises[i]).then( data= > {
          result[i] = data
          // Index is used instead of result.length because if result[9] = 10, it is 10
          // However, other asynchronous results may not be obtained, so the index counter is used to determine whether the asynchronous operation is complete
          if (++index === promises.length) { resolve(result) }  
        },
        reject
      )}
  
    })
  }

  static all (values) {
    if (!Array.isArray(values)) {
      return new TypeError("Please enter an array")}return new Promise((resolve, reject) = > {
      let resultArr = [];
      let len = 0;
      const dealFn = (value, index) = > {
        resultArr[index] = value;
        if (++len === values.length) {
          resolve(resultArr)
        }
      }
      for (let i = 0; i < values.length; i++) {
        let value = values[i]
        // Check if value is still a promise
        if (value && typeof value.then === 'function') {
          value.then((value) = > {
            dealFn(value, i)
          }, reject);
        } else {
          dealFn(value, i)
        }
      }
    })
  }


  static race(promises) {
    if (promises.length === 0)  return
    return new Promise( (resolve, reject) = > {
      for (let i=0; i<promises.length; i++) {
        Promise.reslove(promises[i]).then(data= > {   // S2
          resolve(data)
        }, 
        reject
        )
      }
    })
  }

  static allSettled(promises) {
    return new Promise( (resolve) = > {
      let result = [],  index = 0
      if(promises.length === 0)  { resolve(result) } 
    
      for(let i = 0; i < length; i++) {
        Promise.resolve(promises[i]).then( 
          (value) = > {  
            result[i] = { status: 'fulfilled', value, }
            if(++index === promises.length) { return resolve(result) }
          }, 
          (reason) = > {  
            result[i] = { status: 'rejected', reason, }
            if(++index === length) { return resolve(result) }
          }
        )
      }
  
    })
  }

}

// The delay object for promise
Promise.defer = Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve,reject) = >{
      dfd.resolve = resolve;
      dfd.reject = reject;
  })
  return dfd
}

// npm install promises-aplus-tests -g
// promises-aplus-tests promise.js
module.exports = Promise
Copy the code

Iterator + generator + await

Q1 What is an iterator/iterable

A1 iterator:

  • An object with a next() method + next() returns an object with value and done attributes
  • An optional return()/throw method ==> can also be used to interrupt iterators
interface Iterator { next(value? : any) : IterationResult,return? :() = > any
}
interface IterationResult {
  value: any,
  done: boolean,
}

// Simulate implementation iterators
function createIterator(items) {
  let i = 0
  return {
    next() {
      let done = (i >= items.length)
      let value = (done ? undefined : items[i++])
      return {
        value,
        done
      }
    }
  }
}

let iterator = createIterator([1.2])
console.log(iterator.next())     // { value: 1, done: false }
console.log(iterator.next())    // { value: 2, done: false }
console.log(iterator.next())    // { value: undefined, done: true }
Copy the code

A2: Iterable:

  • In JS, object A is an iterable as long as it deploys the [symbol.itetaor] method + that returns an Iterator

  • Iterables are characterized by the ability to pass for… The of syntax accesses the internal data of its objects

  • Destruct assignment/extension operators/for… Of/array.from ()/yield* are called iterator next() by default

interface Iterable {
  [Symbol.iterator]() : Iterator,
}
interfaceIterator { next(value? :any) : IterationResult,
}
interface IterationResult {
  value: any.done: boolean,}Copy the code

A3 Simulation implementation for… The execution process of

function forOf(obj, cb) {
  let _iterator, _step

  if (typeof obj[Symbol.iterator] ! = ='function') {
    throw new TypeError(obj + 'is not iterable')}if (typeofcb ! = ='function') {
    throw new TypeError('cb must be callable')
  }

  _iterator = obj[Symbol.iterator]()
  _step = _iterator.next()

  while(! _step.done) { cb(_step.value) _step = _iterator.next() } }Copy the code

Q2 What is a generator

A:

  • The generator is a state machine that encapsulates multiple subcoroutines

  • It: {next() {value: XXX, done: XXX}}

  • Next (Params) : 1 Passes params to the main coroutine as the result of the last yield expression

    2 Begins/resumes execution of the main coroutine logic until the next yield expression is encountered

    3 Suspend the main coroutine + execute the yield statement, and return the result to the external thread

    4 Repeat the above steps until the main coroutine executes until return XXX ==> returns XXX to the outside thread + destroys the main coroutine stack

To sum up, the generator flows data in the following order: child coroutines ==> external threads ==> main coroutines

  • Yield * XXX is equivalent to for.. Executes the [Symbol.Iterator] interface in XXX
  • It. Throw (xx) error can be handled by inner and outer try… The catch statement catches
  • It. Return (xx) terminates the iterator
// Simulation implementation generator- simplified version

// The generator function splits the code into switch-case blocks based on the yield statement, and then executes each case separately by toggling _context.prev and _context.next
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(); }}}// Lower version context
var context = {
  next:0.prev: 0.done: false.stop: function stop () {
    this.done = true}}// Invoke with low configuration
let gen = function() {
  return {
    next() {
      value = context.done ? undefined: gen$(context)
      done = context.done
      return {
        value,
        done
      }
    }
  }
} 

// Test use
var g = gen() 
g.next()  // {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

Q3 What is async/await and what is it used for

  • Write asynchronous code in synchronous form
  • Essentially, it is the generator + syntactic sugar for the generator to execute automatically

Differences between async/await and Generator functions:

  • Async /await comes with an executor that automatically executes the next step without manual next
  • Async functions return a Promise object and Generator functions return a Generator object
  • The await function returns the resolve/reject value of the Promise
// A simulated implementation of async
function run(gen) {
  // Wrap the return value as a promise
  return new Promise((resolve, reject) = > {
    var it = gen()
    _next()

    function _next(val) {
      // Error handling
      try {
        var res = it.next(val) 
      } catch(err) {
        return reject(err)
      }
      if (res.done) {
        return resolve(res.value)
      }
      //res.value is wrapped as a promise to accommodate cases where yield is followed by a basic type
      Promise.resolve(res.value).then(
        val= > { _next(val) }, 
        err= > { it.throw(err) }
			)
    }
  
  })
}
Copy the code

Reference documentation

01 Everest – promise

02 This time, understand ES6 Iterator thoroughly

03 Promise/ Async /Generator implementation principle analysis