手撕遵循 Promise/A+ 规范的 Promise

相比于回调函数,Promise 解决了 “回调地狱” 和 “信任问题” 等痛点,而且大大提升了代码的可读性。在现代前端开发中,Promise 几乎成了处理异步的首选(虽然还有更方便的 async/await,逃)。这篇文章从 Promise 的思想和运行机制入手,深刻理解每一个 API,最后手写一个遵循 Promise/A+ 规范的 Promise 来。前端

异步方式

JavaScript 异步方式共有有下面六种。git

  • 事件监听github

  • 回调函数面试

  • 发布/订阅ajax

  • Promise算法

  • 生成器json

  • async/await小程序

回调函数

面试中被问到 回调函数 有什么缺点,相信你必定不假思索地回答 回调地狱。的确如此,当咱们须要发送多个异步请求,而且每一个请求之间须要相互依赖时,就会产生回调地狱。微信小程序

前段时间写了一个天气微信小程序 Natsuha,它获取天气的逻辑大体以下(固然真实场景复杂的多)。api

  • 首先要获取用户的经纬度 (接口 A)

  • 根据经纬度反查城市 (接口 B)

  • 根据城市拿到相应的天气信息 (接口 C)

按照回调的方式去处理这个逻辑,大体会写成下面的样子:

ajax(A, () => {
  // 获取经纬度
  ajax(B, () => {
    // 根据经纬度反查城市
    ajax(C, () => {
      // 根据城市获取天气信息
    });
  });
});
复制代码

看起来很丑陋不是吗?相信你们对回调函数的缺点大体都了解,这里就不展开,只作个总结。

  • 代码逻辑书写顺序与执行顺序不一致,不利于阅读与维护。

  • 异步操做的顺序变动时,须要大规模的代码重构。

  • 回调函数基本都是匿名函数,bug 追踪困难。

  • 回调函数是被第三方库代码(如上例中的 ajax )而非本身的业务代码所调用的,形成了控制反转(IoC)。

简单谈一谈 控制反转,《你不知道的 JavaScript (中卷)》把回调函数的最大缺点归结为 信任问题。例子中 ajax 是一个三方的函数(你彻底能够把它想象成 jQuery 的 $.ajax()),咱们把本身的业务逻辑,也就是将回调函数 交给了 ajax 去处理。但 ajax 对咱们来讲仅仅是一个黑盒,若是 ajax 自己有缺陷的话,咱们的回调函数就处于危险之中,这也就是所谓的“信任问题”。

不过 Promise 的出现解决了这些缺点,它可以把控制反转再反转回来。这样的话,咱们能够不把本身程序的传给第三方,而是让第三方给咱们提供了解其任务什么时候结束的能力,进而由咱们本身的代码来决定下一步作什么。

何为 Promise

《你不知道的 JavaScript (中卷)》举了一个例子:

我在快餐店点了一个汉堡,并支付了 1.07 美金。这意味着我对某个值(汉堡)发出了请求。

接着收银员给我一张 取餐单据,它保证了我最终会获得汉堡,所以 取餐单据 就是一个 承诺

在等待取餐的过程当中,我能够作点其余的事情,好比刷刷推特,看看 996.icu 今天又涨了多少 star。之因此我可作点儿其余的事情,是由于 取餐单据 表明了我 将来的 汉堡。它在某种意义上已经成了汉堡的 占位符。从本质上来说,这个 占位符 使得这个值再也不依赖时间,这是一个 将来值

终于,我听到服务员在喊 250号前来取餐,我就能够拿着 取餐单据 换个人汉堡了。

可是可能还有另外一种结果,在我去取餐时,服务员充满抱歉的告诉我汉堡已经售罄了,除了愤怒,咱们还能够看到 将来值 可能成功,也可能失败。

Promise 基础知识

Promise 的生命周期

每一个 Promise 都会经历一个短暂的生命周期:先是处于 进行中 (pending),此时操做还没有完成,所以它也是 未处理 (unsettled) 的;一旦异步操做执行结束,Promise 变成 已处理 (settled) 状态,此时它会进入到如下两个状态中的其中一个:

  • Fulfilled:Promise 异步操做成功完成

  • Rejected:因为程序错误或其余缘由,异步操做未能成功完成

Promise 构造函数

Promise 自己是一个构造函数,它接收一个叫作 executor 的函数,该函数会被传递两个名为 resolve()reject() 的函数做为参数。resolve() 函数在执行器成功时被调用,而 reject() 在执行器操做失败后被调用。看下面这个例子。

const fs = require('fs');

const promise = path =>
  // 执行器接收 resolve() 和 reject() 做为参数
  new Promise((resolve, reject) => {
    fs.readFile(__dirname + '/' + path, 'utf-8', (err, data) => {
      if (err) {
        // 失败时调用 reject()
        reject(err);
        return;
      }
      // 成功时时调用 resolve()
      resolve(data);
    });
  });
复制代码

Promise 的 then 方法

then() 方法接收两个函数做为参数,第一个做为 完成 时的回调,第二个做为 拒绝 时的回调。两个参数均为可选,所以你能够只监听 完成,或者只监听 拒绝。其中当第一个参数为 null,第二个参数为回调函数时,它意味着监听 拒绝。在实际应用中,完成拒绝 都应当被监听。

const promise = new Promise((resolve, reject) => {
  resolve('success');
});

// 监听完成和拒绝
promise.then(
  res => {
    // 完成
    console.log(res);
  },
  e => {
    // 拒绝
    console.log(e);
  },
);

// 只监听完成
promise.then(res => {
  console.log(res);
});

// 第一个参数为 null 时意味着拒绝
promise.then(null, res => {
  // 完成
  console.log(res);
});
复制代码

Promise 还有两个方法分别是 catch()finally(),前者用于监听 拒绝,后者不管成功失败都会被执行到。链式调用显然可读性更高,因此咱们推荐下面这种写法。

promise
  .then(res => {
    console.log(res);
  })
  .catch(e => {
    console.log(e);
  })
  .finally(() => {
    console.log('不管成功失败都会执行这句');
  });
复制代码

Promise 链式调用

每次调用 then() 或 catch() 方法时都会 建立并返回一个新的 Promise,只有当前一个 Promise 完成或被拒绝后,下一个才会被解决。

看下面这个例子,p.then() 完成后返回第二个 Promise,接着又调用了它的 then() 方法,也就是说只有当第一个 Promise 被解决以后才会调用第二个 then() 方法的 then()

let p = new Promise((resolve, reject) => {
  resolve(42);
});

p.then(value => {
  console.log(value); // 42
}).then(() => {
  console.log('能够执行到'); // '能够执行到'
});
复制代码

将上述示例拆开,看起来是这样的。调用 p1.then() 的结果被存储到 p2 中,p2.then() 被调用来添加最终的 then()

let p1 = new Promise((resolve, reject) => {
  resolve(42);
});

let p2 = p1.then(value => {
  console.log(value);
});

p2.then(() => {
  console.log('能够执行到');
});
复制代码

咱们经过一个实例来看一下链式调用。下面是获取城市天气的场景:咱们首先须要调用 getCity 接口来获取 城市id,接着调用 getWeatherById/城市id 来获取城市的天气信息。首先用 Promise 封装一个原生 Ajax。(敲黑板,面试可能要求手写)

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject) {
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open('GET', url);
    client.onreadystatechange = handler;
    client.responseType = 'json';
    client.setRequestHeader('Accept', 'application/json');
    client.send();
  });

  return promise;
};

const baseUrl = 'https://5cb322936ce9ce00145bf070.mockapi.io/api/v1';
复制代码

经过链式调用来请求数据,最后别忘了捕获错误。

getJSON(`${baseUrl}/getCity`)
  .then(value => getJSON(`${baseUrl}/getWeatherById/${value.cityId}`))
  .then(value => console.log(value))
  .catch(e => {
    console.log(e);
  });
复制代码

捕获错误

当 then() 方法或者 catch() 方法抛出错误时,链式调用的下一个 Promise 中的 catch() 方法能够经过 catch() 接收这个错误。侧面来说,异常不必定只发生在 Promise 中,还有可能发生在 then() 或者 catch() 中。

let p1 = new Promise((resolve, reject) => {
  resolve(42);
});

p1.then(value => {
  throw new Error(' `then()` 错误');
}).catch(e => {
  console.log(e.message); // ' `then()` 错误'
});
复制代码

不只 then() 能够抛出异常,catch() 也能够抛出的异常,且能够被下一个 catch() 捕获。所以,不管如何都应该在 Promise 链的末尾留一个 catch() ,以保证可以正确处理全部可能发生的错误。看下面这个例子。

let p1 = new Promise((resolve, reject) => {
  throw new Error('执行器错误');
});

p1.catch(e => {
  console.log(e.message); // '执行器错误'
  throw new Error(' `catch()` 错误');
}).catch(e => {
  console.log(e.message); // ' `catch()` 错误'
});
复制代码

Promise 链的返回值

Promise 链的一个重要特性是能从一个 Promise 传递数据给下一个 Promise,经过完成处理函数的返回值,来将数据沿着一个链传递下去。咱们看下面这个例子。

function task() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('task');
    }, 1000);
  });
}

task()
  .then(res => {
    console.log(res);
    return 'taskB';
  })
  .then(res => {
    console.log(res);
    return 'taskC';
  })
  .then(res => {
    console.log(res);
    throw new Error();
  })
  .catch(e => {
    console.log(e);
    return 'taskD';
  })
  .then(res => {
    console.log(res);
  });
复制代码

Jietu20190415-172853.jpg

运行结果如上图所示。咱们知道,每次调用 then() 或者 catch() 都会返回一个新的 Promise 实例,经过指定处理函数的返回值,能够沿着一个链继续传递数据。

所以第一个 then() 将 'taskB' 做为下一个 then() 的参数传递下去,一样第二个 then() 将 'taskC' 做为第三个 then() 的参数传递下去。

而第三个 then() 里面抛出一个异常,上面说处处理函数中的抛出异常必定会被后面的拒绝处理函数捕获,因此 catch() 里可以打印出上一个 then() 的错误。

别忘了 catch() 返回 'taskD' 也能够被最后一个 then() 捕获。

其余构造方法

Promise.resolve() 和 Promise.reject()

Promise.resolve() 和 Promise.reject() 相似于快捷方式,用来建立一个 已完成已被拒绝 的 promise。此外,Promise.resolve() 还能接受非 Promise 的 thenable 的做为参数,也就是所谓 拥有 then 方法的对象

// p1 和 p2 等价
const p1 = new Promise((resolve, reject) => {
  reject('Oops');
});

const p2 = Promise.reject('Oops');

// p3 和 p4 等价
const p3 = new Promise((resolve, reject) => {
  resolve('Oops');
});

const p4 = Promise.resolve('Oops');
复制代码

而对于 Promise.resolve(),它还能接收一个非 Promise 的 thenable 做为参数。它能够建立一个已完成的 Promise,也能够建立一个以拒绝的 Promise。

let thenable1 = {
  then(resolve, reject) {
    resolve(1);
  },
};

let p1 = Promise.resolve(thenable1);

p1.then(value => console.log(value)); // 1

let thenable2 = {
  then(resolve, reject) {
    reject(1);
  },
};

let p2 = Promise.resolve(thenable2);

p2.catch(reason => console.log(reason)); // 1
复制代码

Promise.all()

该方法接收单个迭代对象(最多见的就是数组)做为参数,并返回一个 Promise。这个可迭代对象的元素都是 Promise,只有在它们都完成后,所返回的 Promise 才会被完成。

  • 当全部的 Promise 均为完成态,将会返回一个包含全部结果的数组。

  • 只要有一个被拒绝,就不会返回数组,只会返回最早被拒绝的那个 Promise 的缘由

let p1 = new Promise((resolve, reject) => {
  resolve(42);
});
let p2 = new Promise((resolve, reject) => {
  reject(43);
});
let p3 = new Promise((resolve, reject) => {
  reject(44);
});

let p4 = new Promise((resolve, reject) => {
  resolve(45);
});

// 所有完成,返回数组
let p5 = Promise.all([p1, p4]);
p5.then(value => console.log(value)); // [42, 45]

// 只要有一个出错,就不会返回数组,且只会返回最早被拒绝的那个 Promise 的缘由
let p6 = Promise.all([p1, p2, p3, p4]);
p6.catch(value => console.log(value)); // 43
复制代码

Promise.race()

该方法一样接收单个迭代对象(最多见的就是数组)做为参数,不一样的是,该方法只要检测到任意一个被解决,该方法就会作出响应。所以一个有趣的例子是把 请求接口 和一个 setTimeout 进行竞逐,若是 setTimeout 先作出响应,就证实这个接口请求超时。

const p = Promise.race([
  fetch('/some-api'),
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('请求超时')), 3000);
  }),
]);

p.then(value => {
  console.log(value);
}).catch(reason => {
  console.log(reason);
});
复制代码

Promise 的局限性

看起来 Promise 很美好,解决了回调函数的种种问题,但它也有本身的局限性。

  • 一旦建立一个 Promise 并为其注册完成/拒绝处理函数,Promise 将没法被取消。

  • 当处于 pending 状态时,你没法得知当前进展到哪一块

  • 由于 Promise 只能被决议一次(完成或拒绝),若是某些事件不断发生,stream 模式会更合适。

  • 若是不设置回调函数,Promise 内部抛出的错误,不会反应到外部。

手撕代码

手撕代码的以前能够参照一下后面的 Promise A+ 规范翻译,最好仍是本身去官网翻译一遍,这样写起来才会驾轻就熟。下面的代码几乎每句都加了注释,而且连接到每一条规范。

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {
  constructor(executor) {
    // state 的初始状态为等待态
    this.state = PENDING;

    // 成功的值 (1.3)
    this.value = undefined;

    // 失败的缘由 (1.5)
    this.reason = undefined;

    // 由于 then 在相同的 promise 能够被调用屡次,因此须要将全部的 onFulfilled 存到数组 (2.2.6)
    this.onResolvedCallbacks = [];

    // 由于 then 在相同的 promise 能够被调用屡次,因此须要将全部的 onRejected 存到数组 (2.2.6)
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有当前是 pending,才可能转换为 fulfilled
      // 而且不能再转换成其余任何状态,且必须拥有一个不可变的值
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        // onFulfilled 回调按原始调用顺序依次执行 (2.2.6.1)
        this.onResolvedCallbacks.forEach(fn => fn());
      }
    };

    const reject = reason => {
      // 只有当前是 pending,才可能转换为 rejected
      // 而且不能再转换成其余任何状态,且必须拥有一个不可变的缘由
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
        // onRejectec 回调按原始调用顺序依次执行 (2.2.6.1)
        this.onRejectedCallbacks.forEach(fn => fn()); // (2.2.6.2)
      }
    };

    // 若 executor 报错,直接执行 reject()
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    // onFulfilled 和 onRejected 都是可选参数 (2.2.1)

    // 若是 onFulfilled 不是函数,则必须将它忽略 (2.2.1.1)
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : value => value;

    // 若是 onRejected 不是函数,则必须将它忽略 (2.2.1.2)
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : err => {
            throw err;
          };

    // 为了作到链式调用,规定每一个 then 方法必须返回一个 promise,称为 promise2
    const promise2 = new Promise((resolve, reject) => {
      // 在 promise 完成后方可调用 onFulfilled (2.2.2)
      if (this.state === FULFILLED) {
        // onFulfilled/onRejected 必须被异步调用,所以咱们用延时函数模拟 (2.2.4)
        setTimeout(() => {
          try {
            // value 做为完成函数的第一个参数 (2.2.2.1)
            // onFulfilled 函数被记作 x (2.2.7.1)
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            // 若是 onFulfilled/onRejected 抛出异常,则 promise2 必须拒绝执行,并返回拒因 e (2.2.7.2)
            reject(e);
          }
        }, 0);
      }

      // 在 promise 被拒绝后方可调用 onRejected (2.2.3)
      if (this.state === REJECTED) {
        // onFulfilled/onRejected 必须被异步调用,所以咱们用延时函数模拟 (2.2.4)
        setTimeout(() => {
          try {
            // reason 做为拒绝函数的第一个参数 (2.2.3.1)
            // onRejected 函数被记作 x (2.2.7.1)
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            // 若是 onFulfilled/onRejected 抛出异常,则 promise2 必须拒绝执行,并返回拒因 e (2.2.7.2)
            reject(e);
          }
        }, 0);
      }

      if (this.state === PENDING) {
        this.onResolvedCallbacks.push(() => {
          // onFulfilled/onRejected 必须被异步调用,所以咱们用延时函数模拟 (2.2.4)
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              // 若是 onFulfilled/onRejected 抛出异常,则 promise2 必须拒绝执行,并返回拒因 e (2.2.7.2)
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          // onFulfilled/onRejected 必须被异步调用,所以咱们用延时函数模拟 (2.2.4)
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              // 若是 onFulfilled/onRejected 抛出异常,则 promise2 必须拒绝执行,并返回拒因 e (2.2.7.2)
              reject(e);
            }
          }, 0);
        });
      }
    });

    // 返回 promise2 (2.2.7)
    return promise2;
  }

  // catch 实际是 then 的语法糖
  catch(fn) {
    return this.then(null, fn);
  }

  finally(fn) {
    return this.then(
      value => Promise.resolve(fn()).then(() => value),
      reason =>
        Promise.resolve(fn()).then(() => {
          throw reason;
        }),
    );
  }
}

const resolvePromise = (promise2, x, resolve, reject) => {
  // 若是 promise 和 x 指向同一个对象,将以 TypeError 做为拒因拒绝执行 promise (2.3.1)
  if (x === promise2) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  // onFulfilled 和 onRejected 只能被调用一次,所以这里加一个 flag 做为判断 (2.2.2.3 & 2.2.3.3)
  let isCalled = false;

  // 若是 x 是一个对象或者是一个函数 (2.3.3)
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // (2.3.3.1)
      const then = x.then;

      // 若是 then 是函数,就以 x 做为 this 调用它 (2.3.3.2 & 2.3.3.3)
      if (typeof then === 'function') {
        // 后面接收两个回调,第一个是成功的回调,第二个是失败的回调 (2.3.3.3)
        then.call(
          x,
          y => {
            if (isCalled) return;
            isCalled = true;
            // 若是 resolvePromise 以 y 为参数被调用,执行 [[Resolve]](promise, y) (2.3.3.3.1)
            resolvePromise(promise2, y, resolve, reject);
          },
          r => {
            if (isCalled) return;
            isCalled = true;
            // 若是 rejectPromise 以 r 为缘由被调用,则以拒因 r 拒绝 promise (2.3.3.3.2)
            reject(r);
          },
        );
      } else {
        // 若是 then 不是个函数,则以 x 为参数执行 promise (2.3.3.4)
        resolve(x);
      }
    } catch (e) {
      if (isCalled) return;
      isCalled = true;
      // 若是取 x.then 报错,则以 e 为拒因拒绝 `promise` (2.3.3.2)
      reject(e);
    }
  }
  // 若是 then 不是个函数或者对象,则以 x 为参数执行 promise (2.3.4)
  else {
    resolve(x);
  }
};

// Promise.resolve
Promise.resolve = function(promises) {
  if (promises instanceof Promise) {
    return promises;
  }
  return new Promise((resolve, reject) => {
    if (promises && promises.then && typeof promises.then === 'function') {
      setTimeout(() => {
        promises.then(resolve, reject);
      });
    } else {
      resolve(promises);
    }
  });
};

// Promise.reject
Promise.reject = reason => new Promise((resolve, reject) => reject(reason));

// Promise.all
Promise.all = promises => {
  return new Promise((resolve, reject) => {
    let resolvedCounter = 0;
    let promiseNum = promises.length;
    let resolvedValues = new Array(promiseNum);
    for (let i = 0; i < promiseNum; i += 1) {
      (i => {
        Promise.resolve(promises[i]).then(
          value => {
            resolvedCounter++;
            resolvedValues[i] = value;
            if (resolvedCounter === promiseNum) {
              return resolve(resolvedValues);
            }
          },
          reason => {
            return reject(reason);
          },
        );
      })(i);
    }
  });
};

//race方法
Promise.race = promises => {
  return new Promise((resolve, reject) => {
    if (promises.length === 0) {
      return;
    } else {
      for (let i = 0, l = promises.length; i < l; i += 1) {
        Promise.resolve(promises[i]).then(
          data => {
            resolve(data);
            return;
          },
          err => {
            reject(err);
            return;
          },
        );
      }
    }
  });
};
复制代码

最后全局安装 yarn global add promises-aplus-tests,插入下面这段代码,而后使用 promises-aplus-tests 该文件的文件名 来验证你手写的 Promise 是否符合 Promises A+ 规范。

Promise.defer = Promise.deferred = function() {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};
module.exports = Promise;
复制代码

Promises/A+ 规范

一个开放、可靠且通用的 JavaScript Promise 标准。由开发者制定,供开发者参考。

promise 表明着一个异步操做的最终结果,与之交互的主要方式是它的 then 方法,该方法注册了两个回调函数,用于接收 promise 最终的值或者失败的缘由。

该规范详细描述了 then 方法的行为,全部遵循 Promises/A+ 规范实现的 promise 都可以本标准做为参照基础来实施。所以,这份规范是很稳定的。虽然 Promises/A+ 组织偶尔会修订这份规范,但大可能是为了处理一些特殊的边界状况。这些改动都是微小且向下兼容的。若是咱们要进行大规模不兼容的更新,咱们必定会在事先进行谨慎地考虑、详尽的探讨和严格的测试。

最后,核心的 Promises/A+ 规范不会提供如何建立、解决和拒绝 promise,而是专一于提供一个通用的 then 方法。上述对于 promises 的操做方法未来在其余规范中可能会说起。

1. 术语

1.1. 'promise' 是一个拥有 then 方法的对象或者函数,且其行为符合此规范。

1.2. 'thenable' 是一个用来定义 then 方法的对象或者函数。

1.3. 'value' 是任何一个合法的 JavaScript 值 (包括 undefined,thenable 或者 promise)

1.4. 'exception' 是一个使用 throw 语句抛出的值

1.5. 'reason' 代表了一个 promise 为何会被拒绝

2. 要求

2.1. Promise 状态

promise 必须是三个状态之一:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。

  • 2.1.1. 当前状态为 pending 时,一个 promise:

    • 2.1.1.1 能够转换成 fulfilled 或者 rejected 状态
  • 2.1.2. 当前状态为 fulfilled 时,一个 promise:

    • 2.1.2.1 不能再转换成其余任何状态

    • 2.1.2.2 必须拥有一个不可变的值

  • 2.1.3. 当前状态为 rejected 时,一个 promise:

    • 2.1.3.1 不能再转换成其余任何状态

    • 2.1.3.2 必须拥有一个不可变的缘由

这里的不可变指的是恒等(便可用 === 判断相等),而不是意味着更深层次的不可变。(即当 value 或者 reason 为引用类型时,只要求引用地址相等便可,但属性值能够被修改)

2.2. then 方法

promise 必须提供一个 then 方法以访问它当前或最终的值或被拒绝的缘由。

一个 promise 的 then 方法接收两个参数:

promise.then(onFulfilled, onRejected);
复制代码
  • 2.2.1 onFulfilledonRejected 都是可选参数。

    • 2.2.1.1 若是 onFulfilled 不是个函数,它将被忽略

    • 2.2.1.2 若是 onRejected 不是个函数,它将被忽略

  • 2.2.2 若是 onFulfilled 是一个函数:

    • 2.2.2.1 它必须在 promise 完成式后被调用,而且以 promise 的值做为它的第一个参数。

    • 2.2.2.2 在 promise 未完成前不可调用

    • 2.2.2.3 此函数仅可调用一次

  • 2.2.3 若是 onRejected 是一个函数:

    • 2.2.3.1 它必须在 promise 被拒绝后被调用,而且以 promise 的缘由做为它的第一个参数。

    • 2.2.3.2 在 promise 未被拒绝前不可调用

    • 2.2.3.3 此函数仅可调用一次

  • 2.2.4 onFulfilledonRejected 只有在 执行上下文 堆栈仅包含平台代码时才可被调用。[1]

  • 2.2.5 onFulfilledonRejected 必须被做为函数调用 (即没有 this 值)。[2]

  • 2.2.6 then 在相同的 promise 能够被调用屡次

    • 2.2.6.1 当 promise 是完成态, 全部相应的 onFulfilled 回调必须按其原始调用的顺序执行。

    • 2.2.6.2 当 promise 是拒绝态,全部相应的 onRejected 回调必须按其原始调用的顺序执行。

  • 2.2.7 每一个 then 方法必须返回一个 promise [3]

    promise2 = promise1.then(onFulfilled, onRejected);
    复制代码
    • 2.2.7.1 若是 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)

    • 2.2.7.2 若是 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e

    • 2.2.7.3 若是 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值

    • 2.2.7.4 若是 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的拒因

2.3. Promise 解决过程

Promise 解决过程是一个抽象的操做,它接收一个 promise 和一个值,咱们能够表示为 [[Resolve]](promise, x),若是 x 是一个 thenable 的对象,解决程序将试图接受 x 的状态,不然用 x 的值来执行 promise

这种对 thenales 的处理使得 promise 的实现更加有普适性,只要它暴露出一个兼容 Promises/A+ 规范的 then 方法。它还容许让遵循 Promise/A+ 规范的实现和不太规范但可用的实现良好共存。

为了运行 [[Resolve]](promise, x),要执行下面的步骤:

  • 2.3.1 若是 promisex 指向同一个对象,将以 TypeError 做为拒因拒绝执行 promise

  • 2.3.2 若是 x 是一个 promise,那么将 promise 将接受它的状态 [4]

    • 2.3.2.1 若是 x 是等待态,promise 必须保留等待状态直到 x 被完成或者被拒绝。

    • 2.3.2.2 若是 x 是完成态,用相同的值执行 promise

    • 2.3.2.3 若是 x 是拒态,用相同的缘由拒绝 promise

  • 2.3.3 若是 x 是一个对象或者是一个函数,

    • 2.3.3.1 把 x.then 赋值给 then[5]

    • 2.3.3.2 若是取 x.then 的值时抛出错误 e,则以 e 为拒因拒绝 promise

    • 2.3.3.3 若是 then 是函数,将 x 做为函数的做用域 this 来调用它。传递两个回调函数做为参数,第一个参数叫作 resolvePromise,第二个参数叫作 rejectPromise:

      • 2.3.3.3.1 若是 resolvePromisey 为参数被调用,执行 [[Resolve]](promise, y)

      • 2.3.3.3.2 若是 rejectPromiser 为缘由被调用,则以拒因 r 拒绝 promise

      • 2.3.3.3.3 若是 resolvePromiserejectPromise 都被调用,或者被同一参数调用了屡次,则优先采用首次调用并忽略剩下的调用。

      • 2.3.3.3.4 若是调用 then 抛出一个异常 e

        • 2.3.3.3.4.1 若是 resolvePromiserejectPromise 都被调用,则忽略掉它

        • 2.3.3.3.4.2 不然,以 e 为拒因拒绝这个 promise

    • 2.3.3.4 若是 then 不是个函数,则以 x 为参数执行 promise

  • 2.3.4 若是 then 不是个函数或者对象,则以 x 为参数执行 promise

若是一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为拒因来拒绝 promise [6]

3. 注释


  1. 这里的“平台代码”意味着引擎,环境和 promise 实施代码,在实践中要确保 onFulfilledonRejected 异步执行,且应该在 then 方法被调用的那一轮事件循环以后的新执行栈中执行。这个事件队列能够采用“宏任务(macro-task)”机制,相似于 setTimeOut 或者 setImmediate,也可使用“微任务(micro-task)”机制来实现,相似于 MutationObserverprocess.nextTick。由于 promise 实现被认为是平台代码,因此它自己可能包含一个任务调度队列或跳板,在其中调用处理程序。 ↩︎

  2. 在严格模式下 thisundefined,而在非严格模式中,this 为全局对象。 ↩︎

  3. 代码实如今知足全部要求的状况下能够容许 promise2 === promise1 。每一个实现都要文档说明其是否容许以及在何种条件下容许 promise2 === promise1↩︎

  4. 整体来讲,若是 x 符合当前实现,咱们才认为它是真正的 promise 。这一规则容许那些特例实现接受符合已知要求的 Promises 状态。 ↩︎

  5. 这步咱们先是存储了一个指向 x.then 的引用,而后测试并调用该引用,以免屡次访问 x.then 属性。这种预防措施确保了该属性的一致性,由于其值可能在检索调用时被改变。 ↩︎

  6. 实现不该该对 thenable 链的深度设限,并假定超出本限制的递归就是无限循环。只有真正的循环递归才应能致使 TypeError 异常;若是一条无限长的链上 thenable 均不相同,那么递归下去永远是正确的行为。 ↩︎

相关文章
相关标签/搜索