Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promises implementation with ES6 class #14

Open
ghost opened this issue Aug 9, 2019 · 0 comments
Open

Promises implementation with ES6 class #14

ghost opened this issue Aug 9, 2019 · 0 comments

Comments

@ghost
Copy link

ghost commented Aug 9, 2019

同步链接: https://www.shanejix.com/posts/Promises implementation with ES6 class/

本文参考 Promises/A+ 规范实现 new Promise(),.then()等功能,如不了解 Promise请参考《Promise 必知必会》。更具体的 Promises/A+ 规范见这里

demo

看一个测试代码test.js

let promise1 = new Promise(
  (resolve, reject) => {
    // write your code here

    setTimeout(() => {
      resolve("foo");
    }, 300);
  } /* executor */
);

promise1.then((value) => {
  console.log(value); // expected output: "foo"
});

console.log(promise1); // expected output: [object Promise]

期望的test.js执行阶段

new Promise() => setTimeout() => .then() => console.log(promise1)

输出日志如下

Promise {<pending>}

foo

但是这样不能明显的发现问题,修改test.js如下

let promise1 = new Promise((resolve, reject) => {
  resolve("foo");
});

promise1.then((value) => {
  console.log(value);
});

console.log(promise1);

期望的test.js执行阶段

new Promise() => resolve() => .then() => console.log(promise1)

输出日志如下

Promise {<fulfilled>: 'foo'}

foo

这样问题就很明显了:我们期望的是在 执行rosolve()之前就拿到 promise1.then()中的回调函数 - 这就是Promise实现异步操作的关键之处了

new Promise()

如何实现?

Promise基本的使用

new Promise(
  (resolve, reject) => {
    // write your code here
  } /* executor*/
);

干了什么

1. Promise构造函数执行时会调用 executor 函数

2. resolve 和 reject 两个内部函数作为参数传递给 executor

实现new Promise()构造函数框架如下(标准中没有指定构造函数的具体行为)

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // 2.1. Promise 的状态
    // Promise 必须处于以下三种状态之一:pending,fulfilled 或者 rejected。
    this.status = PENDING;

    // 2.2.6.1. 如果 promise 处于 fulfilled 状态,所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行
    this.onFulfilledCallbacks = [];
    // 2.2.6.2. 如果 promise 处于 rejected 状态,所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。
    this.onRejectedCallbacks = [];

    // 成功之后的值
    this.value = null;
    // 失败之后的原因
    this.reason = null;

    /**
     * 更改成功后的状态
     * @param {*} value
     */
    const resolve = (value) => {
      // todo
    };

    /**
     * 更改失败后的状态
     * @param {*} reason
     */
    const reject = (reason) => {
      // todo
    };

    // 传入的函数可能也会执行异常,所以这里用 try...catch 包裹
    try {
      // executor 是一个执行器,进入会立即执行,并传入resolve和reject方法
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
}

module.exports = Promise;

实现 resolvereject

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // 2.1. Promise 的状态
    // Promise 必须处于以下三种状态之一:pending,fulfilled 或者 rejected。
    this.status = PENDING;

    // 2.2.6.1. 如果 promise 处于 fulfilled 状态,所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行
    this.onFulfilledCallbacks = [];
    // 2.2.6.2. 如果 promise 处于 rejected 状态,所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。
    this.onRejectedCallbacks = [];

    // 成功之后的值
    this.value = null;
    // 失败之后的原因
    this.reason = null;

    /**
     * 更改成功后的状态
     * @param {*} value
     */
    const resolve = (value) => {
      // 2.1.1. 当 Promise 处于 pending 状态时:
      // 2.1.1.1. 可以转换到 fulfilled 或 rejected 状态。
      // 2.1.2. 当 Promise 处于 fulfilled 状态时:
      // 2.1.2.1. 不得过渡到任何其他状态。
      // 2.1.2.2. 必须有一个不能改变的值。
      if (this.status === PENDING) {
        // 状态修改为成功
        this.status = FULFILLED;
        // 保存成功之后的值
        this.value = value;
        // 2.2.6.1. 如果 promise 处于 fulfilled 状态,所有相应的 onFulfilled 回调必须按照它们对应的 then 的原始调用顺序来执行。
        while (this.onFulfilledCallbacks.length) {
          this.onFulfilledCallbacks.shift()(value);
        }
      }
    };

    /**
     * 更改失败后的状态
     * @param {*} reason
     */
    const reject = (reason) => {
      // 2.1.1. 当 Promise 处于 pending 状态时:
      // 2.1.1.1. 可以转换到 fulfilled 或 rejected 状态。
      // 2.1.3. 当 Promise 处于 rejected 状态时:
      // 2.1.2.1. 不得过渡到任何其他状态。
      // 2.1.2.2. 必须有一个不能改变的值。
      if (this.status === PENDING) {
        // 状态成功为失败
        this.status = REJECTED;
        // 保存失败后的原因
        this.reason = reason;
        // 2.2.6.2. 如果 promise 处于 rejected 状态,所有相应的 onRejected 回调必须按照它们对应的 then 的原始调用顺序来执行。
        while (this.onRejectedCallbacks.length) {
          this.onRejectedCallbacks.shift()(reason);
        }
      }
    };

    // 传入的函数可能也会执行异常,所以这里用 try...catch 包裹
    try {
      // executor 是一个执行器,进入会立即执行,并传入resolve和reject方法
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
}

module.exports = Promise;

这里需要注意一个细节:多个 .then()添加到同一个 promise

let promise = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function (result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function (result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function (result) {
  alert(result); // 1
  return result * 2;
});

**这就是 ****onFulfilledCallbacks****和 ****onRejectedCallbacks**被处理得为数组得原因

.then()

根据 demo中的启发:**promise.then()****用来注册在这个 Promise 状态确定后的回调。**需要注意的几点

  • 很明显.then()方法需要写在原型链上
  • Promise/A+标准中明确.then()返回一个新对象(详情),Promise实现中几乎都是返回一个新的**Promise**对象

实现**then()****方法框架 **如下

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // ...
  }

  /**
   * then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
   * @param {*} onResolved
   * @param {*} onRejected
   * @returns
   */
  then(onResolved, onRejected) {
    let promise2;

    // 根据标准,如果then的参数不是function,则需要忽略它,此处以如下方式处理
    onResolved = typeof onResolved === "function" ? onResolved : (v) => v;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (r) => {
            throw r;
          };

    if (this.status === "fulfilled") {
      return (promise2 = new Promise((resolve, reject) => {
        // todo
      }));
    }

    if (this.status === "rejected") {
      return (promise2 = new Promise((resolve, reject) => {
        // todo
      }));
    }

    if (this.status === "pending") {
      return (promise2 = new Promise((resolve, reject) => {
        // todo
      }));
    }
  }
}

后续扩展发现,如下then的结构更灵活:

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // ...
  }

  /**
   * then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
   * @param {*} onResolved
   * @param {*} onRejected
   * @returns
   */
  then(onResolved, onRejected) {
    // 根据标准,如果then的参数不是function,则需要忽略它,此处以如下方式处理
    onResolved = typeof onResolved === "function" ? onResolved : (v) => v;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (r) => {
            throw r;
          };

    const promise2 = new Promise((resolve, reject) => {
      if (this.status === "fulfilled") {
        // todo
      }

      if (this.status === "rejected") {
        // todo
      }

      if (this.status === "pending") {
        // todo
      }
    });

    return promise2;
  }
}

三种状态下的 Promise都会返回 new Promise()。返回的 promise2的状态如何确定呢?

看个例子

const promise1 = new Promise((resovle, reject) => {
  // ...
});

const promise2 = promise1.then(
  (value) => {
    return 4;
  },
  (reason) => {
    throw new Error("sth went wrong");
  }
);

根据标准,上述代码,promise2的值取决于then里面函数的返回值:

- 如果 promise1 被 resolve 了,promise2 的将被 4 resolve,

- 如果 promise1 被 reject 了,promise2 将被 new Error('sth went wrong') reject,

所以,需要**在 then 内部执行 onResolved 或者 onRejected,并根据返回值(标准中记为 x)来确定 promise2 的结果。**并且,如果 onResolved/onRejected 返回的是一个 Promise,promise2 将直接取这个 Promise 的结果.

具体实现

// 定义三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  /**
   * Promise 构造函数接收一个 executor 函数,
   * executor 函数执行完同步或异步操作后,调用它的两个参数 resolve 和 reject
   * @param {*} executor
   */
  constructor(executor) {
    // ...
  }

  /**
   * 2.2. then 方法
   * 一个 promise 必须提供一个 then 方法来访问其当前值或最终值或 rejected 的原因。
   * 一个 promise 的 then 方法接受两个参数:
   * promise.then(onFulfilled, onRejected)
   * @param {*} onFulfilled
   * @param {*} onRejected
   * @returns
   */
  then(onFulfilled, onRejected) {
    // 2.2.1. onFulfilled 和 onRejected 都是可选参数:
    // 2.2.1.1. 如果 onFulfilled 不是一个函数,它必须被忽略。
    // 2.2.7.3. 如果 onFulfilled 不是一个函数且 promise1 为 fulfilled 状态,promise2 必须用和 promise1 一样的值来变为 fulfilled 状态。
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    // 2.2.1. onFulfilled 和 onRejected 都是可选参数:
    // 2.2.1.2. 如果 onRejected 不是一个函数,它必须被忽略。
    // 2.2.7.4. 如果 onRejected 不是一个函数且 promise1 为 rejected 状态,promise2 必须用和 promise1 一样的 reason 来变为 rejected 状态。
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };

    // 2.2.7. then 必须返回一个 promise
    const promise2 = new Promise((resolve, reject) => {
      const fulfilledMicrotask = () => {
        // 2.2.4. onFulfilled 或 onRejected 在执行上下文堆栈仅包含平台代码之前不得调用。
        // 3.1. 这可以通过“宏任务”机制(例如 setTimeout 或 setImmediate)或“微任务”机制(例如 MutationObserver 或 process.nextTick)来实现。
        setTimeout(() => {
          try {
            // 2.2.2.1. onFulfilled 必须在 promise 的状态变为 fulfilled 后被调用,并将 promise 的值作为它的第一个参数。
            // 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。
            const x = onFulfilled(this.value);
            // 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x,则运行 Promise 处理程序 [[Resolve]](promise2, x)。
            promiseResolutionHandler(promise2, x, resolve, reject);
          } catch (error) {
            // 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常,promise2 必须用 e 作为 reason 来变为 rejected 状态。
            reject(error);
          }
        });
      };

      const rejectedMicrotask = () => {
        // 2.2.4. onFulfilled 或 onRejected 在执行上下文堆栈仅包含平台代码之前不得调用。
        // 3.1. 这可以通过“宏任务”机制(例如 setTimeout 或 setImmediate)或“微任务”机制(例如 MutationObserver 或 process.nextTick)来实现。
        setTimeout(() => {
          try {
            // 2.2.3.1. 它必须在 promise 的状态变为 rejected 后被调用,并将 promise 的 reason 作为它的第一个参数。
            // 2.2.5. onFulfilled 和 onRejected 必须作为函数调用。
            const x = onRejected(this.reason);
            // 2.2.7.1. 如果 onFulfilled 或 onRejected 返回了一个值 x,则运行 Promise 处理程序 [[Resolve]](promise2, x)。
            promiseResolutionHandler(promise2, x, resolve, reject);
          } catch (error) {
            // 2.2.7.2. 如果 onFulfilled 或 onRejected 抛出了一个异常,promise2 必须用 e 作为 reason 来变为 rejected 状态。
            reject(error);
          }
        });
      };

      // 2.2.2. 如果 onFulfilled 是一个函数:
      // 2.2.2.1. 它必须在 promise 的状态变为 fulfilled 后被调用,并将 promise 的值作为它的第一个参数。
      // 2.2.2.2. 它一定不能在 promise 的状态变为 fulfilled 前被调用。
      // 2.2.2.3. 它最多只能被调用一次。
      if (this.status === FULFILLED) {
        // 如果promise1(此处即为this)的状态已经确定并且是fulfilled,调用 resolvedMicrotask
        fulfilledMicrotask();
      }

      // 2.2.3. 如果 onRejected 是一个函数,
      // 2.2.3.1. 它必须在 promise 的状态变为 rejected 后被调用,并将 promise 的 reason 作为它的第一个参数。
      // 2.2.3.2. 它一定不能在 promise 的状态变为 rejected 前被调用。
      // 2.2.3.3. 它最多只能被调用一次。
      if (this.status === REJECTED) {
        // 如果promise1(此处即为this)的状态已经确定并且是rejected,调用 rejectedMicrotask
        rejectedMicrotask();
      }

      // 2.2.6. then 可能会被同一个 promise 多次调用。
      if (this.status === PENDING) {
        // 如果当前的Promise还处于pending状态,并不能确定调用onResolved还是onRejected,
        // 只能等到Promise的状态确定后,才能确实如何处理
        // 所以需要把**两种情况**的处理逻辑做为callback放入promise1(此处即this)的回调数组里
        this.onFulfilledCallbacks.push(fulfilledMicrotask);
        this.onRejectedCallbacks.push(rejectedMicrotask);
      }
    });

    return promise2;
  }
}

promiseResolutionHandler集中处理程序

/**
 * 2.3. Promise 处理程序
 * Promise 处理程序是一个将 promise2 和 value 作为输入的抽象操作,将其表示为 [[Resolve]](promise2, x)。
 * 补充说明:这里将 resolve 和 reject 也传入进来,因为后续要根据不同的逻辑对 promise2 执行 fulfill 或 reject 操作。
 * @param {*} promise2
 * @param {*} x
 * @param {*} resolve
 * @param {*} reject
 * @returns
 */
function promiseResolutionHandler(promise2, x, resolve, reject) {
  // 2.3.1. 如果 promise2 和 x 引用的是同一个对象,promise2 将以一个 TypeError 作为 reason 来进行 reject。
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise"));
  }

  /**
   
  // 与 2.3.3 有重叠部分

  // 2.3.2. 如果 x 是一个 Promise,根据它的状态:
  if (x instanceof Promise) {
    // 2.3.2.1. 如果 x 的状态为 pending,Promise 必须保持 pending 状态直到 x 的状态变为 fulfilled 或 rejected。
    if (x.state === "pending") {
      x.then(
        (value) => {
          promiseResolutionHandler(promise2, value, resolve, reject);
        },
        reject
      );
    } else if (x.state === "fulfilled") {
      // 2.3.2.2. 如果 x 的状态为 fulfilled,那么 promise2 也用同样的值来执行 fulfill 操作。
      resolve(x.data);
    } else if (x.state === "rejected") {
      // 2.3.2.3. 如果 x 的状态为 rejected,那么 promise2 也用同样的 reason 来执行 reject 操作。
      reject(x.data);
    }
    return;
  }

  */

  // 2.3.3. 除此之外,如果 x 是一个对象或者函数,
  if (typeof x === "object" || typeof x === "function") {
    // 如果 x 是 null,直接 resolve
    if (x === null) {
      return resolve(x);
    }

    // 2.3.3.3.3. 如果 resolvePromise 和 rejectPromise 都被调用,或者多次调用同样的参数,则第一次调用优先,任何之后的调用都将被忽略。
    let isCalled = false;

    try {
      // 2.3.3.1. 声明一个 then 变量来保存 then
      let then = x.then;
      // 2.3.3.3. 如果 then 是一个函数,将 x 作为 this 来调用它,第一个参数为 resolvePromise,第二个参数为 rejectPromise,其中:
      if (typeof then === "function") {
        try {
          then.call(
            x,
            // 2.3.3.3.1. 假设 resolvePromise 使用一个名为 y 的值来调用,运行 Promise 处理程序 [[Resolve]](promise, y)。
            function resolvePromise(y) {
              // 2.3.3.3.3. 如果 resolvePromise 和 rejectPromise 都被调用,或者多次调用同样的参数,则第一次调用优先,任何之后的调用都将被忽略。
              if (isCalled) return;
              isCalled = true;
              promiseResolutionHandler(promise2, y, resolve, reject);
            },
            // 2.3.3.3.2. 假设 rejectPromise 使用一个名为 r 的 reason 来调用,则用 r 作为 reason 对 promise2 执行 reject 操作。
            function rejectPromise(r) {
              if (isCalled) return;
              isCalled = true;
              reject(r);
            }
          );
        } catch (error) {
          // 2.3.3.3.4. 如果调用 then 时抛出一个异常 e,
          // 2.3.3.3.4.1. 如果 resolvePromise 或 rejectPromise 已经被调用过了,则忽略异常。
          if (isCalled) return;

          // 2.3.3.3.4.2. 否则,使用 e 作为 reason 对 promise2 执行 reject 操作。
          reject(error);
        }
      } else {
        // 2.3.3.4. 如果 then 不是一个函数,使用 x 作为值对 promise2 执行 fulfill 操作。
        resolve(x);
      }
    } catch (error) {
      // 2.3.3.2. 如果检索 x.then 的结果抛出异常 e,使用 e 作为 reason 对 promise2 执行 reject 操作。
      return reject(error);
    }
  } else {
    // 2.3.4. 如果 x 不是一个对象或者函数,使用 x 作为值对 promise2 执行 fulfill 操作。
    resolve(x);
  }
}

至此就完成了then()的实现,需要引起注意的地方:

注意点一:可以看出同步任务不涉及 callback 的存储,异步任务会先进入宏任务队列,会在 JS 主栈空闲时执行存储的 callback, 核心实现其实就是——发布订阅模式

注意点二:链式调用的核心就是 .then() 返回一个新的 Promise

注意点三:**Promise**值穿透

// 片段一
new Promise((resolve) => resolve(8))
  .then()
  .catch()
  .then((value) => {
    alert(value);
  });

// 片段二
new Promise((resolve) => resolve(8))
  .then((value) => {
    return value;
  })
  .catch((reason) => {
    throw reason;
  })
  .then((value) => {
    alert(value);
  });

片段一和片段二效果应该一样,如果**then()**的实参留空且让值可以穿透到后面,只需要给**then()**的两个参数设定默认值即可

onResolved =
  typeof onResolved === "function"
    ? onResolved
    : (value) => {
        return value;
      };

onRejected =
  typeof onRejected === "function"
    ? onRejected
    : (reason) => {
        throw reason;
      };

注意点四:**thenable 的核心逻辑 **参考promiseResolutionHandler集中处理程序

.catch()

其实就是.then(null,()=>()}

class Promise {
  // ...

  /**
   * catch方法
   * @param {*} onRejected
   * @returns
   */
  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

Promise.all()

class Promise {
  // ...

  /**
   * Promise.all()方法
   * @param {*} promiseArr
   * @returns
   */
  static all(promiseArr = []) {
    return new Promise((resolve, reject) => {
      // 记录成功的数量
      let index = 0;
      // 记录成功的结果
      let result = [];

      for (let i = 0; i < promiseArr.length; i++) {
        promiseArr[i].then((val) => {
          index++;
          result[i] = val; // 保证结果的顺序和数组顺序一致
          if (index === promiseArr.length) {
            resolve(result);
          }
        }, reject);
      }
    });
  }
}

小结

社区有很多实现,包括一些三方库的实现,还有其他语言基于 Promises/A+ 的实现:https://promisesaplus.com/implementations

完整版实现:https://github.com/shanejix/front-end-playground/blob/master/javascript/implement-promise/promise.js

测试结果:
image.png

new Promise()处理同步任务和异步任务有所区别:同步任务会立即 resolve()掉并修改 当前 promise 的状态。异步任务会预先存储 callback(订阅事件),然后等待时机resolve() 掉 当前 promise ,核心思想就是 **发布订阅模式 **。

.then()默认返回一个新的 promise :这是 promise 实现 **链式调用 **的核心

references

作者:shanejix
出处:https://www.shanejix.com/posts/Promises implementation with ES6 class/
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
声明:转载请注明出处!

@ghost ghost added the JavaScript JavaScript label Aug 9, 2019
@shanejix shanejix changed the title Implementation Promise Promises/A+ implementation with ES6 class Jan 18, 2022
@shanejix shanejix changed the title Promises/A+ implementation with ES6 class Promises A+ implementation with ES6 class Jan 18, 2022
@shanejix shanejix changed the title Promises A+ implementation with ES6 class Promises implementation with ES6 class Mar 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant