takeaway

Last time we explained the use of Promise, today we will use the Promise/A+ specification to write A MyPromise class, without further discussion, let’s start.

What does the specification define

State of Promise

    1. Promise has only one state (pending, depressing, rejected).
    1. A promise can be changed to a fulfilled or rejected state under the pending state
    1. During the depressing state, it cannot be changed to other states again. There must be one value, which cannot be changed
    1. The rejected state cannot be changed to another state. There must be a reason and the rejected state cannot be changed

Then method

    1. The then method is used to get a value or reason that is transferred to a state
    1. This is a big pity. Accept two parameters, onFulfilled and onRejected.
    1. If onFulfilled, onRejected is not a function, then it must be ignored
    1. If ondepressing is a function
    • This must be called after the transition to the state fulfilled, with value as the first parameter of the function
    • Cannot be called before the depressing state is complete
    • Call at most once
    1. If onRejected is a function
    • It must be called after converting to the state Rejected, with Reason as the first argument to the function
    • Cannot be called before the Rejected state is complete
    • Call at most once
    1. Ondepressing and onRejected are both function calls, so this refers to global, strictly undefined
    1. The then method can be called multiple times
    • When the promise completes the fulfilled state, each onFulfilled callback must be invoked in the original then order
    • When the Promise rejects the Rejected state, the onRejected callbacks must be called in the original then order
    1. Then must return a new Promise objectpromise2 = promise1.then(onFulfilled, onRejected);
    • This is a pity or onRejected callback which returns a value X, please see thisAnalyze the x value returned by then
    • If ondepressing or onRejected throws an exception, then promise2 must be the state and the exception information will be the reason for the rejection
    • If ondepressing is not a function and promise1 is already a depressing state, then promisE2 must have the same value as promise1 to fulfill the state promise
    • If onRejected is not a function and promise1 is already in the Rejected state, then promise2 must reject the state promise for the same reason as promise1 does

Analyze the X value returned by then (resolvePromise)

    1. If a promise references the same object as X, TypeError is thrown as a promise for the rejection reason
    1. If x is a promise object
    • 2.1 If the X is in the pending state, the Promise needs to remain in the pending state until X transitions to the fulfilled or Rejected state
    • 2.2 If the X is already a depressing state, use the same value to fulfill the depressing promise
    • 2.3 If x is already in the Rejected state, reject the Rejected Promise for the same reason
    1. If x is an object or a function type
    • 3.1 Set THEN to x. teng
    • 3.2 If the detection attribute X. teng returns an abnormal result E, e is a reject promise
    • 3.3 If then is a function, call it with x as this, with the first argument resolvePromise and the second argument rejectPromise
      • 3.3.1 If the value of resolvePromise is y, continue parsing and run resolvePromise
      • 3.3.2 If the rejectPromise is invoked with a reject promise (r), reject promise (R) is used as a reject promise
      • 3.3.3 If both resolvePromise and rejectPromise are invoked, or if the same parameter is invoked several times, the first invocation takes precedence and subsequent invocations are ignored.
      • 3.3.4 If calling THEN raises exception e
        • 3.3.4.1 If resolvePromise or rejectPromise has already been invoked, ignore it
        • 3.3.4.2 Or e reject promise
    • 3.4 If then is not a function, fulfill (fulfill)promise with x
    1. Fulfill (fulfill)promise with x if x is not an object or function

Handwritten MyPromise

1. The basic version

// Const PENDING = 'PENDING' // const FULFILLED = 'FULFILLED' // Const REJECTED = 'REJECTED' // Class MyPromise {// Accept a function executor constructor(executor) {// Initialize PENDING state this.status = PENDING // initialize value this.value = Reason this. Reason = undefined // Let resolve = (value) => {// The state can be updated only when it is PENDING. === PENDING) {this.status = depressing this.value = value}} // Let reject = (reason) => {// Update the status when the status is PENDING. If (this.status === PENDING) {this.status = REJECTED this.reason = reason}} // Try /catch catches exceptions. Reject promise try {executor(resolve, Reject)} catch (error) {reject(error)}} /** * then this method accepts two parameters * @param {successful callback} onpity * @param {fail callback} onRejected */ this is a big pity, onFulfilled) {// This is a big pity, onFulfilled) OnFulfilled (this. Status === depressing) {onFulfilled(this. Value)} // onFulfilled(this. If (this.status === REJECTED) {onRejected(this.reason)}}}Copy the code

Let’s put it to the test:

const p1 = new MyPromise((resolve, reject) => { resolve("ok"); }).then( (value) => { console.log("success", value); }, (reason) => { console.log("faild", reason); });Copy the code

Console output:

'success ok'
Copy the code

The basic version is now implemented, but there is a problem with executing resolve or reject asynchronously in executor, which causes nothing to happen, as shown below:

const p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve("ok"); }, 1000); }).then( (value) => { console.log("success", value); }, (reason) => { console.log("faild", reason); });Copy the code

Because the state is pending, not completed, when the then method is executed, it is not executed. In this case, we should collect completed and rejected callbacks in pending state, wait until asynchronous execution is complete, trigger resolve or Reject, and then execute callbacks in sequence. Since promise’s callbacks are asynchronous (microtasks), let’s use setTimeout (macro tasks) to optimize:

// Const PENDING = 'PENDING' // const FULFILLED = 'FULFILLED' // Const REJECTED = 'REJECTED' // Class MyPromise {// Accept a function executor constructor(executor) {... / / for success callback enclosing onFulfilledCallbacks = [] / / storage failure callback enclosing onRejectedCallbacks = [] / / definition is complete, Resolve = (value) => {// The state is PENDING. This. Status === PENDING) {this.status = depressing this.value = value // This Update the completion status, which in turn perform success callback enclosing onFulfilledCallbacks. ForEach (fn = > fn ())}} / / define refused, Let reject = (reason) => {// State is PENDING, If (this.status === PENDING) {this.status = REJECTED this.reason = reason // After update declined to state, in turn perform success callback enclosing onRejectedCallbacks. ForEach (fn = > fn ())}} / / try/catch catch exceptions, Reject promise try {executor(resolve, Reject)} catch (error) {reject(error)}} /** * then this method accepts two parameters * @param {successful callback} onpity * @param {fail callback} onRejected */ then (onFulfilled, onRejected) { ... / / in a pending state collect callback if (this. The status = = = pending) {/ / collect success callback enclosing onFulfilledCallbacks. Push ((() = > {setTimeout () = > { OnFulfilled}. This value, 0)}) / / collect failure callback enclosing onRejectedCallbacks. Push (= > {setTimeout () () (= > { onRejected(this.reason) },0) }) } } }Copy the code

2. OnFulfilled and onRejected are the two parameters of then method

According to the parameters in the THEN method defined by the above specification, onFulfilled and onRejected need to be judged

/** * then this will be a big pity. * @param {defeatcallback} onFulfilled */ then (onFulfilled, fulfilled) OnFulfilled === 'function'? // Judge onFulfilled, onFulfilled === 'function'? onFulfilled : value => value onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason } .... }Copy the code

3. Chain calls to the then method

The then method chain calls new Promise((resolve,reject)=>{}).then().then(). Then returns a new Promise.

If ondepressing or onRejected returns a value x, run the following Promise: [[Resolve]](promise2, x)

The Promise resolution process is an abstract operation that takes in a Promise and a value, plus we need to process it once we get the x value, so we pass in four parameters altogether. This process will be named resolvePromise, which will be abstracted first and then processed in it. OnFulfilled or onRejected may also fail, so try/catch will be used to catch the exception, so modify it as follows:

// Define three state constants const PENDING = "PENDING "; // This is a big pity; // Complete state const REJECTED = "REJECTED "; // Reject state class MyPromise {... /** * then this will be a big pity. * @param {defeatcallback} onFulfilled */ then(onFulfilled, fulfilled) OnFulfilled) {// judge onFulfilled, onFulfilled === "function"? onFulfilled : (value) => value; onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason; }; This is a big pity. Let promise2 = new MyPromise((resolve, reject) => {// This is a big pity. This. Status === very depressing) {setTimeout(() => {try {let x = ondepressing (this. Value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); }}, 0); } // Call onRejected when the status is updated to reject, If (this.status === REJECTED) {setTimeout(() => {try {let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); }}, 0); } / / in a pending state collect callback if (this. The status = = = pending) {/ / collect success callback enclosing onFulfilledCallbacks. Push (= > {setTimeout () () (= > {  try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); }}, 0); }); / / collect failure callback enclosing onRejectedCallbacks. Push ((() = > {setTimeout () = > {try {let x = onRejected (enclosing a tiny); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); }}, 0); }); }}); return promise2; }}Copy the code

ResolvePromise function

function resolvePromise (promise2, x, resolve, reject) { // 1. If a promise and X reference the same object, TypeError is raised as a promise for rejection reason if (x === promise2) {return Reject (new TypeError('Chaining cycle detected for promise #<Promise>')); } // Prevent multiple lets called; Fulfill (fulfill) Promise if (x! Fulfill) fulfill (x! Fulfill) if (x! Fulfill) = = null && (typeof x = = = 'object' | | typeof x = = = 'function')) {try {let then = x.t hen / / then is a function, If (typeof then === 'function') {then. Call (x, y => {if (called) return called = true // ResolvePromise (promise2, y, resolve, reject)} reason => { if (called) return called = true reject(reason) }) } else { resolve(x) } } catch (error) { if (called) return called = true reject(error) } } else { resolve(x) } }Copy the code

4. Test MyPromise

Promises -aplus-tests – NPM: Promises -aplus-tests – NPM: Promises – Aplus-tests – NPM: Promises – Aplus-tests – NPM: Promises – Aplus-tests

Promises -aplus-tests [js file name

New testMyPromise. Js

Const MyPromise = require('./MyPromise') // Promise.deferred = function () {var result = {}; result.promise = new MyPromise(function (resolve, reject) { result.resolve = resolve; result.reject = reject; }); return result; } module.exports = MyPromiseCopy the code

Modify the package. The json

"scripts": {
    "test": "promises-aplus-tests testMyPromise"
  },
Copy the code

Execute the command

npm run test
Copy the code

5. Prototype methods catch and finally

5.1 catch

.catch() is only.then() which has no parameter position reserved for ondepressing. Also return a promise


  catch (onRejected) {
    return this.then(null, onRejected)
  }
Copy the code

5.2 the finally

Whether the result is pity or Rejected, the specified callback function will be implemented and a Promise will also be returned

Constructor return p.teng (value => {return p.resolve(callback()).then(()=>value) }, reason => { return p.resolve(callback()).then(()=>{throw reason}) }) }Copy the code

6. Static methods

6.1 resolve

The static method resolve returns a resolved Promise object. If the value passed in is a promise, the resolution will be recursive, so we need to change the constructor resolve method here:

Resolve = (value) => {// ====== add logic ====== // if value is a promise, If (value instanceof MyPromise){// Return value. Then (resolve,reject)}};Copy the code

Resolve method

// Static promise. resolve returns a resolved Promise object. static resolve (value) { return new MyPromise((resolve, reject) => { resolve(value) }) }Copy the code

6.2 reject ()

The static reject method, which returns a Promise object with a reason for the rejection.

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

6.3 all ()

The all() method receives an iterable type (Array, Map, Set) of promises and only returns one promise instance. OnFulfilled callback is the Array of the completion state results of all promise instances as parameters. The result of the onRejected callback is to return the reason for the current rejection as an argument if one of the rejections is rejected

// static all (iterable) {let results = [] // let index = 0 // Reject) => {if (iterable[symbol.iterator]().next().done) {return resolve([])} for (const item of Iterable) {// Use the resolve method, either a promise object or a value, Mypromise.resolve (item). Then (value => {results[index] = value index++ if (index === iterable.length) { resolve(results) } }, reason => { reject(reason) }) } }) }Copy the code

6.4 race ()

Method returns a promise that is resolved or rejected once a promise in the iterator is resolved or rejected. It may be a high degree of completion or rejection, depending on which of the two is the first

// static race() static race(iterable) {return new MyPromise((resolve, resolve) reject) => { for (const item of iterable) { MyPromise.resolve(item).then(value => { resolve(value) }, reason => { reject(reason) }) } }) }Copy the code

6.5 allSettled ()

AllSettled () also accepts an iterable type of promise (Array, Map, Set). Only one promise instance will be returned until all the promise instances return a result. The return result is an array of objects. The promise state will be stored with the key value status. If the promise state is fulfilled, the return value will be stored with the key value. Store the reason with the Reason key

Static allSettled (iterable) {let results = [] // let index = 0 // return MyPromise((resolve, resolve) Reject) => {if (iterable[symbol.iterator]().next().done) {return resolve([])} for (const item of Iterable) {// Use the resolve method, either a promise object or a value, Mypromise.resolve (item). Then (value => {results[index] = {status: 'depressing ', vaule: value } index++ if (index === iterable.length) { resolve(results) } }, reason => { results[index] = { status: 'rejected', reason: reason } index++ if (index === iterable.length) { resolve(results) } }) } }) }Copy the code

By writing MyPromise to have a further understanding of the Promise principle, then return a value in the callback function have a more clear judgment, the next chapter will list some more common Promise topics to combine today’s analysis, why is such a return result