手写 Promise第四课:从队列中执行并管理执行函数

97 阅读2分钟

在前端开发中,Promise 是异步编程的核心工具之一。了解其内部机制对于深入掌握 JavaScript 异步处理至关重要。在上节课中,我们学习了如何将执行函数放入队列。本节课,我们将探讨如何从队列中取出执行函数进行执行,以及如何管理这些执行函数。

执行函数的时机

执行函数的时机主要有两个:

  1. then 方法调用时:每次调用 then 方法时,都会将新的执行函数添加到队列中,并立即尝试执行队列中的函数。
  2. 状态改变时:当 Promise 的状态从 pending 变为 fulfilled 或 rejected 时,也需要执行队列中的函数。

执行函数后的处理

为了避免重复执行,执行函数后需要从队列中删除该函数。

删除任务队列的方式

  • 先进先出:每次都取出队列中的第一个任务执行,然后删除。
  • 避免循环删除:在执行过程中循环删除可能会导致遗漏或重复执行。

代码实现

class MyPromise {
  // 省略其他部分...

  /**
   * 根据实际情况,执行队列
   */
  _runHandlers() {
    if (this._state === PENDING) {
      // 目前任务仍在挂起
      return;
    }
    while (this._handlers[0]) {
      const handler = this._handlers[0];
      this._runOneHandler(handler);
      this._handlers.shift();
    }
  }
  
  _runOneHandler(handler) {
    // 根据handler的类型(onFulfilled或onRejected)执行相应的逻辑
    // 这里需要异步执行,可以使用setTimeout
    setTimeout(() => {
      const result = handler.action(handler.value);
      if (result instanceof MyPromise) {
        result.then((res) => this._resolve(res), (err) => this._reject(err));
      } else {
        this._resolve(result);
      }
    }, 0);
  }

  /**
   * Promise A+规范的then
   * @param {Function} onFulfilled
   * @param {Function} onRejected
   */
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this._pushHandler(onFulfilled, FULFILLED, resolve, reject);
      this._pushHandler(onRejected, REJECTED, resolve, reject);
      this._runHandlers(); // 执行队列
    });
  }

  /**
   * 更改任务状态
   * @param {String} newState 新状态
   * @param {any} value 相关数据
   */
  _changeState(newState, value) {
    if (this._state !== PENDING) {
      // 目前状态已经更改
      return;
    }
    // 下面这个判断是为了处理value为Promise的情况
    // 这一段代码课程中没有涉及,特此注释说明
    if (isPromise(value)) {
      value.then(this._resolve.bind(this), this._reject.bind(this));
      return;
    }
    this._state = newState;
    this._value = value;
    this._runHandlers(); // 状态变化,执行队列
  }
}

总结

通过深入理解 Promise 的内部机制,我们可以更好地控制异步任务的执行流程。本节课我们学习了如何从队列中取出执行函数进行执行,并在执行后从队列中删除,以避免重复执行。这对于编写高效、可靠的异步代码至关重要。