Promise

Purpose: To better use Promise everyday use.

Promise concept

Promise is a JavaScript built-in object that is also a constructor. In particular: the Promise constructor is intended to address asynchrony; Synchronized code can also be used (overkill).

As a built-in object

A static method

  • Promise.all(iterable)
  • Promise.allSettled(iterable)
  • Promise.any(iterable)
  • Promise.race(iterable)
  • Promise.reject(reason)
  • Promise.resolve(value)

As a constructor

Promise.prototype

  • Promise.prototype.constructor
  • Promise.prototype.then(onFulfilled, onRejected)
    • Ondepressing is optional
    • OnRejected is optional
  • Promise.prototype.catch(onRejected)
    • OnRejected is optional
  • Promise.prototype.finally(onFinally)
    • The onFinally parameter is optional

Promise instance

const promise = new Promise(function(resolve, reject) {
  / /... Omit code
  if (/* Asynchronous operation succeeded */){
    resolve(value);
  } else{ reject(error); }});Copy the code
Instructions for executor, resolve, and reject functions
  • The Promise constructor accepts oneexecutorFunction as argument
  • executorThe two arguments to the function areresolveFunctions andrejectfunction
    • resolveThe Promise () function changes the state of the Promise object from “incomplete” to “successful.pendingintoresolved), is called when the asynchronous operation succeeds, and passes the result of the asynchronous operation as a parameter;
    • rejectThe Promise () function changes the state of the Promise object from “incomplete” to “failed.pendingintorejected), is called when an asynchronous operation fails, and an error reported by the asynchronous operation is passed as an argument.
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
Copy the code
Description of the THEN method
  • Once the Promise instance is generated, you can use itthenMethods specified separatelyfulfilledState andrejectedState callback function.
  • thenA method can take two arguments.
    • Parameter Optional: Ondepressing and onRejected are both optional parameters and do not have to be provided.
    • ifonFulfilledIs not a function, which must be ignored
    • ifonRejectedIs not a function, which must be ignored
    • ifonFulfilledIs the function:
      • It must be called when the promise execution ends, and its first argument is the promise’s final value, value
      • It cannot be called until the promise execution ends
      • It cannot be invoked more than once
    • ifonRejectedIs the function:
      • When a promise is rejected, it must be called, and its first argument is reason, the promise’s reason
      • A promise cannot be invoked until it is rejected
      • It cannot be invoked more than once
  • thenMethod returns a new Promise instance (note, not the original Promise instance). So you can write it chained, where a then method is followed by another THEN method.

Handwritten Promise constructors for better understanding promises (PromiseA+ standard)

Special note: Implement the Promise constructor with a class

1. The implementationexecutorThe function,resolveThe function,rejectfunction

class Promise{
  / / the constructor
  constructor(executor){
    / / success
    let resolve = () = >{};/ / fail
    let reject = () = >{};Execute the executor function immediatelyexecutor(resolve, reject); }}Copy the code

2. Implement the state of the Promise constructor

  • The Promise constructor has three states
    • pending(In progress)
    • fulfilled(Successful)
    • rejected(Failed)
  • The Promise constructor switches state
    • pendingIs the initial state
    • throughResolve functionCan be converted tofulfilled(Successful state)
      • On success, it cannot go to another state and must have an immutable value.
    • throughReject functionCan be converted torejected(Failed state)
      • When a failure occurs, it cannot be changed to another state, and there must be an immutable reason.
    • ifThe executor functionIf an error occurs, execute it directlyReject function
  • The Promise constructor has several key properties
    • state: Status description
    • value: success value
    • reason: Cause of failure
class Promise{
  constructor(executor){
    // Initialize state to wait state (state can only change once)
    // It can only be changed if the state is pending
    this.state = 'pending';
    // Success initial value
    this.value = undefined;
    // Failure cause Initial value
    this.reason = undefined;
    
    // resolve, reject
    const resolve = (value) = > {
      // If state is not pending, the following condition is false and the block-level scoped code is no longer executed
      if (this.state === 'pending') {
        // State will be fulfilled successfully
        this.state = 'fulfilled';
        // Update the successful value
        this.value = value; }};const reject = (reason) = > {
      // If state is not pending, the following condition is false and the block-level scoped code is no longer executed
      if (this.state === 'pending') {
        // reject State changes to rejected state
        this.state = 'rejected';
        // Update failure cause
        this.reason = reason; }};// If the executor executes a reject error, it executes reject directly, passing the error message as an input parameter
    try{
      executor(resolve, reject);
    } catch(error) { reject(error); }}}Copy the code

3. Then method to implement Promise instance (promise.prototype.then)

  • The then method takes two (optional) arguments:onFulfilledFunctions andonRejectedfunction
  • onFulfilledThe function takes a success valuevalue
  • onRejectedThe argument to the function is the cause of failurereason

Executor functions execute synchronously

class Promise{
  constructor(executor){ 
      / /... Omit code
  }
  // The then method has two parameters onFulfilled and onRejected
  then(onFulfilled,onRejected) {
    // Perform onFulfilled function, passing in the successful value
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    // If the status is rejected, execute the onRejected function and pass in the failure reason
    if (this.state === 'rejected') {
      onRejected(this.reason); }; }}Copy the code

The executor function calls resolve or reject in asynchrony

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    // ondepressing function callback array
    this.onResolvedCallbacks = [];
    // The onRejected function callback array
    this.onRejectedCallbacks = [];
    const resolve = (value) = > {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        // Once resolve executes, call the onFulfilled function callback array
        this.onResolvedCallbacks.forEach(fn= >fn()); }};const reject = (reason) = > {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        // Once reject is executed, onRejected calls back to the array
        this.onRejectedCallbacks.forEach(fn= >fn()); }};try{
      executor(resolve, reject);
    } catch(err) { reject(err); }}then(onFulfilled,onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
    // State is pending
    if (this.state === 'pending') {
      // The onFulfilled function is passed the onFulfilled function callback array
      this.onResolvedCallbacks.push(() = >{
        onFulfilled(this.value);
      })
      // onRejected passes the onRejected function callback array
      this.onRejectedCallbacks.push(() = >{
        onRejected(this.reason); })}}}Copy the code
  • The code above simultaneously implements the problem of multiple THEN calls to the same Promise instance

    // Multiple THEN cases
    const p = new Promise(a); p.then(); p.then();Copy the code

4. Implement the chain call of the Promise instance

The then, catch, and finally of the prototype object that calls the Promise constructor all return a new Promise instance

class Promise{
  constructor(executor){
    / /... Omit code
  }
  then(onFulfilled,onRejected) {
    // Return promise2
    const promise2 = new Promise((resolve, reject) = >{
      if (this.state === 'fulfilled') {
        const x = onFulfilled(this.value);
        // This is a big promise. // This is a big promise. // This is a big promise
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'rejected') {
        const x = onRejected(this.reason);
        // resolvePromise () (x, promise2, onRejected)
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() = >{
          const x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallbacks.push(() = >{
          const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }}}));// Return promise, complete the chain
    returnpromise2; }}Copy the code
ResolvePromise function implementation
  1. ifpromiseandxPoint to the same object (x === promise2), will cause a circular reference, waiting for its own completion, will report “circular reference” error TypeError as the reason to refuse to implement the promise
const p = new Promise(resolve= > {
  resolve('success');
});
const p1 = p.then(data= > {
  // Circular reference, waiting for their own completion, a lifetime
  return p1;
})
Copy the code
  1. Compare X with promise2 to avoid circular applications
  2. ifx== nulltypeof x ! == 'object'typeof x ! == 'function'directlyresolv(x)
  3. ifx ! = null && (typeof x === 'object' || typeof x === 'function')
    • X is not a promise, directlyresolv(x)
    • X is a promise that calls the then method directly
function resolvePromise(promise2, x, resolve, reject){
  // Loop reference error
  if(x === promise2){
    / / reject an error
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // Prevent multiple calls
  let called;
  // x is not null and x is an object or function
  if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+ specifies the then method that declares then = x
      const then = x.then;
      // If then is a function, the default is Promise
      if (typeof then === 'function') { 
        // Let then execute the first argument this, followed by a successful callback and a failed callback
        // This refers to x in the then method
        then.call(x, y= > {
          // Only one can be called for success or failure
          if (called) return;
          called = true;
          // the result of resolve is still a promise
          resolvePromise(promise2, y, resolve, reject);
        }, err= > {
          // Only one can be called for success or failure
          if (called) return;
          called = true;
          reject(err);Reject (reject); reject (reject)})}else {
        resolve(x); // You can do just that}}catch (e) {
      // Also a failure
      if (called) return;
      called = true;
      // Then is not validreject(e); }}else{ resolve(x); }}Copy the code

5. Improve the onFulfilled function and onRejected function in the THEN method

  • This is a big pity, onFulfilled,onRejected are optional parameters, if they are not functions,Must be ignored.
    • Ondepressing is a common value, which will be directly equal to value => value when it is successful
    • This is a big pity. OnRejected returns a common value. If value => value when it fails, it will run into the next onpity in the THEN
  • OnFulfilled or onRejected cannot be called asynchronously, which must be fulfilled asynchronously. We’ll use setTimeout to solve the asynchronous problem
  • This is a pity or onRejected. ()
class Promise{
  constructor(executor){
      / /... Omit code
  }
  then(onFulfilled,onRejected) {
    // onFulfilled if this is not a function, ignore onFulfilled and return value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
    // onRejected if it is not a function, just ignore onRejected and throw an error
    onRejected = typeof onRejected === 'function' ? onRejected : err= > { throw err };
    let promise2 = new Promise((resolve, reject) = > {
      if (this.state === 'fulfilled') {
        / / asynchronous
        setTimeout(() = > {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      };
      if (this.state === 'rejected') {
        / / asynchronous
        setTimeout(() = > {
          // If an error is reported
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() = > {
          / / asynchronous
          setTimeout(() = > {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0);
        });
        this.onRejectedCallbacks.push(() = > {
          / / asynchronous
          setTimeout(() = > {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0)}); }; });// Return promise, complete the chain
    returnpromise2; }}Copy the code

Implement catch and resolve, Reject, race, and All methods

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value= > {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn= >fn()); }};let reject = reason= > {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn= >fn()); }};try{
      executor(resolve, reject);
    } catch(err) { reject(err); }}then(onFulfilled,onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
    onRejected = typeof onRejected === 'function' ? onRejected : err= > { throw err };
    let promise2 = new Promise((resolve, reject) = > {
      if (this.state === 'fulfilled') {
        QueueMicrotask can be used instead of setTimeout
        setTimeout(() = > {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      };
      if (this.state === 'rejected') {
        setTimeout(() = > {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch(e) { reject(e); }},0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0);
        });
        this.onRejectedCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(e) { reject(e); }},0)}); }; });return promise2;
  }
  / / catch method
  catch(fn){
    return this.then(null,fn); }}function resolvePromise(promise2, x, resolve, reject){
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') { 
        then.call(x, y= > {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err= > {
          if(called)return;
          called = true; reject(err); })}else{ resolve(x); }}catch (e) {
      if(called)return;
      called = true; reject(e); }}else{ resolve(x); }}/ / resolve method
Promise.resolve = function(val){
  return new Promise((resolve,reject) = >{
    resolve(val)
  });
}
/ / reject method
Promise.reject = function(val){
  return new Promise((resolve,reject) = >{
    reject(val)
  });
}
/ / race method
Promise.race = function(promises){
  return new Promise((resolve,reject) = >{
    for(let i=0; i<promises.length; i++){ promises[i].then(resolve,reject) }; })}//all (get all promises, execute then, place the results in an array, and return them together)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject) = >{
    for(let i=0; i<promises.length; i++){ promises[i].then(data= >{
        processData(i,data);
      },reject);
    };
  });
}
Copy the code

Promise is used with async/await

let result = true;
// Get data from the server interface
async function getServerData(){
    // simulate the request
    const p = new Promise((resolve,reject) = >{
        setTimeout(() = > {
            if(result){
                resolve('data')}else {
                reject('err')}},1000);
    })
    return p
}
// For the front-end to call the interface
async function dataController() {
    try {
       const data = await getServerData();
       console.log('data',data);
    } catch (error) {
        // You can catch reject
        console.log('error', error); }}// Front-end interface
dataController()
Copy the code
  • Special instructionstry... catchIn thecatchCan capturerejectFunction of thereason

Refer to the article

Classic INTERVIEW Questions: The most detailed handwritten Promise tutorial ever

About Generator Functions