添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

2.1 Promise States https://promisesaplus.com/#promise-states

三个状态:pending、fulfilled、reject

状态转换:只能pending → fulfilled或 pending → reject

pending/reject时需要有对应且不改变(===)的值(value/reason)

  • “value” is any legal JavaScript value (including undefined , a thenable, or a promise).
  • “reason” is a value that indicates why a promise was rejected.
  • executor, resolve, reject

    核心Promise A+规范没有定义如何创建/resolve/reject Promise,只是规定了then方法:

    The core Promises/A+ specification does not deal with how to create, fulfill, or reject promises, choosing instead to focus on providing an interoperable then method. Future work in companion specifications may touch on these subjects.

    这里按照ES6 Promise的接口实现

    创建Promise时传入一个函数 (resolve, reject) => {} ,称为executor function

    const promise = new Promise((resolve, reject) => {
       // ...
      // resovle(someValue)
      // or
      // reject(someReason)
    

    其中resolve和reject是Promise的方法,它们的this指向这个Promise实例

    简易实现:

    class MyPromise {
      constructor(executor) {
        this.status = 'pending';
        this.data = undefined; // value or reason
        try {
          executor(this.resolve.bind(this), this.reject.bind(this));
        } catch (e) {
          // reject when executor failed
          this.reject(e);
      resolve(value) {
        if (this.status === 'pending') {
          this.status = 'fulfilled';
          this.data = value;
      reject(reason) {
        if (this.status === 'pending') {
          this.status = 'rejected';
          this.data = reason;
    

    then方法

    2.2 https://promisesaplus.com/#the-then-method

    A promise must provide a then method to access its current or eventual value or reason.

    A promise’s then method accepts two arguments:

    promise.then(onFulfilled, onRejected)

    规定的大致翻译

    onFulfilled、onRejected为可选参数,如果不是函数将被忽略 (2.2.1)

    若onFulfilled是函数,在fulfilled之后调用,第一个参数为promise的value。仅调用一次。(2.2.2)

    若onRejected是函数,在rejected之后调用,第一个参数为promise的reason。仅调用一次。(2.2.3)

  • 在执行栈清空之后才能调用 (2.2.4)
  • 作为函数调用(不指定this)(2.2.5)
  • 同一个promise的then方法可以被多次调用 (2.2.6)

  • 如果/当fulfilled,所有onFulfilled回调将按照then的调用顺序执行
  • 如果/当rejected,所有onRejected回调将按照then的调用顺序执行
  • then返回一个promise (2.2.7)

    promise2 = promise1.then(onFulfilled, onRejected);

    当onFulfilled或onRejected返回一个值x,执行the Promise Resolution Procedure (2.2.7.1)

    [[Resolve]](promise2, x)

    当onFulfilled或onRejected抛出异常e,promise2将以e为reason被reject (2.2.7.2)

    如果promise1fulfilled并且onFulfilled不是函数,promise2将以promise1的value fulfilled (2.2.7.3)

    如果promise1 rejected并且onRejected不是函数,promise2将以promise1的reason fulfilled (2.2.7.4)

    then返回一个Promise,因此可以链式调用

    fulfilled/rejected状态

    当状态为fulfilled或rejected,在返回的Promise直接调用onFulfilled/onRejected

    这里是简化版,规范的实现参见the Promise Resolution Procedure

    // 简化的fulfilled:在返回的promise executor中调用onFulfilled并传入value
    if (this.status === 'fulfilled') {
      return new Promise((resolve, reject) => {
        try {
          const x = onFulfilled(this.data); // rejected时调用onRejected
          resolve(x); // 实际上更复杂(2.2.7.1)
        } catch (e) {
          reject(e); // 2.2.7.2
      });
    

    pending状态、多次调用then

    当状态为pending,在返回的Promise中将fulfilled和rejected时的操作加入本Promise相应的回调函数队列:

    this.onFulfilledCallbacks = []; // 存放成功的回调
    this.onRejectedCallbacks = []; // 存放失败的回调
    if (this.status === 'pending') {
      return new Promise((resolve, reject) => {
        // this指向当前pending的这个promise
        // 即返回promise要执行的内容会保存在前一个promise里
        this.onFulfilledCallbacks.push((value) => {
          try { const x = onFulfilled(value); resolve(x); }
          catch (e) { reject(e); }
        });
        this.onRejectedCallbacks.push((reason) => {
          try { const x = onRejected(reason); resolve(x); }
          catch (e) { reject(e); }
        });
      });
    

    执行resolve/reject更改状态后,执行所有onResolved/onRejected回调函数

  • 因为状态转换只会进行一次,回调函数也只会执行一次
  • resolve
    
    
    
    
        
    (value) {
      if (this.status === 'pending') {
        this.status = 'fulfilled';
        this.data = value;
        this.onFulfilledCallbacks.forEach((fn) => fn());
    

    2.2.7.3,2.2.7.4 造成了“Promise值穿透”

    表现为then的参数不是函数时,之前的value/reason会继续传递给下一个then:

    Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log); // 1

    给非函数的onFulfilled、onRejected默认值即可实现:

    // 返回值将被resolve(Promise Resolution Procedure),抛出的异常将被reject
    onResolved = typeof onResolved === 'function' ? onResolved : (v) => { return v; };
    onRejected = typeof onRejected === 'function' ? onRejected : (r) => { throw r; };;

    onFulfilled、onRejected 应当等待调用栈空(平台代码除外)(2.2.4)

    实现成宏任务(setTimeout)就可以通过PromiseA+测试,但

  • 实际上JS的Promise.then是一个微任务
  • 可以用queueMicrotask(IE不支持)/ process.nextTick(nodejs)实现微任务
  • 多个嵌套的 setTimeout 调用在浏览器中的最小延迟为 4ms,可能导致性能问题
  • fulfilled/rejected状态:返回的promise的executor内异步执行:

    // 简化的fulfilled
    if (this.status === 'fulfilled') {
      return new Promise((resolve, reject) => {
        setTimeout(()=>{ // 使用“零延迟”的setTimeout安排一个新的宏任务
          try { const x = onFulfilled(this.data); resolve(x); }
          catch (e) { reject(e); }
      	});
      });
    

    pending状态:resolve/reject中异步执行回调函数(状态改变是同步的)

    resolve(value) {
      if (this.status === 'pending') {
        this.status = 'fulfilled';
        this.data = value;
        setTimeout(() => { // 使用“零延迟”的setTimeout安排一个新的宏任务
          this.onFulfilledCallbacks.forEach((fn) => fn(this.data));
        });
    

    截至目前的代码

    运行PromiseA+测试,可以通过2.3之前的测试哒

    对于onFulfilled/onRejected的返回值,只是直接resolve(x),只能正确处理非thenable的返回值。

    class MyPromise {
      constructor(executor) {
        this.status = 'pending';
        this.data = undefined; // value or reason
        this.onFulfilledCallbacks = []; // 存放成功的回调
        this.onRejectedCallbacks = [];  // 存放失败的回调
        try {
          executor(this.resolve.bind(this), this.reject.bind(this));
        } catch (e) {
          this.reject(e);
      resolve(value) {
        if (this.status === 'pending') {
          this.status = 'fulfilled';
          this.data = value;
          setTimeout(() => { // 异步
            this.onFulfilledCallbacks.forEach((fn) => fn(this.data));
          });
      reject(reason) {
        if (this.status === 'pending') {
          this.status = 'rejected';
          this.data = reason;
          setTimeout(() => { // 异步
            this.onRejectedCallbacks.forEach((fn) => fn(this.data));
          });
      then(onFulfilled, onRejected) {
        // onFulfilled/onRejected默认值和值穿透
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
        onRejected =
          typeof onRejected === 'function' ? onRejected : (r) => { throw
    
    
    
    
        
     r; };
        // 返回新的Promise,根据不同状态分别实现
        switch (this.status) {
          case 'fulfilled': {
            return new Promise((resolve, reject) => {
              setTimeout(() => { // 异步
                try {
                  const x = onFulfilled(this.data);
                  resolve(x);
                } catch (e) {
                  reject(e);
              });
            });
          case 'rejected': {
            return new Promise((resolve, reject) => {
              setTimeout(() => { // 异步
                try {
                  const x = onRejected(this.data);
                  resolve(x);
                } catch (e) {
                  reject(e);
              });
            });
          case 'pending': {
            // 加入回调队列
            return new Promise((resolve, reject) => {
              this.onFulfilledCallbacks.push((value) => {
                try {
                  const x = onFulfilled(value);
                  resolve(x);
                } catch (e) {
                  reject(e);
              });
              this.onRejectedCallbacks.push((reason) => {
                try {
                  const x = onRejected(reason);
                  resolve(x);
                } catch (e) {
                  reject(e);
              });
            });
    

    The Promise Resolution Procedure

    实现标准的[[Resolve]](promise, x)

    2.3 The Promise Resolution Procedure

    The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.

    当x是thenable时,返回的promise会尽量继承x的状态;否则resolve(x)

    (规范写得很详细,按照它实现就行,就不翻译了)

    thenable

    “thenable” is an object or function that defines a then method.

    符合标准的Promise都是thenable,这样保证了不同的Promise实现可以互相兼容

    thenable不一定是Promise,也要按照标准尽量处理

    Promise Resolution Procedure (2.2.7.1)

    实现一个函数替换之前直接resolve(x)的部分,

    function promiseResolutionProcedure(promise, x, resolve, reject)

    其中promise是返回的Promise,x为onFulfilled/onRejected返回值。

    resolve,reject是promise的executor参数,当promise执行executor时它们会绑定promise为this

    这样调用:

    // resolved时then返回的promise
    const promise = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        try {
          const x = onFulfilled(this.data);
          promiseResolutionProcedure(promise, x, resolve, reject); // here
        } catch (e) {
          reject(e);
      });
    });

    x === promise (2.3.1)

    if (promise === x) {
      return reject(new TypeError('Chaining cycle detected for promise'));
    

    测试这个错误

    const promise = new Promise((resolve) => {
      resolve();
    }).then(() => {
      return promise;
    });

    p.s. 可以在then的onFulfilled参数中访问then返回的promise,是因为onFulfilled执行时promise已经定义过了(then的参数异步执行)

    比如在构造函数executor里是不能同步访问自己哒(executor同步执行)

    // ReferenceError: Cannot access 'p2' before initialization
    const p = new Promise((resolve) => { console.log(p); resolve(); });
    // 改成var不会报错,但是值为undefined,同样没有初始化,参见js的词法环境
    var p2 = new
    
    
    
    
        
     Promise((resolve) => { console.log(p2); resolve(); }); // undefined

    我们的promiseResolutionProcedure(promise, x, resolve, reject)在promise的executor中被异步地调用,因此也能访问promise。

    p.p.s 上面的例子,如果在resolve之后访问p2不会报错。

    但这不是因为没有执行或者p2有了值,是因为Promise的状态不会改变导致错误不会被显示

    const p = new Promise((resolve) => {
      resolve();
      console.log(p2); // error but nothing happens
    });

    x is thenable (2.3.3)

    thenable判断
  • x is function or object (2.3.3)
  • x.then is function
  • 保存x.then,之后通过保存的then调用 (2.3.3.1)
  • 防止不同实现中,多次调用x.then返回不同的结果
  • 如果获取x.then时抛出异常,则reject (2.3.3.2)
  • 当x不是thenable,则resolve(x) (2.2.3.4,2.3.4)

    执行then

    以x为this调用then,传入resolvePromise和rejectPromise两个参数

  • resolvePromise(y)函数执行[[Resolve]](promise, y)
  • rejectPromise(r)函数reject promise with reason r
  • if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
      try {
        then = x.then; // 2.3.3.1 value could change between retrievals
        if (typeof then === 'function') {
          // 2.3.3.3
          then.call(
            (y) => promiseResolutionProcedure(promise, y, resolve, reject),
            (r) => reject(r)
        } else {
          resolve(x); // 2.3.3.4
      } catch (e) {
        reject(e); // 2.3.3.2
    } else {
      resolve(x); // 2.3.4
    
    防止多次调用resolvePromise/rejectPromise

    根据规范2.3.3.3.3,2.3.3.3.4.1,A、B、C三处只有第一次调用有效,之后的调用将被忽略。

    在处理函数中使用一个变量保存是否调用过

    let thenCalledOrThrow = false;
    if (typeof then === 'function') {
      try {
        then.call(
          (y) => { // A
            if (thenCalledOrThrow) return;
            thenCalledOrThrow = true;
            promiseResolutionProcedure(promise, y, resolve, reject);
          (r) => { // B
            if (thenCalledOrThrow) return;
            thenCalledOrThrow = true;
            reject(r);
      } catch (e) { // C
        if (thenCalledOrThrow) return;
        thenCalledOrThrow = true;
        reject(e);
    

    P.S. 关于为什么三处都需要判断thenCalledOrThrow

    看起来我们的resolve和reject也只在第一次执行时有效果,为何需要额外判断?

    注意规定的是resolvePromise/rejectPromise/异常reject只执行第一次,而不是resolve、reject。多次执行ABC三处可能导致resolve、reject的调用顺序改变。

    比如这个测试:

    // onFulfilled返回一个thenable,它的then方法
    // 先以一个异步resolve的Promise为y参数,调用resolvePromise,
    // 再抛出一个异常
    const promise = new MyPromise((resolve) => resolve('dummy')).then(() => {
      return {
        then: function (resolvePromise) {
          resolvePromise(
            new Promise((resolve) => { setTimeout(resolve(1)); })
          throw 2;
    });
    promise.then(console.log, console.error); // should be 1

    正确的实现中,promise应当被resolve,值为1。因为已经执行过resolvePromise,抛出的异常会被忽略。

    如果在C处不加判断,执行resolvePromise(y),y将在下一次宏(微)任务中调用resolve,在那之前抛出异常会导致reject先被执行,promise被reject,reason为2。

    x is promise (2.3.2)

    实现了2.3.3就可以通过测试了,因为Promise也是thenable。

    规定2.3.2单独规定If x is a promise(x是我们实现的MyPromise实例)使得我们可以使用implementation-specific means来处理。

    我们的实现的then函数保证了onFulfilled/onRejected只会调用一次,也不会抛出异常,因此不需要考虑2.3.3的很多细节。

    “adopt its state”
  • 当x pending,返回promise也pending
  • 当x fulfilled/rejected,返回promise也以同样的value/reason fulfilled/rejected
  • 调用x.then,即根据x的状态调用resolve/reject,改变promise的状态

    if (x instanceof MyPromise) {
      x.then(resolve, reject);
      return;
    

    但是以resolve作为onFulfilled,无法处理resolve参数为thenable的情况:

    // onFulfilled返回一个MyPromise
    // 它的executor以一个thenable作为参数调用resolve
    const promise = new MyPromise((resolve) => resolve('dummy')).then(() => {
      return new MyPromise((resolve) => {
        resolve(Promise.resolve(1));
      });
    });
    promise.then(console.log, console.error); // should be 1

    promise的值应当为1,而不是Promise.resolve(1)。参考x is thenable中的规则,应当继续通过Promise Resolution Procedure处理(thenable类型)的参数。

    最终实现为:

    if (x instanceof MyPromise) {
      x.then( // 递归调用promiseResolutionProcedure作为onFulfilled参数
        (y) => promiseResolutionProcedure(promise, y, resolve, reject),
        reject // reject不需要
      return;
    

    与x is thenable情况相比,只是简化了判断thenable、避免多次调用、处理异常这些零碎部分。

    至此实现了一个符合PromiseA+规则的Promise。代码:

    https://github.com/mahouoji/promise-aplus-implement

    PromiseA+测试

    https://github.com/promises-aplus/promises-tests

    修改package.json

    "scripts": {
        "test": "promises-aplus-tests index.js"
    

    在MyPromise实现代码后添加:

    MyPromise.deferred = function () {
      let dfd = {};
      dfd.promise = new MyPromise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
      });
      return dfd;