自定义实现JavaScript中的Promise

Posted 孙群

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义实现JavaScript中的Promise相关的知识,希望对你有一定的参考价值。

目录

Promisejavascript ES6规范中的一个重要对象,可以方便地实现各种异步操作。

浏览器兼容性

以下是各种浏览器对Promise的支持情况:

对于不支持Promise的浏览器我们可以自定义Promise实现,将其作为原生Promise的Polyfill。

自定义Promise实现

以下是自定义实现的Promise:

/**
 * 自定义实现Promise
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
 *
 * 核心:
 * promise的resolve()或reject()执行,表示status状态变化,并将结果作为入参触发watcher的onFullfill()或onReject()方法
 *
 * 逻辑:
 * 1. promise的状态是外部通过调用executor中的resolve()和reject()方法实现的
 * 2. 当调用resolve()的时候,promise的状态从pending变成fullfilled,并执行通过.then()监听fullfilled状态的回调,且resovle()传入的结果会作为onFullfill()回调的入参
 * 3. 当调用reject()的时候,promise的状态从pending变成rejected,并执行通过.then()监听rejected状态的回调,且reject()传入的错误会作为onReject()回调的入参
 *
 * case分析:
 * p1.then(p1OnFullfilled, p1OnReject) => p2
 * 1. p1OnFullfilled执行
 *  1.1 该方法的返回值是Promise,该Promise是一个新的Promise p3,不是p2,当这个新的Promise执行resovle()时,其resolve()传入的结果会作为p2的onFullfill()的回调入参
 *  1.2 该方法的返回值不是Promise,该值作为入参触发p2._resolve()执行
 * 2. p1OnReject执行
 *  2.1 该方法内部通过throw抛出异常,该异常作为入参触发执行p2._reject()执行
 *  2.2 该方法内部没有抛出异常,则执行p2的onFullfill()回调,且返回值会作为p2的onFullfill()的回调参数
 *
 * @param executor function,Required,其签名格式为function(resolve, reject)。
 * executor是带有 resolve 和 reject 两个参数的函数。
 * Promise构造函数执行时立即调用executor 函数,resolve 和 reject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回所建promise实例对象前被调用)。
 * resolve 和 reject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。
 * executor 内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject 函数将promise的状态改为rejected。
 * 如果在executor函数中抛出一个错误,那么该promise 状态为rejected。executor函数的返回值被忽略。
 */
function MyPromise(executor) 
  this._status = 0; // 0表示pending,1表示fullfilled,-1表示rejected
  this._result = undefined; // resolve()执行的入参
  this._err = null; // reject()执行的入参

  // watcher是通过then来观察结果的Promise,由于可以通过多次调用p1.then()来得到多个新的Promise,所以此处是watcher数组
  this._watchers = [];


  /**
   * 封装传递给executor()的resolve()方法
   * 逻辑:
   * 1. 更改当前promise的status,并记录result
   * 2. 通知watchers结果
   *
   * @parma result Any,Optional,Promise的resovle的结果值
   */
  this._resolve = (result) => 
    // Promise已经resolved或rejected,不能再次resolve
    if (this._status !== 0) 
      return;
    

    this._status = 1;
    this._result = result;

    for (const watcher of this._watchers) 
      this._notifyWatcher(watcher);
    
  ;


  /**
   * 封装传递给executor()的reject()方法
   * 逻辑:
   * 1. 更改当前promise的status,并记录error
   * 2. 通知watchers错误
   *
   * @param err Any,Optional,Promise的reject的原因
   */
  this._reject = (err) => 
    // Promise已经resolved或rejected,不能再次reject
    if (this._status !== 0) 
      return;
    

    this._status = -1;
    this._err = err;

    for (const watcher of this._watchers) 
      this._notifyWatcher(watcher);
    
  ;

  try 
    executor.call(this, this._resolve, this._reject);
   catch (e) 
    this._reject(e);
  


/**
 * 内部私有方法,用于向当前promise中添加一个watcher以监听当点promise的resolve()或reject()的结果
 *
 * @param watcher MyPromise,Required,监听当前promise的resolve()或reject()结果的观察者Promise对象
 */
MyPromise.prototype._addWatcher = function(watcher) 
  if (this._watchers.indexOf(watcher) < 0) 
    this._watchers.push(watcher);
  

  if (this._status !== 0) 
    // 已经resolved或rejected
    this._notifyWatcher(watcher);
  
;

/**
 * 内部私有方法,用于将当前promise的resolve()或reject()的结果通知观察者
 *
 * @param watcher MyPromise,Required,监听当前promise的resolve()或reject()结果的观察者Promise对象
 */
MyPromise.prototype._notifyWatcher = function(watcher) 
  if (this._watchers.includes(watcher)) 
    if (this._status > 0) 
      // 已经fullfilled
      setTimeout(() => 
        this._removeWatcher(watcher);
        watcher._onPrentResolve(this._result);
      , 0);
     else if (this._status < 0) 
      // 已经rejected
      setTimeout(() => 
        this._removeWatcher(watcher);
        watcher._onParentReject(this._err);
      , 0);
    
  
;

/**
 * 内部私有方法,用于将监听当前promise的resolve()或reject()的观察者从观察者列表中删除
 *
 * @param watcher MyPromise,Required,监听当前promise的resolve()或reject()结果的观察者Promise对象
 */
MyPromise.prototype._removeWatcher = function(watcher) 
  const index = this._watchers.indexOf(watcher);
  if (index >= 0) 
    this._watchers.splice(index, 1);
  
;

/**
 * 功能:
 * then() 方法返回一个 Promise。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
 *
 * 逻辑:
 * promise1.then()方法需要返回一个新的Promise对象promise2
 *
 * @param onPromise1Resolve function,Optional,当 Promise 变成接受状态(fulfilled)时调用的函数。
 * 该函数有一个参数,即接受的最终结果(the fulfillment  value)。
 * 如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数。
 *
 * @param onPromise1Reject function,Optional,当 Promise 变成拒绝状态(rejected)时调用的函数。
 * 该函数有一个参数,即拒绝的原因(rejection reason)。
 * 如果该参数不是函数,则会在内部被替换为一个 "Thrower" 函数 (it throws an error it received as argument)。
 *
 * @return MyPromise,返回新的Promise对象,具体的返回值依据以下规则返回。如果 then 中的回调函数:
 * 返回了一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
 * 没有返回任何值,那么 then 返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined。
 * 抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
 * 返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
 * 返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
 * 返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
 */
MyPromise.prototype.then = function(onPromise1Resolve, onPromise1Reject) 
  const promise1 = this;

  const promise2 = new MyPromise(function(resolve, reject) 
    // 此处的this执行新的Promise p
    this._onPrentResolve = function(promise1Result) 
      let value = undefined;

      // 如果未设置onPromise1Resolve,则直接将promise2进行resolve(promise1Result)
      if (!onPromise1Resolve) 
        resolve(promise1Result);
        return;
      

      try 
        // 执行promise1的resovle之后的回调方法onPromise1Resolve()
        value = onPromise1Resolve(promise1Result);
       catch (e) 
        // 如果onPromise1Resolve()方法执行异常,则将对promise2执行reject()
        reject(e);
        return;
      

      if (value instanceof MyPromise) 
        // 返回了一个新的Promise对象promise3
        const promise3 = value;

        // promise2需要等待新的Promise执行完成才能resolve或reject
        // 首先覆写promise2._onPrentResolve和promise2._onParentReject方法以便接受promise3执行resolve、reject时的数据
        this._onPrentResolve = function(promise3Result) 
          resolve(promise3Result);
        ;

        this._onParentReject = function(promise3Err) 
          reject(promise3Err);
        ;

        // 使得promise2观察promise3的状态
        promise3._addWatcher(this); // this是promise2
       else 
        resolve(value);
      
    ;

    this._onParentReject = function(promise1Err) 
      let value = undefined;

      // 如果未设置onPromise1Reject,则直接将promise2进行reject(promise1Err)
      if (!onPromise1Reject) 
        reject(promise1Err);
        return;
      

      try 
        // 执行promise1的reject之后的回调方法onPromise1Reject()
        value = onPromise1Reject(promise1Err);
       catch(e) 
        // 如果onPromise1Reject()方法执行异常,则将对promise2执行reject()
        reject(e);
        return;
      

      if (value instanceof MyPromise) 
        // 返回了一个新的Promise对象promise3
        const promise3 = value;

        // promise2需要等待新的Promise执行完成才能resolve或reject
        // 首先覆写promise2._onPrentResolve和promise2._onParentReject方法以便接受promise3执行resolve、reject时的数据
        this._onPrentResolve = function(promise3Result) 
          resolve(promise3Result);
        ;

        this._onParentReject = function(promise3Err) 
          reject(promise3Err);
        ;

        // 使得promise2观察promise3的状态
        promise3._addWatcher(this); // this是promise2
       else 
        resolve(value);
      
    ;

	// 使得promise2观察promise1的状态
    promise1._addWatcher(this); // this是promise2
  );

  return promise2;
;

/**
 * 功能:
 * catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。
 * (事实上, obj.catch(onRejected) 内部实现就是调用 obj.then(undefined, onRejected))
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
 *
 * 逻辑:
 * 1. 对于p1.then(onResolve, onReject),onResolve中出现异常时,onReject不会捕获
 * 2. 对于p1.then(onResolve).catch(onCatch),onResolve中出现异常时,onCatch会捕获
 * 3. 对于p1.then(onResolve, onReject).catch(onCatch),onResolve中出现异常时,onReject不会捕获,onCatch会捕获
 * 4. 对于p1.then(onResolve, onReject).catch(onCatch),onReject中出现异常时,onCatch会捕获
 *
 * @return MyPromise,返回新的Promise对象
 */
MyPromise.prototype.catch = function(onCatch) 
  return this.then(null, onCatch);
;

/**
 * 功能:
 * finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。
 * 这为在Promise是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在then()和catch()中各写一次的情况。
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
 *
 * 逻辑:
 * finally() 虽然与 .then(onFinally, onFinally) 类似,它们不同的是:
 * 调用内联函数时,不需要多次声明该函数或为该函数创建一个变量保存它。
 * 由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。
 * 与Promise.resolve(2).then(() => , () => ) (resolved的结果为undefined)不同,Promise.resolve(2).finally(() => ) resolved的结果为 2。
 * 同样,Promise.reject(3).then(() => , () => ) (resolved 的结果为undefined), Promise.reject(3).finally(() => ) rejected 的结果为 3。
 *
 * @return MyPromise,返回新的Promise对象
 */
MyPromise.prototype.finally = function(onFinally) 
  const onParentResolve = function(result) 
    // 忽略onFinally()的执行结果
    onFinally();

    // 将之前promise的结果透传
    return result;
  ;

  const onParentReject = function(err) 
    // 忽略onFinally()的执行结果
    onFinally();

    // 将之前promise的错误透传
    throw err;
  ;

  return this.then(onParentResolve, onParentReject);
;

/**
 * 功能:
 * Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。
 * 如果这个值是一个 promise,那么将返回这个promise;
 * 如果这个值是thenable(即带有"then" 方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
 * 否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
 *
 * @param value Any,Optional,将被Promise对象解析的参数,也可以是一个Promise对象,或者是一个thenable。
 *
 * @return MyPromise,返回一个带着给定值解析过的Promise对象,如果参数本身就是一个Promise对象,则直接返回这个Promise对象。
 */
MyPromise.resolve = function(value) 
  if (value instanceof MyPromise) 
    return value;
   else 
    return new MyPromise(function(resolve) 
      resolve(value);
    );
  
;

/**
 * Promise.reject()方法返回一个带有拒绝原因的Promise对象。
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
 *
 * @param reason Any,Optional,表示Promise被拒绝的原因。
 *
 * @retrun MyPromise,返回一个给定原因了的被拒绝的Promise。
 */
MyPromise.reject = function(reason) 
  return new MyPromise(function(resolve, reject) 
    reject(reason);
  );
;

/**
 * Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);
 * 如果参数中  promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
 *
 * @param iterable Iterable,Required,一个可迭代对象,如Array。
 *
 * @return MyPromise,返回一个Promise对象。
 * 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise。
 * 如果传入的参数不包含任何 promise,则返回一个异步完成(asynchronously resolved) Promise。注意:Google Chrome 58 在这种情况下返回一个已完成(already resolved)状态的 Promise。
 * 其它情况下返回一个处理中(pending)的Promise。这个返回的 promise 之后会在所有的 promise 都完成或有一个 promise 失败时异步地变为完成或失败。 返回值将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定。
 */
MyPromise.all = function(iterable) 
  return new MyPromise(function(resolve, reject) 
    const that = this;

    if (iterable.length === 0) 
      // 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise。
      resolve([]);
     else 

      const promises = iterable.map(item => 
        if (item instanceof MyPromise) 
          return item;
         else 
          // 将非MyPromise实例的值封装成resolved的Promise
          return MyPromise.resolve(item);
        
      );

      const allCount = promises.length;

      const resultsObj = ; // index: value

      function handleResolve(index, result) 
        // 如果promise已经resolved或rejected,则不再继续后续逻辑
        if (that._status !== 0) 
          return;
        

        resultsObj[index] = result;

        const keys = Object.keys(resultsObj);

        if (keys.length === allCount) 
          // 所有promises都已经处理完成
          const results = [];

          keys.forEach(key => 
            const value = resultsObj[key];
            const index = parseInt(key);
            results[index] = value;
          );

          resolve(results);
        
      

      function handleReject(err) 
        // 如果promise已经resolved或rejected,则不再继续后续逻辑
        if (that._status !== 0) 
          return;
        

        reject(err);
      

      promises.forEach((promise, index) => 
        promise.then(function(result) 
          handleResolve(index, result);
        , function(err) 
          handleReject(err);
        );
      );
    
  );
;

/**
 * Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
 *
 * 逻辑:
 * race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
 * 如果迭代iterable是空的,则返回的 promise 将永远等待。
 * 如果迭代iterable中包含非Promise值、已经resolved的Promise对象、已经rejected的Promise对象, 则 Promise.race() 将解析为迭代iterable中找到的第一个值。
 *
 * @param iterable Iterable,Required,一个可迭代对象,如Array。
 *
 * @return MyPromise,返回一个Promise对象。只要给定的迭代iterable中的一个promise解决或拒绝,就采用第一个promise的值作为它的值,从而异步地解析或拒绝(一旦堆栈为空)。
 *
 */
MyPromise.race = function(iterable) 
  return new MyPromise(function(resolve, reject) 
    // 如果迭代iterable是空的,则返回的 promise 将永远等待。
    if (iterable.length === 0) 
      return;
    

    iterable.map(item => 
      if (item instanceof MyPromise) 
        item.then((result) => 
          if (this._status === 0) 
            resolve(result);
          
        , (err) => 
          if (this._status === 0) 
            reject(err)
          
        );
       else 
      	if (this._status === 0) 
      	  resolve(item);
      	
      
    );
  );
;

通过以下方式将其作为Promise Polyfill使用:

  <script type="text/javascript" src="promise-polyfill.js"></script>
  <script type="text/javascript">
    if (!window.Promise) 
      window.Promise = MyPromise;
    
  </script>

参考

MDN - 使用 Promise
MDN - Promise

以上是关于自定义实现JavaScript中的Promise的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段12——JavaScript的Promise对象

VSCode自定义代码片段12——JavaScript的Promise对象

Javascript中的Promise

自定义Promise实现

实现一个自定义Promise

实现一个自定义Promise