Promise

What is Promise?

Promise is a solution to asynchronous programming, simply a container that holds an asynchronous operation that has not yet been completed and is expected to be completed in the future

This is a pity. Use new Promise to create a Promise object that represents the final completion (or failure) of an asynchronous operation and its result value. There are three states: Pending (fulfilling), which is a pity.

The pending state can be changed to progressively through the resolve asynchronous operation or rejected asynchronous operation

This is a big pity and rejected. Once the state changes, it will not change again

advantages

  1. Out of callback hell, can proceed.thenChain call, so that asynchronous operations become more synchronous, process more clear specification, improve the maintainability and readability of the code

disadvantages

  1. Cannot be cancelled once executed
  2. Unable to track progress

Promise/A+ specification interpretation

The term

  1. promiseIs athenMethod objects or functions that behave according to this specification
  2. thenableIs athenThe object or function of a method
  3. valuepromiseThe value of the successful state, which is equal toresolve, including various data types, as well asundefined/thenableOr is itpromise
  4. reasonpromiseThe value of state failure, which isrejectIs the reason for the rejection
  5. exceptionIs a use ofthrowThe exception value thrown

Promise Status

Promises should have three states. Pay attention to the fluid relationship between them.

  1. pending

    1.1 Initial State, changeable. 1.2 A promise is in this state before either resolve or Reject. 1.3 Can be fulfilled through the resolve -> depressing state; 1.4 Reject -> Reject;

  2. fulfilled

    2.1 Final State, immutable. 2.2 A promise becomes this state after it is resolved. 2.3 Must have a value

  3. rejected

    3.1 Final State, immutable. 3.2 A Promise becomes this state when it is rejected. 3.3 There must be a Reason

Tips: To summarize, the promise state flow looks like this

pending -> resolve(value) -> fulfilled pending -> reject(reason) -> rejected

then

A promise should provide a THEN method that accesses the final result, either value or Reason.

promise.then(onFulfilled, onRejected);
Copy the code
  1. Parameter requirements

    Onpity must be a function type, and if it is not a function, it should be ignored.

  2. OnFulfilled features

    2.1 When the promise becomes a pity, Ondepressing should be called, and the parameter is value 2.2, which should not be called until the promise becomes a big pity. 2.3 It can only be called once (so a variable is needed to limit the number of times of execution during implementation)

  3. OnRejected features

    3.1 When promise becomes Rejected, OnRejected should be called with reason 3.2. It should not be called until the promise turns to Rejected.

  4. OnFulfilled and onRejected should be micro tasks

    QueueMicrotask is used here to invoke microtasks.

  5. The then method can be called multiple times

    5.1 After the promise state becomes a pity, This is a big pity. All the onFulfilled callback needs to be implemented according to the order of THEN, that is, according to the order of registration (so an array is needed to store multiple onFulfilled callback). All onRejected callbacks need to be executed in the order of THEN, that is, in the order of registration.

  6. The return value

    Then should return a promise

    promise2 = promise1.then(onFulfilled, onRejected);
    Copy the code

    6.1 onFulfilled or onRejected executes x, and calls resolvePromise(which may be difficult to understand, but can be delayed). This is a big pity. If onFulfilled or onRejected executes (), exception E will be thrown. This is the big pity. 6.3 If onFulfilled is not a function, promise2 will trigger the fulfilled function with the value of promise1. 6.4 If onFulfilled is not a function, Promise2 Triggers Rejected with the reason of promise1

  7. resolvePromise

    resolvePromise(promise2, x, resolve, reject);
    Copy the code

    Reject TypeError if X is a PROMsie if X is pending Then the promise must be pending until X becomes a pity or rejected. If X is fulfilled, fulfill promise with the same value. If x is rejected, reject promise with the same reason. If x is rejected, reject promise with the same reason. Reject promise with eas the reason if x. Chen is wrong, reject promise with eas the reason. If then is a function, Then. Call (x, resolvePromiseFn, rejectPromise) reject); R, reject promise with r. If both resolvePromise and rejectPromise are invoked, the first invocation takes precedence and subsequent invocations are ignored. If the call to then throws an exception e if resolvePromise or rejectPromise has already been called, then ignore, Reject promise with eas the reason if then is not a function.

Fulfill a Promise

Step by step

Normal usepromiseThe time is throughnewKeyword tonew Promise()And then useclassSo let’s do that.

class YuPromise {
  constructor(){}}Copy the code

Define three state types

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
Copy the code

Setting the initial state

class YuPromise {
  constructor() {
    // The initial state is pending
    this.status = PENDING;
    this.value = null;
    this.reason = null; }}Copy the code

resolverejectmethods

  1. According to the specification, these two methods change status from Pending to pity/Rejected.
  2. Note that the input arguments to both functions are value and reason.
class YuPromise {
  constructor() {
    // The initial state is pending
    this.status = PENDING;
    this.value = null;
    this.reason = null;
  }

  resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = FULFILLED; }}reject(reason) {
    if (this.status === PENDING) {
      this.reason = reason;
      this.status = REJECTED; }}}Copy the code

I’m going to add promise

  1. The input parameter is a function that takes resolve and reject.
  2. Note that when a promise is initialized, this function is executed and any errors are thrown through Reject
class YuPromise {
  constructor(fn) {
    // The initial state is pending
    this.status = PENDING;
    this.value = null;
    this.reason = null;

    try {
      // Bind this using resolve and reject on the current Promise instance
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
      this.reject(e); }}resolve(value) {
    if (this.status === PENDING) {
      this.value = value;
      this.status = FULFILLED; }}reject(reason) {
    if (this.status === PENDING) {
      this.reason = reason;
      this.status = REJECTED; }}}Copy the code

Let’s implement the key THEN method

  1. Then receives two parameters, onFulfilled and onRejected
then(onFulfilled, onRejected) {}
Copy the code
  1. Check and process the arguments, ignoring the previous ones if they are not function. This ignore refers to the return of value or Reason as is.
isFunction(param) {
    return typeof param === 'function';
}

then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
        throw reason;
    };
}
Copy the code
  1. The return value of. Then is a promise wrapped around a promise.
then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) = > {})
    return promise2
}

Copy the code
  1. Depending on the current state of the promise, different functions are called
then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) = > {
        switch (this.status) {
            // This is done immediately after the then call, mainly in cases where there are direct resolutions, such as resolve(1).
            case FULFILLED: {
                realOnFulfilled()
                break;
            }
            case REJECTED: {
                realOnRejected()
                break; }}})return promise2

}
Copy the code
  1. This is written to execute the moment the then function is called. This is a pity or Rejected. What if status is not fulfilled or Rejected? It may still be pending. Therefore, we need a monitoring mechanism for the state. When the state becomes a pity or Rejected, we will perform callback.
  • So we need to get all the callback first, and then we can execute it at some point. Create two new arrays to store successful and failed callbacks, respectively, and then, if pending.

    // Use arrays, mainly.then multiple times on a Promise instance
      this.fulfilledCallbackList = [];
      this.rejectedCallbackList = [];
    
    then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) = > {
        switch (this.status) {
            case FULFILLED: {
                realOnFulfilled()
                break;
            }
            case REJECTED: {
                realOnRejected()
                break;
            }
            case PENDING: {
                this.fulfilledCallbackList.push(realOnFulfilled)
                this.rejectedCallbackList.push(realOnRejected)
            }
        }
    })
    return promise2
    
    }
    Copy the code

When status changes, all callbacks are executed. Use the getters and setters of ES6. This is more semantically appropriate for what to do when status changes (you can also do it sequentially, adding a forEach line after assigning status).

// Prevent nesting in getter
this._status = PENDING;

get status() {
    return this._status;
}

set status(newStatus) {
    this._status = newStatus;
    switch (newStatus) {
        case FULFILLED: {
            this.fulfilledCallbackList.forEach(callback= > {
                callback(this.value);
            });
            break;
        }
        case REJECTED: {
            this.rejectedCallbackList.forEach(callback= > {
                callback(this.reason);
            });
            break; }}}Copy the code

The return value of then

Then returns a Promise. Then returns a Promise. Then returns a Promise.

  1. If ondepressing or onRejected throws an exception E, promise2 must reject the implementation and return the rejection cause E. (In this case, we need a manual catch code, reject when we encounter an error.)
then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) = > {
         const fulfilledMicrotask = () = > {
            try {
                realOnFulfilled(this.value);
            } catch (e) {
                reject(e)
            }
        };
        const rejectedMicrotask = () = > {
            try {
                realOnRejected(this.reason);
            } catch(e) { reject(e); }}switch (this.status) {
            case FULFILLED: {
                fulfilledMicrotask()
                break;
            }
            case REJECTED: {
                rejectedMicrotask()
                break;
            }
            case PENDING: {
              this.fulfilledCallbackList.push(realOnFulfilled)
              this.rejectedCallbackList.push(realOnRejected)
            }
        }
    })
    return promise2
}
Copy the code
  1. If ondepressing is not a function and promise1 executes successfully, promise2 must execute successfully and return the same value

  2. If onRejected is not a function and promise1 rejects it, promise2 must reject it and return the same data.

If onRejected succeeds, promise2 should be resolved

We actually did this in the argument checking, which is this code

const realOnFulfilled = this.isFunction(onFulfilled)
  ? onFulfilled
  : (value) = > {
      return value;
    };
const realOnRejected = this.isFunction(onRejected)
  ? onRejected
  : (reason) = > {
      throw reason;
    };
Copy the code
  1. If onFulfilled or onRejected returns a value x, then the resolvePromise method is run
then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > {
        return value
    }
    const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) = > {
        throw reason;
    };
    const promise2 = new YuPromise((resolve, reject) = > {
        const fulfilledMicrotask = () = > {
            try {
                realOnFulfilled(this.value);
            } catch (e) {
                reject(e)
            }
        };
        const rejectedMicrotask = () = > {
            try {
                realOnRejected(this.reason);
            } catch(e) { reject(e); }}switch (this.status) {
            case FULFILLED: {
                fulfilledMicrotask()
                break;
            }
            case REJECTED: {
                rejectedMicrotask()
                break;
            }
            case PENDING: {
               this.fulfilledCallbackList.push(realOnFulfilled)
               this.rejectedCallbackList.push(realOnRejected)
            }
        }
    })
    return promise2
}
Copy the code

resolvePromise

resolvePromise(promise2, x, resolve, reject) {
    // If newPromise and x point to the same object, reject newPromise as TypeError
    // This is to prevent endless loops
    if (promise2 === x) {
        return reject(new TypeError('The promise and the return value are the same'));
    }

    if (x instanceof YuPromise) {
        // If x is a Promise, make newPromise accept the state of x
        // Continue to execute x, and if you get a y, continue to parse y
        queueMicrotask(() = > {
            x.then((y) = > {
                this.resolvePromise(promise2, y, resolve, reject); }, reject); })}else if (typeof x === 'object' || this.isFunction(x)) {
        // If x is an object or function
        if (x === null) {
            // null is also considered an object
            return resolve(x);
        }

        let then = null;

        try {
            // assign x. teng to then
            then = x.then;
        } catch (error) {
            // If an error e is thrown when taking the value x. teng, reject a promise based on e
            return reject(error);
        }

        // If then is a function
        if (this.isFunction(then)) {
            let called = false;
            // call x as the function's scope this
            // Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise
            try {
                then.call(
                    x,
                    // If resolvePromise is called with the value y, run resolvePromise
                    (y) = > {
                        // A variable called is required to ensure that it is called only once.
                        if (called) return;
                        called = true;
                        this.resolvePromise(promise2, y, resolve, reject);
                    },
                    // If rejectPromise is invoked with argument r, reject the promise with argument r
                    (r) = > {
                        if (called) return;
                        called = true;
                        reject(r);
                    });
            } catch (error) {
                // If calling the then method throws an exception e:
                if (called) return;

                // Otherwise use e as the basis for rejecting the promisereject(error); }}else {
            // If then is not a function, execute a promise with an x argumentresolve(x); }}else {
        // If x is not an object or function, execute a promise with x as an argumentresolve(x); }}Copy the code

OnFulfilled and onRejected are micro tasks

Use the queueMicrotask wrapper to execute the function

const fulfilledMicrotask = () = > {
  queueMicrotask(() = > {
    try {
      const x = realOnFulfilled(this.value);
      this.resolvePromise(promise2, x, resolve, reject);
    } catch(e) { reject(e); }}); };const rejectedMicrotask = () = > {
  queueMicrotask(() = > {
    try {
      const x = realOnRejected(this.reason);
      this.resolvePromise(promise2, x, resolve, reject);
    } catch(e) { reject(e); }}); };Copy the code

Let’s just write some code and test it out

const test = new YuPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(111);
  }, 1000);
}).then(console.log);

console.log("case1", test);

setTimeout(() = > {
  console.log("case2", test);
}, 2000);

// Print the content as expected
// case1 YuPromise {
// _status: 'pending',
// value: null,
// reason: null,
// fullfilledList: [],
// rejectedList: [] }
// case2 YuPromise {
// _status: 'fullfilled',
// value: undefined,
// reason: null,
// fullfilledList: [],
// rejectedList: [] }
Copy the code

Why can I call.then and not.catch? Because we didn’t declare a catch method inside the class, we added a catch method

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

promise.resolve

The existing object will be fulfilled as a Promise object. If the parameter of the Promise. Resolve method is not an object with the then method (also called thenable object), a new Promise object will be returned, and its state will be fulfilled. Note that this is a static method because it is called through promise.resolve, not an instance.

static resolve(value) {
    if (value instanceof YuPromise) {
        return value;
    }

    return new YuPromise((resolve) = > {
        resolve(value);
    });
}
Copy the code

promise.reject

Returns a new Promise instance with the status Rejected. The reason argument to the promise. reject method is passed to the instance callback.

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

promise.race

const p = Promise.race([p1, p2, p3]);

This method wraps multiple Promise instances into a new Promise instance. If one instance of P1, P2, and P3 changes state first, then the state of P changes. The return value of the first changed Promise instance is passed to p’s callback.

static race(promiseList) {
    return new YuPromise((resolve, reject) = > {
        const length = promiseList.length;

        if (length === 0) {
            return resolve();
        } else {
            for (let i = 0; i < length; i++) {
                YuPromise.resolve(promiseList[i]).then(
                    (value) = > {
                        return resolve(value);
                    },
                    (reason) = > {
                        returnreject(reason); }); }}}); }Copy the code

Write some test code

const test = new YuPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(111);
  }, 1000);
});

const test2 = new YuPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(222);
  }, 2000);
});

const test3 = new YuPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(333);
  }, 3000);
});

YuPromise.race([test, test2, test3]).then(console.log); // Print 111 as expected
Copy the code

The complete code

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

// queueMicrotask pollfill
if (typeofqueueMicrotask ! = ="function") {
  queueMicrotask = function (callback) {
    Promise.resolve()
      .then(callback)
      .catch((e) = > {
        setTimeout(() = > {
          throw e;
        });
      });
  };
}

class YuPromise {
  constructor(fn) {
    this.status = PENDING;
    this._status = PENDING;
    this.value = null;
    this.reason = null;
    this.fulfilledCallbackList = [];
    this.rejectedCallbackList = [];
    try {
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
      this.reject(e); }}get status() {
    return this._status;
  }

  set status(newStatus) {
    this._status = newStatus;
    switch (newStatus) {
      case FULFILLED: {
        this.fulfilledCallbackList.forEach((callback) = > {
          callback(this.value);
        });
        break;
      }
      case REJECTED: {
        this.rejectedCallbackList.forEach((callback) = > {
          callback(this.reason);
        });
        break; }}}resolve(value) {
    if (this.status ! == PENDING)return;
    this.value = value;
    this.status = FULFILLED;
  }

  reject(reason) {
    if (this.status ! == PENDING)return;
    this.reason = reason;
    this.status = REJECTED;
  }

  then(onFulfilled, onRejected) {
    const realOnFulfilled = this.isFunction(onFulfilled)
      ? onFulfilled
      : (value) = > {
          return value;
        };
    const realOnRejected = this.isFunction(onRejected)
      ? onRejected
      : (reason) = > {
          throw reason;
        };
    const promise2 = new YuPromise((resolve, reject) = > {
      const fulfilledMicrotask = () = > {
        queueMicrotask(() = > {
          try {
            const x = realOnFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }}); };const rejectedMicrotask = () = > {
        queueMicrotask(() = > {
          try {
            const x = realOnRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }}); };switch (this.status) {
        case FULFILLED: {
          fulfilledMicrotask();
          break;
        }
        case REJECTED: {
          rejectedMicrotask();
          break;
        }
        case PENDING: {
          this.fulfilledCallbackList.push(fulfilledMicrotask);
          this.rejectedCallbackList.push(rejectedMicrotask); }}});return promise2;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  isFunction(param) {
    return typeof param === "function";
  }

  resolvePromise(promise2, x, resolve, reject) {
    // If newPromise and x point to the same object, reject newPromise as TypeError
    // This is to prevent endless loops
    if (promise2 === x) {
      return reject(
        new TypeError("The promise and the return value are the same")); }if (x instanceof YuPromise) {
      // If x is a Promise, make newPromise accept the state of x
      // Continue to execute x, and if you get a y, continue to parse y
      queueMicrotask(() = > {
        x.then((y) = > {
          this.resolvePromise(promise2, y, resolve, reject);
        }, reject);
      });
    } else if (typeof x === "object" || this.isFunction(x)) {
      // If x is an object or function
      if (x === null) {
        // null is also considered an object
        return resolve(x);
      }

      let then = null;

      try {
        // assign x. teng to then
        then = x.then;
      } catch (error) {
        // If an error e is thrown when taking the value x. teng, reject a promise based on e
        return reject(error);
      }

      // If then is a function
      if (this.isFunction(then)) {
        let called = false;
        // call x as the function's scope this
        // Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise
        try {
          then.call(
            x,
            // If resolvePromise is called with the value y, run resolvePromise
            (y) = > {
              // A variable called is required to ensure that it is called only once.
              if (called) return;
              called = true;
              this.resolvePromise(promise2, y, resolve, reject);
            },
            // If rejectPromise is invoked with argument r, reject the promise with argument r
            (r) = > {
              if (called) return;
              called = true; reject(r); }); }catch (error) {
          // If calling the then method throws an exception e:
          if (called) return;

          // Otherwise use e as the basis for rejecting the promisereject(error); }}else {
        // If then is not a function, execute a promise with an x argumentresolve(x); }}else {
      // If x is not an object or function, execute a promise with x as an argumentresolve(x); }}static resolve(value) {
    if (value instanceof YuPromise) {
      return value;
    }

    return new YuPromise((resolve) = > {
      resolve(value);
    });
  }

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

  static race(promiseList) {
    return new YuPromise((resolve, reject) = > {
      const length = promiseList.length;

      if (length === 0) {
        return resolve();
      } else {
        for (let i = 0; i < length; i++) {
          YuPromise.resolve(promiseList[i]).then(
            (value) = > {
              return resolve(value);
            },
            (reason) = > {
              returnreject(reason); }); }}}); }}Copy the code

Implement the Promise perimeter

Encapsulate a simple XHR request

  1. Use cb form
function ajax(url, success, fail) {
    const client = new XMLHttpRequest();
    client.open('GET', url);
    client.onreadystatechange = function () {
        if(this.readystate ! = =4) {
            return;
        }
        if(this.status === 200) {
            success(this.response);
        }else {
            fail(new Error(this.statusText));
        }
    }
    client.send();
}

// If request 2 depends on the result of request 1, request 2 is implemented in the callback of request 1, creating callback hell layer by layer
ajax('/test.json'.function (res) {
    console.log('success', res);
}, function(statusText) {
    console.log('error', statusText);
});
Copy the code
  1. Encapsulate it with a promise. (Ps: Promise encapsulates it with Async roots.
function ajaxAsync(url) {
  return new Promise((resolve, reject) = > {
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = function () {
      if (this.readystate ! = =4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText)); }}; client.send(); }); } ajaxAsync("/test.json")
  .then((res) = > {
    console.log("success", res);
  })
  .catch((err) = > {
    console.log("error", err);
  });
Copy the code

The step to change to a PROMISE is to internally construct the Promise instance and execute the corresponding function that changes the promise state where the callback function was executed before

Implement a promise.chain, one by one

Implementation idea:

  1. For of async await
  2. Array reduce method (convert to promise1. Then (() => {return promise2}))
/ / the topic
function promiseCreator1() {
  return new Promise((resolve) = > {
    setTimeout(resolve, 1000);
  });
}

function promiseCreator2() {
  return new Promise((resolve) = > {
    setTimeout(resolve, 1000);
  });
}

const promiseCreatorList = [promiseCreator1, promiseCreator2];
Copy the code

For of async await

async function main() {
  async function forOfLoop() {
    for (const promiseInstance of promiseCreatorList) {
      awaitpromiseInstance(); }}await forOfLoop();
}
main();
Copy the code

Array reduce method

// Reduce can accept the second argument, but we can give it an empty Promise instance, which ensures that all Promise instances are present during the iteration
const promiseChain = promiseCreatorList.reduce((prev, cur) = > {
  return prev.then(cur);
}, Promise.resolve());

// The last value returned must also be a Promise instance
promiseChain.then(() = > {
  console.log("end");
});
Copy the code

Promise.allSettled

The promise.allSettled () method returns a Promise after all the given promises have fulfilled or rejected, with an array of objects, each representing the corresponding Promise result.

function PromiseAllSeltted(promiseArray) {
  return new Promise(function (resolve, reject) {
    if (!Array.isArray(promiseArray)) {
      return reject(new TypeError("arguments muse be an array"));
    }
    let counter = 0;
    let promiseNum = promiseArray.length;
    let resolvedArray = [];
    for (let i = 0; i < promiseNum; i++) {
      Promise.resolve(promiseArray[i])
        .then((value) = > {
          resolvedArray[i] = {
            value,
            status: "fulfilled"}; }) .catch((reason) = > {
          resolvedArray[i] = {
            reason,
            status: "rejected"}; }) .finally(() = > {
          counter++;
          if(counter == promiseNum) { resolve(resolvedArray); }}); }}); }/ / test
const pro1 = new Promise((res, rej) = > {
  setTimeout(() = > {
    res("1");
  }, 1000);
});
const pro2 = new Promise((res, rej) = > {
  setTimeout(() = > {
    rej("2");
  }, 2000);
});
const pro3 = new Promise((res, rej) = > {
  setTimeout(() = > {
    res("3");
  }, 3000);
});

const proAll = PromiseAllSeltted([pro1, pro2, pro3])
  .then((res) = > console.log(res))
  .catch((e) = > {
    console.log(e);
  });
Copy the code

Hand write a promise.all

It takes an array of promises or constants, returns a Promise, and it wraps the array with Promise. Resolve, turning non-promise elements into promises, Resolve is resolved when all promises in the array are fulfilled, and reject is rejected when a Promise fails

When one Promise fails, will other promises be implemented? Yes, promises are executed on instantiation, and.then just gets the result

function PromiseAll(promiseArray) {
  // The first thing to do is return a promise
  return new Promise((resolve, reject) = > {
    // Check if it is an array
    if (!Array.isArray(promiseArray)) {
      return reject(new Error("The argument passed in needs to be an array."));
    }
    let resArr = [];
    let count = 0; ResArr [10] = 1; resArr. Length = 11; Js will leave space out
    const promiseLen = promiseArray.length;
    ForEach = forEach; forEach = forEach; forEach = forEach; forEach = forEach
    for (let i = 0; i < promiseLen; i++) {
      // Promise. Resolve can also convert constants to promises
      Promise.resolve(promiseArray[i])
        .then((res) = > {
          Promise.all receives the elements in what order and returns the results in what order. But with push, it is possible that one of the promises will execute faster and push ahead
          // resArr.push(res);
          resArr[i] = res;
          count++;
          // Do not place it outside.then, because it is executed synchronously
          if (count === promiseLen) {
            resolve(resArr);
          }
        })
        .catch((err) = >reject(err)); }}); }Copy the code

Implement a cachePromise

All promises are implemented when they are instantiated

For example, if you want to call an interface and the request is a constant, it may be used by many pages. If there is no global state management, it will waste the performance of the server to call each page and execute it once

So I’m going to use the decorator here

Decoration popularization

Target is the stereotype of the class that it belongs to. Name is the property descriptor that it modifies, so configuration like writable and so on. Value is the property value, see Object.defineProperty

Defining a class in ES6 is just a syntactic sugar, but when we add a property to a class, we call object.defineProperty, which takes three parameters: target, name, and Descriptor

// Attribute descriptor
let descriptor = {
  value: function () {
    console.log("meow ~");
  },
  enumerable: false.configurable: true.writable: true};// Property descriptor modified with readonly decorator
descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;

// Add attributes to classes in ES6
Object.defineProperty(Cat.prototype, "say", descriptor);
Copy the code

When a decorator ACTS on the class itself, we operation object and the class itself, and when a decorator effect on a particular attribute of the class, our operation object is neither the class itself, nor the attributes of a class, but its descriptor (descriptor), and the descriptor record with us for all the information in this attribute, so, We can extend and encapsulate it freely to achieve the same purpose as the decorator mentioned earlier.

Of course, you can also extend and encapsulate directly on Target if you prefer

CachePromise implementation

There’s nothing you can do about it. The decorator is for ES7. If it’s not supported, you might need to install a Babel plugin

If you have a cache, you need to consider the age, no cache is permanent, here to mark the expiration time

const cacheMap = new Map(a);function enableCache(target, name, descriptor) {
  const val = descriptor.value;
  descriptor.value = async function (. args) {
    const cacheKey = `${name}The ${JSON.stringify(args)}`;
    if(! cacheMap.get(cacheKey)) {const cacheValue = Promise.resolve(val.apply(this, args)).catch((_) = > {
        cacheMap.set(cacheKey, null);
      });
      cacheMap.set(cacheKey, cacheValue);
    }
    return cacheMap.get(cacheKey);
  };
  return descriptor;
}

class PromiseClass {
  @enableCache()
  static async getInfo(){} } PromiseClass.getInfo.then(...) .catch(...) ;Copy the code

Use of promise.race: If you have a large number of images to display on your page, what other forms of loading can limit the number of simultaneous loads other than lazy loading? (Code problem, write code to achieve concurrent control)

function limitLoad(urls, handler, limit) {}function loadImg(url) {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve();
    }, url.time);
  });
}

const urls = [{ info: "info".time: 200}... ] ; limitLoad(urls, loadImg,3);

// 1, 2, 3, 4, 5, 6, 7
// Request 1, 2, 3 first, if one of them is finished, then choose one from the rest
// Ensure that one time is 3 simultaneous requests
// Use promise. race to resolve once one of the races completes
function limitLoad(urls, handler, limit) {
    // Do not have external impact, copy
    const sequeue = [].cancat(urls);

    // Take three, notice that splice() will change the array, so the three used here will not actually exist in the next iteration
    const promises = sequeue.splice(0.3).map((url, index) = > {
        return handler(url).then(() = > {
            // Record the current index for the following padding
            return index;
        });
    })

    // Resolve every time the first one completes
    const p = Promise.race(promises);

    // This is a synchronous for loop that starts.then().then()
    for(let i = 0, len= sequeue.length; i < len; i ++) {
        p = p.then((res) = > {
            promises[res] = handler(sequeue[i]).then(() = > {
                return res;
            })
            return Promise.race(promises); }}})/ / ideas:
// Three requests are intercepted to send, then three requests are sent, then resolve when one request is completed
// Then make a new request at the current resolve location and start a race
Then ().then().then().then().then().then()
// Using promise.race () for parallelism, the Promise will be executed at initialization, but the entire Promise will return a result. The promises in the Promise array will still be executed
Copy the code

See the topic to talk

Why is the promise resolve value output undefined

const test = new YuPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(111);
  }, 1000);
}).then((value) = > {
  console.log("then");
});

setTimeout(() = > {
  console.log(test);
}, 3000);
Copy the code

Then return undefined, so the value is undefined. If you explicitly return a value, it is not undefined; Such as the return value.

Then returns a new Promise, so what’s the point of using an array to store the callback function when the original Promise was implemented?

When we ask this question, we assume that when we do chain calls.

At this point, each dot then returns a new promise, so ledCallBackList is an empty array for each callback.

In this case, it really doesn’t make sense to store callbacks in arrays; you could just store them in a variable.

const test = new YuPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(111);
  }, 1000);
})
  .then((value) = > {})
  .then(() = > {});
Copy the code

But there’s another way promises can be used, in which the promise instance is the same, and the existence of the array makes sense

const test = new YuPromise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(111);
  }, 1000);
});

test.then(() = > {});
test.then(() = > {});
test.then(() = > {});
test.then(() = > {});
Copy the code

Why do I print a promise in a catch callback that says the state is pending

const test = new YuPromise((resolve, reject) = > {
  setTimeout(() = > {
    reject(111);
  }, 1000);
}).catch((reason) = > {
  console.log("Error" + reason);
  console.log(test);
});

setTimeout(() = > {
  console.log(test);
}, 3000);
Copy the code
  1. The catch function returns a new promise, and test is the new promise
  2. In the catch callback, when the promise is printed, the entire callback is not complete (so the state is pending), and the state changes only when the entire callback is complete
  3. The callback function of catch, if successfully executed, will change the state of this new Promise to depressing

What does this code print? (Note that node versions vary.)

const promise = new YuPromise((resolve) = > {
  setTimeout(() = > {
    resolve();
  }, 1000);
}).then(() = > {
  console.log("case1", promise);
});

setTimeout(() = > {
  console.log("case2", promise);
}, 1000);
Copy the code

Promise instantiation is synchronous. The setTimeout in a Promise is added to the task queue (s1) and then the global setTimeout is added to the task queue (S2) and then s1 executes, resolve is a microtask, Joining the microtask queue emptens the microtask queue before the current macro task returns control to the browser, and case1 prints, and s2 is executed as pending. Since then, undefined is returned, s2 is in fullfilled state

  1. In the browser it would be case 1 -> case2, okay
  2. For example, if you run the following command on node 10.16.3, case2 executes first. If you run the command on node 14.17.1, case1 executes first
// xxx@MacBook-Pro Promise % node -v
/ / v10.16.3
// xxx@MacBook-Pro Promise % node Promise.js
// case2
// case1
// xxx@MacBook-Pro Promise % nvm use 14.17.1
// Now using node v14.17.1 (npm v6.14.13)
// xxx@MacBook-Pro Promise % node -v
/ / v14.17.1
// xxx@MacBook-Pro Promise % node Promise.js
// case1
// case2
Copy the code

Other problems

  1. Does lazy loading of images use Promise?

Lazy loading of images is usually done by using the browser’s native Observer to listen for elements to enter the viewable area and replacing the element’s SRC for lazy loading

  1. There’s no logic in a getter, so why write a getter?

Because getters and setters come in pairs

  1. Why use a private variable?

Because if you call this.status in the getter, it’s an infinite loop, and you use a private variable _status to prevent nesting

  1. Why wrap queueMicrotask instead of setTimeout?

Because setTimeout is a classic macro task

  1. Why isn’t Promise. Race traversed, passed to resolve, executed more than once?

The resolve or reject operation is locked when it executes.

  1. Why use arrays?

Because arrays can hold multiple functions, and also satisfy the order condition

  1. Then each time you return a new Promise, the new Promise will initialize the array to be empty, so what’s the point of using the array? Why not use a variable?

Because the current Promise instance can be called multiple times, not necessarily chained

Because you can

promise.then(() = > {});
promise.then(() = > {});
promise.then(() = > {});
promise.then(() = > {});
promise.then(() = > {});
Copy the code
  1. What is resolvePromise for?

In order to solve for x

  1. After the first promise in the race method list is resolved, the state changes and the rest of the list is implemented and their results are nothing?

Yes, race. The fastest runner is’ resolve ‘or’ reject ‘, and the rest is’ run ‘

Matters needing attention

  1. A try catch can only catch an error thrown synchronously, not an error thrown by an asynchronous task, nor can a promise error be caught unless async await is used

  2. “Then”, “catch”, “catch”, “catch”, “catch”, “catch”, “catch”, “catch” So if you need to differentiate, you can catch each Promise.

  3. The state of each Promise is determined only by the state returned by the previous Promise, reject terminates if the previous Promise returned a value that was not an exception

References & Articles to be read

  • Promise – MDN
  • Using_promises – MDN
  • Promises/A + specification
  • Promise.allSettled() – MDN
  • Decoration – JELLY
  • queueMicrotask – MDN
  • Execution context – MDN
  • Collaborative asynchronous javascript-MDN
  • I Promise you
  • JavaScript execution mechanism – Stubborn gold
  • Node Event loop mechanism – Node