深入了解Promise机制并使用JS实现一个Promise(二)

170 阅读8分钟

接着上一篇深入了解Promise机制并使用JS实现一个Promise(一)文章接着使用js来实现一个xpromise

已实现效果

经过上一篇文章我们已近得到下面的代码,实现了promise的状态、值、以及then方法的实现。下面我们接着来实现promise中其他的方法,目前已存在的代码如下:

class XPromise {
  constructor(handle) {
    // 用来记录当前XPromise的状态 pending | fulfilled | rejected
    this["[[PromiseState]]"] = "pending";
    // 用来记录当前XPromise的值
    this["[[PromiseResult]]"] = undefined;
    this.resolveFnList = [];
    this.rejectFnList = [];
    // 使用否箭头函数规避this问题, 否则就得这里bind一下
    // handle(this.#resolve.bind(this), this.#reject.bind(this))
    handle(this.#resolve, this.#reject);
  }

  // 对标promise中的resolve
  #resolve = (val) => {
    this["[[PromiseState]]"] = "fulfilled";
    this["[[PromiseResult]]"] = val;
    // 将his.resolveFn(val)的时机放入微任务中执行
    this.#observerAndExecuteFn(this.resolveFnList, val)
  };

  // 对标promise中的reject
  #reject = (error) => {
    this["[[PromiseState]]"] = "rejected";
    this["[[PromiseResult]]"] = error;
    // 将执行this.rejectFn(val)的时机放入微任务中执行
    this.#observerAndExecuteFn(this.rejectFnList, error)
  };

  // 将.then函数接受的onResolve和onReject函数放在微任务中执行
  #observerAndExecuteFn = (fnList, promiseResult) => {
    const executeFnList = () => {
      let itemFn = null;
      while (fnList.length) {
        itemFn = fnList.shift();
        itemFn(promiseResult);
      }
    };
    const targetNode = document.getElementsByTagName("body")[0];
    const observe = new MutationObserver(executeFnList);
    observe.observe(targetNode, {
      attributes: true,
    });
    targetNode.setAttribute("x-promise", "watch");
  };

  /**
   * xpromise实例方法
   * 1. 在promise resolve/reject之后执行onResolved/onReject
   * 2. 返回一个新的xpromise实例(新的实例而不是原来的xpromise实例)
   * @param {*} onResolved 
   * @param {*} onReject 
   * @returns 
   */

  then = (onResolved, onReject) => {
    // 返回一个新的xpromise实例
    return new XPromise((resolve, reject) => {
      // 在这里需要把当前这个promise的结果拿到
      // resolveFn是需要push到函数队列中的,接受当前promise实例结果的值
      const resolveFn = (val) => {
        const result = onResolved && onResolved(val);
        if (result instanceof XPromise) {
          // result 既然是xpromise实例
          // 那这个xpromise的结果就可以从.then中获取
          result.then((val) => resolve(val));
        } else {
          resolve(result);
        }
      };
      this.resolveFnList.push(resolveFn);
      const rejectFn = (error) => {
        onReject && onReject(error);
        reject(error);
      };
      this.rejectFnList.push(rejectFn);
    });
  };
}

export default XPromise;

Promise实例catch方法

定义

Promise 实例的 catch() 方法用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 的方法。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。 我们看下原生promise.catch的效果 在这里插入图片描述

实现

可以看到,一个是返回新的promise,另一个是可以catch住之前的onReject。而.catch实际上是promise.then(null, onReject)的一个简写,而我们在之前已经完整实现了promise.then方法,所以只需要直接return this.then(null, onReject)即可。完整代码如下:


  // promise.then(null, onReject)的简写
  catch = (onReject) => {
    return this.then(undefined, onReject)
  }
}

export default XPromise;

运行的效果如下所示: 在这里插入图片描述

Promise实例finnal方法

定义

finally() 方法返回一个 Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在 then() 和 catch() 中各写一次的情况。 在这里插入图片描述 可以看到finally有如下几个特征

  1. 接受一个函数作为参数,但该函数没有参数
  2. 没有返回
  3. 下一个promise.then对象接受的是finally的上一个then的返回

实现

/**
   * 本质上就是个高阶函数
   * 是对this.then函数的封装,接受一个fn,
   * 执行fn之后就走正常的then逻辑即可
   * 可以理解为对this.then的升阶接受一个函数参数
   * 高阶函数,类似HOC执行this.then之前执行fn
   * @param {*} fn 
   * @returns 
   */
  finally = (fn) => {
  	// fn && fn(); fn 函数不能放在这里运行,不然就是同步了,应该放在下面,在resolve之后执行fn
    // finally 函数就是对 this.then 函数包装了一下
    // 本质还是执行this.then函数,不过在执行原本this.then函数中的onResolved和onReject之前先执行fn
    return this.then(
      // 这个val就是上一个then的值
      val => {
        // 执行传递进来的fn
        fn && fn();
        if (val instanceof XPromise) {
          // 上一个then返回的是promise 通过then获取上一个promise的值,再resolve给下一个then即可
          return val.then(v => XPromise.resolve(v))
        } else {
          return XPromise.resolve(val)
        }
      },
      // 这个error就是上一个then的值
      error => {
        fn && fn();
        if (error instanceof XPromise) {
          return error.catch(v => XPromise.reject(v))
        } else {
          return XPromise.reject(error)
        }
      }
    )
  }

执行效果如下: 在这里插入图片描述

Promise.resolve 静态方法

定义

Promise.resolve() 静态方法将给定的值转换为一个 Promise。如果该值本身就是一个 Promise,那么该 Promise 将被返回; 在这里插入图片描述

实现

我们分析,首先Promise.resolve()函数接受一个值根据该值的类型做返回即可:

  1. 非promise:返回一个新的promise实例
  2. promise:直接返回改promise即可 完整代码如下:

  /**
   * 根据参数的类别判断
   * 非promise:返回一个新的`promise`实例
   * promise:直接返回改promise即可
   * @param {*} val 
   * @returns 
   */
  static resolve = (val) => {
    if (val instanceof XPromise) {
      return val
    } else {
      return new XPromise((resolve) => {
        resolve(val)
      })
    }
  }

代码执行效果如下: 在这里插入图片描述

Promise.reject静态方法

定义

Promise.reject() 静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数。 在这里插入图片描述

实现

Promise.rejectPromise.resolve整体很像这里需要注意的是,根据MDN上的描述,我们知道返回的一定是一个新的promise对象,所以就不用对入参类型进行判断了 在这里插入图片描述 整体代码如下:

 static reject = (error) => {
    return new XPromise((undefined, reject) => {
      reject(error)
    })
  }

代码执行效果如下所示: 在这里插入图片描述

Promise.race静态方法

定义

Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。我们看原生的Promise.all的效果如下: 在这里插入图片描述

实现

思路是接受一个promise数组,数组中的每个promise并发执行,谁最先得到结果,将结果返回resolve/reject掉即可

 static race = (promiseList) => {
    let execute = false
    return new XPromise((resolve, reject) => {
      promiseList.forEach(promiseItem => {
        // 每个promiseItem都是xpromise 那就可以通过.then获取值
        promiseItem.then(val => {
          if (execute) return;
          resolve(val)
          execute = true
        }, error => {
          if (execute) return;
          reject(error)
          execute = true
        })
      });
    })
  }

代码执行效果如下: 在这里插入图片描述 在这里插入图片描述

Promise.all静态方法

定义

Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。 在这里插入图片描述

实现

 static all = (promiseList) => {
    return new XPromise((resolve, reject) => {
      // 1. 用于存储每个promise的结果
      // 2. 返回的promise结果顺序与接入的promise顺序相同故使用new Array(promiseList.length);的方式存结果
      const result = new Array(promiseList.length);
      // 用于计数运行了多个个promise
      let num = 0
      promiseList.forEach((promiseItem, index) => {
        // 每一个结果都是一个对象
        result[index] = {}
        promiseItem.then(val => {
          // 更新该位置的promise信息
          result[index]['status'] = "fulfilled"
          result[index]['value'] = val
          // 如果都运行完了就直接resolve
          // 证明所有promise都是成功的, 整个promise状态也是成功的
          num ++
          if (num >= promiseList.length) resolve(result)
        }, error => {
            // 在promise数组中有某个promise失败了
            // 整个promise状态算失败,reject失败最快的那个即可
            reject(error)
        })
      })
    })
  }

代码执行效果如下: 在这里插入图片描述

Promise.allSettled静态方法

定义

Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组 在这里插入图片描述

实现

代码如下:

  static allSettled = (promiseList) => {
    return new XPromise((resolve, reject) => {
      // 1. 用于存储每个promise的结果
      // 2. 返回的promise结果顺序与接入的promise顺序相同故使用new Array(promiseList.length);的方式存结果
      const result = new Array(promiseList.length);
      // 用于计数运行了多个个promise
      let num = 0
      promiseList.forEach((promiseItem, index) => {
        // 每一个结果都是一个对象
        result[index] = {};
        promiseItem.then((val)=> {
          // 更新该位置的promise信息
          result[index]['status'] = "fulfilled"
          result[index]['value'] = val
          // 如果都运行完了就直接resolve
          num ++
          if (num >= promiseList.length) resolve(result)
        }, error => {
          // 更新该位置的promise信息
          result[index]['status'] = "rejected"
          result[index]['reason'] = error
          // 如果都运行完了就直接resolve
          num ++
          if (num >= promiseList.length) resolve(result)
        })
      })
    })
  }

代码执行效果如下: 在这里插入图片描述

某种意义上Promise.allSettled方法也是弥补了Promise.all方法一旦有某一个promise被拒绝了就拿不到整个结果的缺陷

Promise.any静态方法

定义

Promise.any() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。

在这里插入图片描述

实现

static any = (promiseList) => {
    return new XPromise((resolve, reject) => {
      // 1. 用于存储每个promise的结果
      // 2. 返回的快的promise结果先push入数组
      const result = []
      // 用于计数运行了多个个promise
      let num = 0
      // 等每个promise都处理完了,根据每个promise结果决定整个promise的结果
      const dealResult = () => {
        // 看能否找到一个成功的promise,先找到的也就是返回最快的promise
        const successPromise = result.find(resultItem => resultItem.status === 'fulfilled')
        if (successPromise) {
          resolve(successPromise)
        } else {
          throw new Error('All promises were rejected')
        }
      }

      promiseList.forEach((promiseItem, index) => {
        // 每一个结果都是一个对象
        const resultItem = {}
        promiseItem.then((val)=> {
          // 更新该位置的promise信息
          resultItem['status'] = "fulfilled"
          resultItem['value'] = val
          result.push(resultItem)
          // 如果都运行完了再判断整个Promise的状态
          num ++
          if (num >= promiseList.length) dealResult()
        }, error => {
          // 更新该位置的promise信息
          resultItem['status'] = "rejected"
          resultItem['value'] = error
          result.push(resultItem)
          // 如果都运行完了再判断整个Promise的状态
          num ++
          if (num >= promiseList.length) dealResult()
        })
      })
    })
  }

代码运行效果如下: 在这里插入图片描述

参考文档

promise-catch