关于Promise规范与执行顺序的问题

Cole
  • 23

最近参照Promise/A+规范实现了一个自己的Promise,代码如下:

const enum PromiseState {
  PENDING = "pending",
  FULFILLED = "fulfilled",
  REJECTED = "rejected",
}

export class PromiseLike {
  private _state: PromiseState;
  private _value: any; // 被决议的终值
  private _callbacks: any[]; // then中被注册的回调

  constructor(exector: (resolve, reject) => any) {
    this._state = PromiseState.PENDING;
    this._value = undefined;
    this._callbacks = [];

    const resolve = value => {
      if (this._state !== PromiseState.PENDING) return;
      this._state = PromiseState.FULFILLED;
      this._value = value;

      createMicroTask(() => {
        this._callbacks.forEach(cb => {
          cb();
        });
      });
    };
    const reject = reason => {
      if (this._state !== PromiseState.PENDING) return;
      this._state = PromiseState.REJECTED;
      this._value = reason;

      createMicroTask(() => {
        this._callbacks.forEach(cb => {
          cb();
        });
      });
    };

    exector(resolve, reject);
  }

  then(onfulfilled?, onrejected?) {
    const onFulfilled = typeof onfulfilled === "function" ? onfulfilled : undefined;
    const onRejected = typeof onrejected === "function" ? onrejected : undefined;

    const returnValue = new PromiseLike((resolve, reject) => {
      const callback = () => {
        // 如果Promise已决议,不重复执行
        if (this._state === PromiseState.PENDING) {
          return;
        } else {
          let fn;
          // onFulfilled或onRejected为undefined则被忽略
          if (this._state === PromiseState.FULFILLED) {
            if (onFulfilled) {
              fn = onFulfilled;
            } else {
              resolve(this._value);
              return;
            }
          } else if (this._state === PromiseState.REJECTED) {
            if (onRejected) {
              fn = onRejected;
            } else {
              reject(this._value);
              return;
            }
          }
          let result;
          try {
            result = fn.call(undefined, this._value);
          } catch (e) {
            reject(e);
            return;
          }
          resolution(returnValue, result, resolve, reject);
        }
      };

      if (this._state !== PromiseState.PENDING) {
        createMicroTask(callback);
      } else {
        this._callbacks.push(callback);
      }
    });

    return returnValue;
  }
}

// [[Resolve]](promise,x)
function resolution(promise, value, resolve, reject) {
  if (promise === value) {
    reject(new TypeError("PromiseLike: promise cannot have the same reference with value"));
    return;
  } else if (typeof value === "function" || (value && typeof value === "object")) {
    let then;
    try {
      then = value.then;
    } catch (e) {
      reject(e);
      return;
    }
    if (typeof then === "function") {
      let called = false;
      const resolvePromise = val => {
        if (called) return;
        called = true;
        resolution(promise, val, resolve, reject);
      };
      const rejectPromise = res => {
        if (called) return;
        called = true;
        reject(res);
      };
      try {
        then.call(value, resolvePromise, rejectPromise);
      } catch (e) {
        rejectPromise(e);
      }
    } else {
      resolve(value);
    }
  } else {
    resolve(value);
  }
}

function createMicroTask(fn) {
  queueMicrotask(fn);
}

// 测试用
// export const adapter = {
//   deferred() {
//     let res, rej;
//     const promise = new PromiseLike((resolve, reject) => {
//       res = resolve;
//       rej = reject;
//     });
//     return {
//       promise,
//       resolve: res,
//       reject: rej,
//     };
//   },
// };
// module.exports = adapter;

这个代码通过了Promise/A+的所有测试用例。但是有这么一道关于Promise的面试题:

new Promise<void>((resolve, reject) => {
    resolve();
  })
    .then(() => {
      console.log(0);
      return new Promise((resolve, reject) => {
        resolve(4);
      });
    })
    .then(res => {
      console.log(res);
    });

  new Promise<void>((resolve, reject) => {
    resolve();
  })
    .then(() => {
      console.log(1);
    })
    .then(() => {
      console.log(2);
    })
    .then(() => {
      console.log(3);
    })
    .then(() => {
      console.log(5);
    })
    .then(() => {
      console.log(6);
    });

问代码的输出应该是什么。这道题的输出应该是0 1 2 3 4 5 6,但是用我自己写的PromiseLike,输出为:0 1 2 4 3 5 6。我在网上也找了几个别人实现的Promise,输出两种情况都有。所以我的问题是:

  1. 是在哪一步出了问题导致结果不一样
  2. 通过Promise/A+所有测试用例的Promise就是一个标准的Promise实现吗

    2.1 如果是,那这种输出不一样的情况是因为规范有遗漏吗

    2.2 如果不是,是因为Promise/A+官方测试用例有没覆盖到的情况吗

谢谢大家了

回复
阅读 1k
3 个回答
fefe
  • 13.5k
✓ 已被采纳

你这里有两套 promise 。promise/A+ 是不关心这两套 promise 之间的执行顺序关系的。

你可以对一下 promise/A+ ,这两个结果应该都是符合规范的。只能说你的实现与 javascript 可能有点不一样。仅参照 promise/A+ ,两套 Promise 存在的时候是有很多中不同可能结果的。因为 promise/A+ 根本没有规定两个无关的 promise 之间的执行顺序。

javascript 标准是仔细规定了 promsise 的行为的。它的标准是在规定整个程序的行为,promise 只是其中的一部分。按照 javascript 标准,肯定是有唯一结果的。

所以,你的 promiseLike 符合 promise/A+ 规范,但是跟 javacript 标准并不完全相同。


比如,javascprit 里,用 Promise resolve promise 的时候会产生一个微任务:15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).,但是你的实现并没有。

但是,是否产生微任务都不违反 promise/A+ 。

不懂,只是基于显示结果可以知道:
打印1,之后回到了then(res),结果发现这是个Promise,Promise入栈,离开
打印2,之后回到了then(res),发现这个Promise还没执行,执行Promise,离开
打印3,之后回到了then(res),这时候拿到4了,执行then,离开。

所以比较大的概率是,在执行过程中发现新的Promise对象时,将其加入处理序列,但是本次轮询中不进行处理,而你的代码可能是加入处理序列后马上就处理了。

宣传栏