JS中Promise的手写实现

95 阅读9分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

我们自己来手写实现一个Promise类,我们既可以参考Promise/A+(promisesaplus.com/)的规范,也可以参照ES6(ES2015)中Promise的实现。

结构的设计

首先我们要明确的是Promise是一个类,并且它的构造函数需要传入一个回调函数executor作为参数,这个executor会在调用new Promise时被立即执行,并且在执行的时候会被传入两个函数(resolve和reject)作为参数,通过在executor中调用这两个函数来改变promise的状态;

promise的状态(status)在创建出来时是pending,在调用resovle时变成fulfilled,调用reject时变成rejected,并且状态一旦确定就不可改变了;

resolve在调用的时候接收一个value,作为promise的value;

reject在调用的时候接收一个reason,作为promise的reason;

于是我们实现了这样的一个MyPromise类:

const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'

class MyPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING
    this.value = undefined
    this.reason = undefined
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.status = PROMISE_STATUS_FULFILLED
        this.value = value
      }
    }
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.status = PROMISE_STATUS_REJECTED
        this.reason = reason
      }
    }
    executor(resolve, reject)
  }
}

const promise = new MyPromise((resovle, reject) => {
  console.log('executor会被立即执行');
  resovle(1111)
  reject(2222)
})

console.log(promise.status, promise.value, promise.reason); // fulfilled 1111 undefined

then方法设计

Promise存在一个then的实例方法,该方法会接收两个回调函数onFulfilled和onRejected作为参数,并且在Promise对象的状态确定之后调用;

onFulfilled会在Promise对象的状态变成fulfilled之后被调用,并传入Promise对象的value作为参数;

onRejected会在Promise对象的状态变成rejected之后被调用,并传入Promise对象的reason作为参数;

根据Promise/A+规范,onRejected和onFulfilled必须确保异步执行

const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'
class MyPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING
    this.value = undefined
    this.reason = undefined
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.status = PROMISE_STATUS_FULFILLED
        this.value = value
        queueMicrotask(() => {
          this.onFulfilled(value)
        })
      }
    }
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.status = PROMISE_STATUS_REJECTED
        this.reason = reason
        queueMicrotask(() => {
          this.onRejected(reason)
        })
      }
    }
    executor(resolve, reject)
  }
  then(onFulfilled, onRejected) {
    this.onFulfilled = onFulfilled
    this.onRejected = onRejected
  }
}

const promise = new MyPromise((resolve, reject) => {
  resolve(111)
  reject(222)
})
// onFulfilled和onRejected异步执行:实现方式可以通过宏任务或微任务
promise.then((value) => {
  console.log('value:', value);
}, (reason) => {
  console.log('reason:', reason);
})

then方法的优化一

上面我们虽然借助微任务实现了then方法,但还有一些情况我们没考虑进去:

  1. then方法需要能够被多次调用;

按照我们上面的实现,then方法被调用多次时,之后最后的那个then方法传入的回调函数会被执行;

  1. 如果在then方法调用的时候,promise的状态已经确定了;

在我们的代码实现中,通过将promise对象的状态改变和对应回调函数的执行加入到微任务队列中,使得我们能够在promise对象状态改变的时候,执行then方法中传入的回调函数,当我们的then方法调用都放到同步代码中时,这样做是没问题的;

但是,如果我们在setTimeout中传入的回调函数中执行then方法,此时代码的运行结果就会出乎意料了;由于setTimeout传入的回调函数会被加入到宏任务中执行,而对于then方法传入的回调函数的执行是在微任务中,此时我们会发现setTimeout中的回调函数中的then方法传入的回调函数并不会被执行;

const promise = new MyPromise((resolve, reject) => {
  resolve(1111)
})
setTimeout(() => {
  // 此处then方法传入的回调函数并不会被执行
  promise.then(value => {
    console.log(value)
  })
})

因此我们需要对then方法进行优化来解决这些问题:

  1. 采用数组结构来保存then方法传入的回调函数,并在promise对象状态确定之后遍历执行;
  2. 如果在执行then方法时,promise对象的状态已经确定了,就立即执行对应的回调函数;
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class MyPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        this.status = PROMISE_STATUS_FULFILLED;
        queueMicrotask(() => {
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        this.status = PROMISE_STATUS_REJECTED;
        queueMicrotask(() => {
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
    if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
      onFulfilled(this.value);
    }
    if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
      onRejected(this.reason);
    }
    if (this.status === PROMISE_STATUS_PENDING) {
      if (onFulfilled) {
        this.onFulfilledFns.push(onFulfilled);
      }
      if (onRejected) {
        this.onRejectedFns.push(onRejected);
      }
    }
  }
}

then方法的设计优化二

在Promises/A+规范中,还规定了then方法的返回值必须是一个promise对象:

  • 如果then方法中传入的onFulfilled或onRejected方法正常执行,那么then方法将返回一个fulfilled的promise对象,并且将then方法的回调函数返回值作为value;
  • 如果then方法传入的回调函数在执行的时候抛出异常,那么then方法将返回一个rejected的promise对象,并且将异常作为reason;
  • 如果then方法没有对应状态的回调函数,那么then方法将返回一个相同状态的promise对象,并且使用原promise的value或reason;

因此,对我们上面的代码再进行优化:

const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class MyPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        this.status = PROMISE_STATUS_FULFILLED;
        queueMicrotask(() => {
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        this.status = PROMISE_STATUS_REJECTED;
        queueMicrotask(() => {
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
         try {
           const result = onFulfilled(this.value);
           resolve(result);
         } catch(e) {
           reject(e)
         }
    }
    if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
       try {
         const result = onRejected(this.reason);
         resolve(result)
       } catch (e) {
         reject(e)
       }
    }
    if (this.status === PROMISE_STATUS_PENDING) {
      if (onFulfilled) {
        this.onFulfilledFns.push((value) => {
           try {
             const result = onFulfilled(value)
             resolve(result)
           } catch (e) {
             reject(e)
           }
        });
      }
      if (onRejected) {
        this.onRejectedFns.push((reason) => {
          execFnWithCatchErr(onRejected, reason, resolve, reject)
           try {
             const result = onRejected(reason)
             resolve(result)
           } catch (e) {
             reject(e)
           }
        });
      }
    }
    })
    
  }
}

观察上面的代码,发现:

try {
  const result = onRejected(reason)
  resolve(result)
} catch (e) {
	reject(e)
}

这样的代码总是存在,因为我们可以将它抽离成一个函数:

function execFnWithCatchErr(fn, arg, resolve, reject) {
  try {
    const result = fn(arg)
    resolve(result)
  } catch (e) {
    reject(e)
  }
}

从而来简化then方法中的代码:

const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
function execFnWithCatchErr(fn, arg, resolve, reject) {
  try {
    const result = fn(arg)
    resolve(result)
  } catch (e) {
    reject(e)
  }
}
class MyPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        this.status = PROMISE_STATUS_FULFILLED;
        queueMicrotask(() => {
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        this.status = PROMISE_STATUS_REJECTED;
        queueMicrotask(() => {
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFnWithCatchErr(onFulfilled, this.value, resolve, reject)
    }
    if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
      execFnWithCatchErr(onRejected, this.reason, resolve, reject)
    }
    if (this.status === PROMISE_STATUS_PENDING) {
      if (onFulfilled) {
        this.onFulfilledFns.push((value) => {
          execFnWithCatchErr(onFulfilled, value, resolve, reject)
        });
      }
      if (onRejected) {
        this.onRejectedFns.push((reason) => {
          execFnWithCatchErr(onRejected, reason, resolve, reject)
        });
      }
    }
    })
    
  }
}

catch方法的实现

上面我们已经基本实现了一个Promises/A+规范的MyPromise类了,下面我们来对ES6中Promise的一些方法进行实现,它们并不在Promises/A+规范中,只是ES6为了方便我们操作做了一些补充和拓展。

我们先来分析一下catch方法的作用:

  • catch方法会对一个promise对象的rejected状态进行捕获;
  • catch方法同样会返回一个新的promise对象;
  • 当前一个promise对象的then方法在调用时没有传入对应的onRejected回调函数时,且这个promise对象的最终状态是rejected时,可以在then方法之后链式调用catch方法来对这个rejected状态进行处理;

对应的代码实现:

const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
function execFnWithCatchErr(fn, arg, resolve, reject) {
  try {
    const result = fn(arg);
    resolve(result);
  } catch (e) {
    reject(e);
  }
}
class MyPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        this.status = PROMISE_STATUS_FULFILLED;
        queueMicrotask(() => {
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        this.status = PROMISE_STATUS_REJECTED;
        queueMicrotask(() => {
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
       // 当调用then方法时没有传入onRejected回调函数时,
      // 如果这个promise最终状态为rejected,我们可以将onRejected设置为一个抛出异常的函数
      // 这样它会在执行的时候被异常捕获,返回一个rejected的promise对象
      if (!onRejected) {
        onRejected = (reason) => {throw reason}
      }
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFnWithCatchErr(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFnWithCatchErr(onRejected, this.reason, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_PENDING) {
        if (onFulfilled) {
          this.onFulfilledFns.push((value) => {
            execFnWithCatchErr(onFulfilled, value, resolve, reject);
          });
        }
        if (onRejected) {
          this.onRejectedFns.push((reason) => {
            execFnWithCatchErr(onRejected, reason, resolve, reject);
          });
        }
      }
    });
  }
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
}
const promise = new MyPromise((resolve, reject) => {
  reject('err1')
})

promise.catch((reason) => {
  console.log(reason);
})

promise.then(value => {
  console.log(value);
}).catch(reason => {
  console.log(reason);
})
// 运行结果
// err1
// err1

finally方法的实现

finally方法的作用是当一个promise对象确定最终状态之后,这个方法总是会被调用,并且finally方法中传入的回调函数不会被传入任何值(value或者reason);

const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
function execFnWithCatchErr(fn, arg, resolve, reject) {
  try {
    const result = fn(arg);
    resolve(result);
  } catch (e) {
    reject(e);
  }
}
class MyPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.value = value;
        this.status = PROMISE_STATUS_FULFILLED;
        queueMicrotask(() => {
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.reason = reason;
        this.status = PROMISE_STATUS_REJECTED;
        queueMicrotask(() => {
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
    executor(resolve, reject);
  }
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      // 当调用then方法时没有传入onRejected回调函数时,
      // 如果这个promise最终状态为rejected,我们可以将onRejected设置为一个抛出异常的函数
      // 这样它会在执行的时候被异常捕获,返回一个rejected的promise对象
      if (!onRejected) {
        onRejected = (reason) => {throw reason}
      }
      // 当调用then方法时没有传入onFulfilled回调函数时,
      // 如果这个promise最终状态为fulfilled时,我们可以将onFulfilled设置成一个返回参数值的函数
      if (!onFulfilled) {
        onFulfilled = (value) => value
      }
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFnWithCatchErr(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFnWithCatchErr(onRejected, this.reason, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_PENDING) {
        if (onFulfilled) {
          this.onFulfilledFns.push((value) => {
            execFnWithCatchErr(onFulfilled, value, resolve, reject);
          });
        }
        if (onRejected) {
          this.onRejectedFns.push((reason) => {
            execFnWithCatchErr(onRejected, reason, resolve, reject);
          });
        }
      }
    });
  }
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
  finally(onFinally) {
    if (onFinally) {
      this.then(() => {onFinally()}, () => {onFinally()})
    }
  }
}

const promise = new MyPromise((resolve, reject) => {
  // reject(111)
  resolve(111)
})

promise.then(value => {
  console.log(value);
}).catch(reason => {
  console.log(reason);
}).finally(() => {
  console.log('总是会被执行');
})

Promise.resolve/reject方法的实现

这是两个Promise类上的静态方法:

  • Promise.resolve方法的作用是将输入值转成一个状态为fulfilled的promise对象;
  • Promise.reject方法的作用是将输入值转成一个状态为rejected的promise对象;
class MyPromise {
  static resolve(value) {
    return new MyPromise((resolve) => {
      resolve(value)
    })
  }
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }
 ...
}

MyPromise.resolve(111).then(value => {
  console.log(value);
})

MyPromise.reject(222).catch(reason => {
  console.log(reason);
})

Promise.all/allSettled方法的实现

  • all方法会接收一个数组,这个数组中的promise状态会决定all方法返回的promise状态,当数组中所有的promise状态为fulfilled时,返回promise状态为fulfilled,并且使用数组中所有promise的value组成一个数组作为新promise的value;当数组中存在一个promise状态为rejected时,返回的promise状态也会rejected,并且使用这个promise的reason作为新promise的reason;
  • all方法与all方法的区别在于它返回的promise对象状态始终为fulfilled,并且它的value会保存数组中每个promise的最终状态和值;
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class MyPromise {
  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const values = [];
      promises.forEach((promise) => {
        promise.then(
          (value) => {
            values.push(value);
            if (values.length === promises.length) {
              resolve(values);
            }
          },
          (reason) => {
            reject(reason);
          }
        );
      });
    });
  }
  static allSettled(promises) {
    return new MyPromise((resolve) => {
      const values = []
      promises.forEach(promise => {
        promise.then(value => {
          values.push({
            status: PROMISE_STATUS_FULFILLED,
            value
          })
          if (values.length === promises.length) resolve(values)
        }, reason => {
          values.push({
            status: PROMISE_STATUS_REJECTED,
            reason
          })
          if (values.length === promises.length) resolve(values)
        })
      })
    })
  }
  ...
}
const promise1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    // resolve(111);
    reject(111)
  }, 1000);
});

const promise2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(222);
  }, 2000);
});

const promise3 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(333)
  }, 3000);
});


MyPromise.all([promise1, promise2, promise3]).then(value => {
  console.log(value);
}, reason => {
  console.log(reason);
})

MyPromise.allSettled([promise1, promise2, promise3]).then(value => {
  console.log(value);
})
// 运行结果:
// 111
// [
//   { status: 'rejected', reason: 111 },
//   { status: 'fulfilled', value: 222 },
//   { status: 'fulfilled', value: 333 }
// ]

Promise.race/any方法的实现

  • race方法会接收一个promise数组,并采用状态确定的第一个promise对象作为结果返回;
  • any方法类似race方法,只是它会采用第一个状态为fulfilled的promise对象作为结果返回,如果数组中的promise对象最终状态都为rejected,则会抛出异常AggregateError;
class MyPromise {
  static race(promises) {
    return new MyPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(value => {
          resolve(value)
        }, reason => {
          reject(reason)
        })
      })
    })
  }
  static any(promises) {
    return new MyPromise((resolve, reject) => {
      const reasons = []
      promises.forEach(promise => {
        promise.then(value => {
          resolve(value)
        }, reason => {
          reasons.push(reason)
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons))
          }
        })
      })
    })
  }
  ...
}