Step by step analysis of handwritten promise

Posted by cyberdwarf on Mon, 20 Dec 2021 01:17:39 +0100

Promise is a constructor for asynchronous requests introduced by es6 to help solve the problem of callback hell. The following content will customize the implementation of promise, which only includes basic use, so some boundary conditions are not taken into account.
If you are not familiar with promise usage, you can move
Understanding and use of Promise (I)
Understanding and use of Promise (II)

executor

Firstly, the basic structure of promise is established

Define constructor

The executor part of promise is directly executed in the main thread

class icePromise {
    constructor(executor){
        const resolve = () => {
            console.log('resolve Called')
        }
        const reject = () => {
            console.log('reject Called')
        }
    }
    executor(resolve, reject)
}

const promise = new icePromise((resolve, reject)=>{
    resolve()
})
Define status

1. Define constants

const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'

2. When you create an instance through a constructor, you should need a state, so it is defined in the class

this.status = STATUS_PENDING 

3. State judgment in the resolve and reject methods
When it is pending, the resolve or reject method can be executed. Modify the status before execution

then method

In onFulfilled/onRejected, the callback function in then will be executed, and the two functions will be bound to the properties of the instance
1. Define a then method in the class

then(onFulfilled, onRejected){
    this.onFulfilled = onFulfilled
    this.onRejected = onRejected
}

2. Execute this. In resolve and reject, respectively Onfulfilled and this In onrejected
An error will be reported at this time, because the executor will execute immediately, and the function in then is a micro task,
It will be executed after the main thread completes execution

3. Queue microtask is added to resolve and reject

The first version of the overall architecture is completed

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class IcePromise {
  constructor(executor) {
    this.status = STATUS_PENDING;
    
    const resolve = (value) => {
      if (this.status === STATUS_PENDING) {
        this.status = STATUS_FULFILLED;
        queueMicrotask(() => {
          this.onFulfilled(value);
        });
      }
    };
    
    const reject = (reason) => {
      if (this.status === STATUS_PENDING) {
        this.status = STATUS_REJECTED;
        queueMicrotask(() => {
          this.onRejected(reason);
        });
      }
    };
    
    executor(resolve, reject);
  }
  
  then(onFulfilled, onRejected) {
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
  }
}

const promise = new IcePromise((resolve, reject) => {
  resolve("resolve");
  reject("reject");
});

promise.then(
  (value) => {
    console.log("success1", value);
  },
  (reason) => {
    console.log("fail1", reason);
  }
);

promise.then(
  (value) => {
    console.log("success2", value);
  },
  (reason) => {
    console.log("fail2", reason);
  }
);

The state of promise is returned twice, and only the first resolve is executed. The successful or failed functions in the then method are also correct

However, the problem is that the then method is executed twice, and only the second one is executed. The then method is optimized below.

then

Solve the problem of calling the then method multiple times

1. Variables defined in the constructor are used to collect all successful / failed callback functions

this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []

2. Add to the array by push ing in the then method

this.onFulfilledCallbacks.push()
this.onRejectedCallbacks.push()

3. Traverse in resolve and reject
this. Onfulfilled callbacks and this Methods in onrejectedcallbacks

The code is as follows

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class IcePromise {
  constructor(executor) {
    this.status = STATUS_PENDING;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    
    const resolve = (value) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          this.onResolvedCallbacks.forEach((fn) => {
            fn(value);
          });
        });
      }
    };
    
    const reject = (reason) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          this.onRejectedCallbacks.forEach((fn) => {
            fn(reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  
  then(onFulfilled, onRejected) {
      this.onResolvedCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
  }
}

// Test code
const promise = new IcePromise((resolve, reject) => {
  resolve("resolve---");
  reject("----reject");
});
promise.then(
  (value) => {
    console.log("res1", value);
  },
  (reason) => {
    console.log("err1", reason);
  }
)
promise.then(
  (value) => {
    console.log("res2", value);
  },
  (reason) => {
    console.log("err2", reason);
  }
);
// Call after determining the status
setTimeout(() => {
  promise.then(
    (res) => {
      console.log("res3", res);
    },
    (err) => {
      console.log("err3", err);
    }
  );
}, 1000);

Solve the problem of multiple calls of then, but there are still other problems. One is that the resolve and reject methods are executed at the same time, and the other is that the then method of promise, which is delayed by a timer, does not output a response result

Solve the problem of delayed call

1. Save value and reason

this.value = undefined
this.reason = undefined

The resolve and reject methods are given to this Value and this Reason assignment

2. State judgment in then method
When the status is pending, continue to add functions to onfulledcallbacks and onRejectedCallbacks arrays; When the status is not pending, the onFulfilled or onRejected methods are executed directly

if (this.status === STATUS_FULFILLED && onFulfilled) {
  onFulfilled(this.value);
}
if (this.status === STATUS_REJECTED && onRejected) {
  onRejected(this.reason);
}

if (this.status === STATUS_PENDING) {
  this.onResolvedCallbacks.push(onFulfilled);
  this.onRejectedCallbacks.push(onRejected);
}

3. Change of pending status
① If it is judged not to be pending in queueMicrotask, return
② Modify pending status

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

class IcePromise {
  constructor(executor) {
    this.status = STATUS_PENDING;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;
    
    const resolve = (value) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_FULFILLED;
          this.value = value;
          this.onResolvedCallbacks.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    
    const reject = (reason) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedCallbacks.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  
  then(onFulfilled, onRejected) {
    if (this.status === STATUS_FULFILLED && onFulfilled) {
      onFulfilled(this.value);
    }
    if (this.status === STATUS_REJECTED && onRejected) {
      onRejected(this.reason);
    }
    if (this.status === STATUS_PENDING) {
      this.onResolvedCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
    }
  }
}

// Test code
const promise = new IcePromise((resolve, reject) => {
  resolve("resolve---");
  reject("----reject");
});

promise.then(
  (value) => {
    console.log("res1", value);
  },
  (reason) => {
    console.log("err1", reason);
  }
)

promise.then(
  (value) => {
    console.log("res2", value);
  },
  (reason) => {
    console.log("err2", reason);
  }
);

// Call after determining the status
setTimeout(() => {
  promise.then(
    (res) => {
      console.log("res3", res);
    },
    (err) => {
      console.log("err3", err);
    }
  );
}, 1000);

promise.then(
  (value) => {
    console.log("res4", value);
  },
  (reason) => {
    console.log("err4", reason);
  }
).then(
  (value) => {
    console.log("res5", value);
  },
  (reason) => {
    console.log("err5", reason);
  }
)

It solves the problems of multiple calls of resolve and reject and delayed call of timer, but it is found that then cannot make chain call at this time

Solve the problem of chain call

1. The then method returns a new icePromise and puts the judgment logic in it
2,this. Onfulfilled callbacks and this Onrejectedcallbacks passes in the callback function,
The callback function returns the execution result of the resolve or reject function
3. Encapsulating tool functions for handling try catch

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";
const respondWithCatchError = (fn, value, resolve, reject) => {
  try {
    const result = fn(value);
    resolve(result);
  } catch (error) {
    reject(error);
  }
};

class IcePromise {
  constructor(executor) {
    this.status = STATUS_PENDING;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;
    
    const resolve = (value) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_FULFILLED;
          this.value = value;
          this.onResolvedCallbacks.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    
    const reject = (reason) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedCallbacks.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  
  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      if (this.status === STATUS_FULFILLED && onFulfilled) {
        respondWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === STATUS_REJECTED && onRejected) {
        respondWithCatchError(onRejected, this.reason, resolve, reject);
      }
      if (this.status === STATUS_PENDING) {
        this.onResolvedCallbacks.push(() => {
          respondWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedCallbacks.push(() => {
          respondWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
}
// Test code
const promise = new IcePromise((resolve, reject) => {
  resolve("resolve---");
  reject("----reject");
});
promise
  .then(
    (value) => {
      console.log("res1", value);
    },
    (reason) => {
      console.log("err1", reason);
    }
  )
  .then(
    (value) => {
      console.log("res2", value);
    },
    (reason) => {
      console.log("err2", reason);
    }
  )
  .then(
    (res) => {
      console.log("res3", res);
    },
    (err) => {
      console.log("err3", err);
    }
  );

At this point, the then function can be called in a chain, and the basic functions have been realized~

catch

The catch function receives a failed callback
1. Call the then method and add the onRejected method to the callback of the second promise

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

2. In the then method, judge the onRejected passed in, and throw an exception when there is no delivery

const defaultOnRejected = (reason) => {
  throw reason;
};
onRejected = onRejected || defaultOnRejected;

The overall implementation is as follows

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

const respondWithCatchError = (fn, value, resolve, reject) => {
  try {
    const result = fn(value);
    resolve(result);
  } catch (error) {
    reject(error);
  }
};

class IcePromise {
  constructor(executor) {
    this.status = STATUS_PENDING;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;

    const resolve = (value) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_FULFILLED;
          this.value = value;
          this.onResolvedCallbacks.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };

    const reject = (reason) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedCallbacks.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };

    executor(resolve, reject);
  }

  then(onFulfilled, onRejected) {
    const defaultOnFulfilled = (value) => {
      return value;
    };
    const defaultOnRejected = (reason) => {
      throw reason;
    };
    onFulfilled = onFulfilled || defaultOnFulfilled;
    onRejected = onRejected || defaultOnRejected;

    return new Promise((resolve, reject) => {
      if (this.status === STATUS_FULFILLED && onFulfilled) {
        respondWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === STATUS_REJECTED && onRejected) {
        respondWithCatchError(onRejected, this.reason, resolve, reject);
      }

      if (this.status === STATUS_PENDING) {
        this.onResolvedCallbacks.push(() => {
          respondWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedCallbacks.push(() => {
          respondWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }

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

const promise = new IcePromise((resolve, reject) => {
  reject("----reject");
  resolve("resolve---");
});

// Test code
promise
  .then((value) => {
    console.log("res1", value);
  })
  .then((value) => {
    console.log("res2", value);
  })
  .catch((error) => {
    console.log("catch", error);
  });

The results are as follows

finally

finally method will be executed after the resolve or reject method is executed

finally(onFinally){
    this.then(()=>{
        onFinally()
    }, ()=>{
        onFinally()
    })
}

The overall implementation is as follows

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

const respondWithCatchError = (fn, value, resolve, reject) => {
  try {
    const result = fn(value);
    resolve(result);
  } catch (error) {
    reject(error);
  }
};

class IcePromise {
  constructor(executor) {
    this.status = STATUS_PENDING;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;
    
    const resolve = (value) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_FULFILLED;
          this.value = value;
          this.onResolvedCallbacks.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    
    const reject = (reason) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedCallbacks.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  
  then(onFulfilled, onRejected) {
    const defaultOnFulfilled = (value) => {
      return value;
    };
    const defaultOnRejected = (reason) => {
      throw reason;
    };
    onFulfilled = onFulfilled || defaultOnFulfilled;
    onRejected = onRejected || defaultOnRejected;
    return new Promise((resolve, reject) => {
      if (this.status === STATUS_FULFILLED && onFulfilled) {
        respondWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === STATUS_REJECTED && onRejected) {
        respondWithCatchError(onRejected, this.reason, resolve, reject);
      }
      if (this.status === STATUS_PENDING) {
        this.onResolvedCallbacks.push(() => {
          respondWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedCallbacks.push(() => {
          respondWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
  
  catch(onRejected) {
    this.then(null, onRejected);
  }
  
  finally(onFinally) {
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }
}

// Test code
const promise = new IcePromise((resolve, reject) => {
  reject("----reject");
});
promise
  .then(
    (value) => {
      console.log("res1", value);
    },
    (reason) => {
      console.log("err1", reason);
    }
  )
  .finally(() => {
    console.log("finally");
  });

resolve/reject

resolve and reject are Promise class methods, which can also be implemented by calling the then method

static resolve(value){
    return new icePromise((resolve)=>resolve(value))
}

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

The complete implementation is as follows

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";

const respondWithCatchError = (fn, value, resolve, reject) => {
  try {
    const result = fn(value);
    resolve(result);
  } catch (error) {
    reject(error);
  }
};

class IcePromise {
  constructor(executor) {
    this.status = STATUS_PENDING;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;
    
    const resolve = (value) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_FULFILLED;
          this.value = value;
          this.onResolvedCallbacks.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    
    const reject = (reason) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedCallbacks.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  
  then(onFulfilled, onRejected) {
    const defaultOnFulfilled = (value) => {
      return value;
    };
    const defaultOnRejected = (reason) => {
      throw reason;
    };
    onFulfilled = onFulfilled || defaultOnFulfilled;
    onRejected = onRejected || defaultOnRejected;
    return new Promise((resolve, reject) => {
      if (this.status === STATUS_FULFILLED && onFulfilled) {
        respondWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === STATUS_REJECTED && onRejected) {
        respondWithCatchError(onRejected, this.reason, resolve, reject);
      }
      if (this.status === STATUS_PENDING) {
        this.onResolvedCallbacks.push(() => {
          respondWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedCallbacks.push(() => {
          respondWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
  
  catch(onRejected) {
    this.then(null, onRejected);
  }
  
  finally(onFinally) {
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }
  
  static resolve(value) {
    return new Promise((onResolve) => {
      onResolve(value);
    });
  }
  
  static reject(reason) {
    return new Promise((onResolve, onRejected) => {
      onRejected(reason);
    });
  }
}

// Test code
const promise = Promise.reject(1);
promise
  .then(
    (value) => {
      console.log("res1", value);
    },
    (reason) => {
      console.log("err1", reason);
    }
  )

The results are as follows

all/allSettled

all and allSettled methods are promise class methods
1. all method
As long as one promise method executes reject, it will execute reject. When all promises return resolve, the resolve method will be executed.
2. allSettled method
When all promises are completed, the resolve method is executed to return the status and results of all promises.

static all(promise){
    return new icePromise((resolve, reject)=>{
      const values = []
      promises.forEach(promise => {
        promise.then(res => {
          values.push(res)
          if (values.length === promises.length) {
            resolve(values)
          }
        }, err => {
          reject(err)
        })
      })
    })
    })
}

static allSettled(promise){
    return new icePromise((resolve, reject)=>{
        const values = []
        promise.then(res=>{
            values.push({ status: '', value: '' })
            if(values.length === promise.length){
                resolve(values)
            }
        }, err=>{
            values.push({ status: '', value: '' })
            if(values.length === promise.length){
                resolve(values)
            }
        })
    })
}

The complete implementation is as follows

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";
const respondWithCatchError = (fn, value, resolve, reject) => {
  try {
    const result = fn(value);
    resolve(result);
  } catch (error) {
    reject(error);
  }
};
class IcePromise {
  constructor(executor) {
    this.status = STATUS_PENDING;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;
    const resolve = (value) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_FULFILLED;
          this.value = value;
          this.onResolvedCallbacks.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedCallbacks.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
    const defaultOnFulfilled = (value) => {
      return value;
    };
    const defaultOnRejected = (reason) => {
      throw reason;
    };
    onFulfilled = onFulfilled || defaultOnFulfilled;
    onRejected = onRejected || defaultOnRejected;
    return new Promise((resolve, reject) => {
      if (this.status === STATUS_FULFILLED && onFulfilled) {
        respondWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === STATUS_REJECTED && onRejected) {
        respondWithCatchError(onRejected, this.reason, resolve, reject);
      }
      if (this.status === STATUS_PENDING) {
        this.onResolvedCallbacks.push(() => {
          respondWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedCallbacks.push(() => {
          respondWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
  catch(onRejected) {
    this.then(null, onRejected);
  }
  finally(onFinally) {
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }
  static resolve(value) {
    return new Promise((onResolve) => {
      onResolve(value);
    });
  }
  static reject(reason) {
    return new Promise((onResolve, onRejected) => {
      onRejected(reason);
    });
  }
  static all(promises) {
    return new Promise((resolve, reject) => {
      const result = [];
      promises.forEach((promise) => {
        promise.then(
          (value) => {
            result.push(value);
            if (result.length === promises.length) {
              resolve(result);
            }
          },
          (reason) => {
            reject(reason);
          }
        );
      });
    });
  }
  static allSettled(promises) {
    return new Promise((resolve, reject) => {
      const result = [];
      promises.forEach((promise) => {
        promise.then(
          (value) => {
            result.push({
              status: STATUS_FULFILLED,
              value,
            });
            if (result.length === promises.length) {
              resolve(result);
            }
          },
          (reason) => {
            result.push({
              status: STATUS_REJECTED,
              reason,
            });
            if (result.length === promises.length) {
              resolve(result);
            }
          }
        );
      });
    });
  }
}

// Test code
const promise1 = IcePromise.resolve(1);
const promise2 = new IcePromise((resolve, reject) => {
  setTimeout(() => {
    reject(2);
  });
});
const promise3 = IcePromise.resolve(3);

IcePromise.all([promise1, promise2, promise3]).then(
  (value) => {
    console.log("res1", value);
  },
  (reason) => {
    console.log("err1", reason);
  }
);
IcePromise.allSettled([promise1, promise2, promise3]).then(
  (value) => {
    console.log("res2", value);
  },
  (reason) => {
    console.log("err2", reason);
  }
);

The results are as follows

race/any

race and any are both promise class methods.
1. race method
As long as a promise execution is completed, the result of its execution will be returned

2. any method
① If there is a fully filled status, wait until the fully filled execution is completed, execute resolve, and the result is value
② If all the promises are rejected, an AggregateError error will be reported after all the promises become rejected.

static race(promises){
    return new icePromise((resolve, reject)=>{
        promises.forEach(promise=>{
            promise.then(resolve, reject)        
        })
    })
}

static any(promises){
    const reasons = []
    return new icePromise((resolve, reject)=>{
        promises.forEach(promise=>{
            promise.then(resolve, err=>{
                reasons.push(err)
                if(reasons.length === promises.length){
                    reject(new AggregateError(reasons))
                }
            })
        })
    })
}

The overall implementation is as follows

const STATUS_PENDING = "pending";
const STATUS_FULFILLED = "fulfilled";
const STATUS_REJECTED = "rejected";
const respondWithCatchError = (fn, value, resolve, reject) => {
  try {
    const result = fn(value);
    resolve(result);
  } catch (error) {
    reject(error);
  }
};
class IcePromise {
  constructor(executor) {
    this.status = STATUS_PENDING;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    this.value = undefined;
    this.reason = undefined;
    const resolve = (value) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_FULFILLED;
          this.value = value;
          this.onResolvedCallbacks.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      if (this.status === STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== STATUS_PENDING) return;
          this.status = STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedCallbacks.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
    const defaultOnFulfilled = (value) => {
      return value;
    };
    const defaultOnRejected = (reason) => {
      throw reason;
    };
    onFulfilled = onFulfilled || defaultOnFulfilled;
    onRejected = onRejected || defaultOnRejected;
    return new Promise((resolve, reject) => {
      if (this.status === STATUS_FULFILLED && onFulfilled) {
        respondWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === STATUS_REJECTED && onRejected) {
        respondWithCatchError(onRejected, this.reason, resolve, reject);
      }
      if (this.status === STATUS_PENDING) {
        this.onResolvedCallbacks.push(() => {
          respondWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedCallbacks.push(() => {
          respondWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
  catch(onRejected) {
    this.then(null, onRejected);
  }
  finally(onFinally) {
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }
  static resolve(value) {
    return new Promise((onResolve) => {
      onResolve(value);
    });
  }
  static reject(reason) {
    return new Promise((onResolve, onRejected) => {
      onRejected(reason);
    });
  }
  static all(promises) {
    return new Promise((resolve, reject) => {
      const result = [];
      promises.forEach((promise) => {
        promise.then(
          (value) => {
            result.push(value);
            if (result.length === promises.length) {
              resolve(result);
            }
          },
          (reason) => {
            reject(reason);
          }
        );
      });
    });
  }
  static allSettled(promises) {
    return new Promise((resolve, reject) => {
      const result = [];
      promises.forEach((promise) => {
        promise.then(
          (value) => {
            result.push({
              status: STATUS_FULFILLED,
              value,
            });
            if (result.length === promises.length) {
              resolve(result);
            }
          },
          (reason) => {
            result.push({
              status: STATUS_REJECTED,
              reason,
            });
            if (result.length === promises.length) {
              resolve(result);
            }
          }
        );
      });
    });
  }
  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve, reject);
      });
    });
  }
  static any(promises) {
    return new Promise((resolve, reject) => {
      const reasons = [];
      promises.forEach((promise) => {
        promise.then(resolve, (reason) => {
          reasons.push(reason);
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons));
          }
        });
      });
    });
  }
}

// Test code
const promise1 = new IcePromise((resolve, reject) => {
  setTimeout(() => {
    reject(1);
  });
});
const promise2 = IcePromise.reject(2);
const promise3 = IcePromise.reject(3);
IcePromise.race([promise1, promise2, promise3]).then(
  (value) => {
    console.log("res1", value);
  },
  (reason) => {
    console.log("err1", reason);
  }
);
IcePromise.any([promise1, promise2, promise3]).then(
  (value) => {
    console.log("res1", value);
  },
  (reason) => {
    console.log("err1", reason);
  }
);

The above is all the code of custom promise. There are still many places for developers to master about js advanced. You can see other blog posts I wrote and keep updating~

Topics: Javascript Front-end ECMAScript Promise