1. Promise/A+规范
2.Promise基础
空函数执行
let p = new Promise(() => {});setTimeout(console.log, 0, p); // Promise <pending>
2.1. Promise状态
期约的状态是私有的,不能直接通过 JavaScript 检测到。
- 待定(pending)
- 兑现(fulfilled)
- 拒绝(rejected)
Promise一旦fulfilled或者rejected就不能逆转
2.2. 解决值、拒绝理由及期约用例
2.3. 通过执行函数控制期约状态
由于期约的状态是私有的,所以只能在内部进行操作。内部操作在期约的执行器函数中完成。执行器函数主要有两项职责:初始化期约的异步行为和控制状态的最终转换。其中,控制期约状态的转换是通过调用它的两个函数参数实现的。这两个函数参数通常都命名为resolve()和 reject()。调用resolve()会把状态切换为兑现,调用 reject()会把状态切换为拒绝。另外,调用 reject()也会抛出错误(后面会讨论这个错误)。
let p1 = new Promise((resolve, reject) => resolve());setTimeout(console.log, 0, p1); // Promise <resolved>let p2 = new Promise((resolve, reject) => reject());setTimeout(console.log, 0, p2); // Promise <rejected>// Uncaught error (in promise)
在前面的例子中,并没有什么异步操作,因为在初始化期约时,执行器函数已经改变了每个期约的
状态。这里的关键在于,执行器函数是同步执行的。这是因为执行器函数是期约的初始化程序。通过下
面的例子可以看出上面代码的执行顺序:
new Promise(() => setTimeout(console.log, 0, 'executor'));setTimeout(console.log, 0, 'promise initialized');// executor// promise initialized
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000));// When this console.log executes, the timeout callback has not yet executed:setTimeout(console.log, 0, p); // Promise <pending>
example4
let p = new Promise((resolve, reject) => {resolve();reject(); // No effect});setTimeout(console.log, 0, p); // Promise <resolved>
let p = new Promise((resolve, reject) => {setTimeout(reject, 10000); // After 10 seconds, invoke reject()// Do executor things});setTimeout(console.log, 0, p); // Promise <pending>setTimeout(console.log, 11000, p); // Check state after 11 seconds// (After 10 seconds) Uncaught error// (After 11 seconds) Promise <rejected>
2.4 Promise.resolve()
期约并非一开始就必须处于待定状态,然后通过执行器函数才能转换为落定状态。通过调用
Promise.resolve()静态方法,可以实例化一个解决的期约。下面两个期约实例实际上是一样的:
let p1 = new Promise((resolve, reject) => resolve());let p2 = Promise.resolve();
这个解决的期约的值对应着传给 Promise.resolve()的第一个参数。使用这个静态方法,实际上
可以把任何值都转换为一个期约:
setTimeout(console.log, 0, Promise.resolve());// Promise <resolved>: undefinedsetTimeout(console.log, 0, Promise.resolve(3));// Promise <resolved>: 3// Additional arguments are ignoredsetTimeout(console.log, 0, Promise.resolve(4, 5, 6));// Promise <resolved>: 4

let p = Promise.resolve(7);setTimeout(console.log, 0, p === Promise.resolve(p));// truesetTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p)));// true
let p = new Promise(() => {});setTimeout(console.log, 0, p); // Promise <pending>setTimeout(console.log, 0, Promise.resolve(p)); // Promise <pending>setTimeout(console.log, 0, p === Promise.resolve(p)); // true
2.5 Promise.reject()
与 Promise.resolve()类似,Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误
(这个错误不能通过 try/catch 捕获,而只能通过拒绝处理程序捕获)。下面的两个期约实例实际上是
一样的:
let p1 = new Promise((resolve, reject) => reject());let p2 = Promise.reject();
let p = Promise.reject(3);setTimeout(console.log, 0, p); // Promise <rejected>: 3p.then(null, (e) => setTimeout(console.log, 0, e)); // 3
2.6 同步/异步执行的二元性
try {throw new Error('foo');} catch(e) {console.log(e); // Error: foo}try {Promise.reject(new Error('bar'));} catch(e) {console.log(e);}// Uncaught (in promise) Error: bar// ExecutionDualityExample01.js
第一个 try/catch 抛出并捕获了错误,第二个 try/catch 抛出错误却没有捕获到。乍一看这可能
有点违反直觉,因为代码中确实是同步创建了一个拒绝的期约实例,而这个实例也抛出了包含拒绝理由
的错误。这里的同步代码之所以没有捕获期约抛出的错误,是因为它没有通过异步模式捕获错误。从这
里就可以看出期约真正的异步特性:它们是同步对象(在同步执行模式中使用),但也是异步执行模式
的媒介。
在前面的例子中,拒绝期约的错误并没有抛到执行同步代码的线程里,而是通过浏览器异步消息队
列来处理的。因此,try/catch 块并不能捕获该错误。代码一旦开始以异步模式执行,则唯一与之交互
的方式就是使用异步结构——更具体地说,就是期约的方法。
3. Promise实例方法
3.1 实现Thenable接口
class MyThenable {then() {}}
3.2 Promise.prototype.then()
function onResolved(id) {setTimeout(console.log, 0, id, 'resolved');}function onRejected(id) {setTimeout(console.log, 0, id, 'rejected');}let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000));let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));p1.then(() => onResolved('p1'),() => onRejected('p1'));p2.then(() => onResolved('p2'),() => onRejected('p2'));// (after 3s)// p1 resolved// p2 rejected// PromiseThenExample01.js
因为期约只能转换为最终状态一次,所以这两个操作一定是互斥的。
如前所述,两个处理程序参数都是可选的。而且,传给 then()的任何非函数类型的参数都会被静
默忽略。如果想只提供 onRejected 参数,那就要在 onResolved 参数的位置上传入 undefined。这
样有助于避免在内存中创建多余的对象,对期待函数参数的类型系统也是一个交代。
function onResolved(id) {setTimeout(console.log, 0, id, 'resolved');}function onRejected(id) {setTimeout(console.log, 0, id, 'rejected');}let p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000));let p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));// Non-function handlers are silently ignored, not recommendedp1.then('gobbeltygook');// Canonical form of explicit onResolved handler skippingp2.then(null, () => onRejected('p2'));// p2 rejected (after 3s)// PromiseThenExample02.js
let p1 = new Promise(() => {});let p2 = p1.then();setTimeout(console.log, 0, p1); // Promise <pending>setTimeout(console.log, 0, p2); // Promise <pending>setTimeout(console.log, 0, p1 === p2); // false
这个新期约实例基于 onResovled 处理程序的返回值构建。换句话说,该处理程序的返回值会通过
Promise.resolve()包装来生成新期约。如果没有提供这个处理程序,则 Promise.resolve()就会
包装上一个期约解决之后的值。如果没有显式的返回语句,则 Promise.resolve()会包装默认的返回
值 undefined。
let p1 = Promise.resolve('foo');// Calling then() with no handler function acts as a passthroughlet p2 = p1.then();setTimeout(console.log, 0, p2); // Promise <resolved>: foo// These are equivalentlet p3 = p1.then(() => undefined);let p4 = p1.then(() => {});let p5 = p1.then(() => Promise.resolve());setTimeout(console.log, 0, p3); // Promise <resolved>: undefinedsetTimeout(console.log, 0, p4); // Promise <resolved>: undefinedsetTimeout(console.log, 0, p5); // Promise <resolved>: undefined// PromiseThenExample03.js
...// These are equivalent:let p6 = p1.then(() => 'bar');let p7 = p1.then(() => Promise.resolve('bar'));setTimeout(console.log, 0, p6); // Promise <resolved>: barsetTimeout(console.log, 0, p7); // Promise <resolved>: bar// Promise.resolve() preserves the returned promiselet p8 = p1.then(() => new Promise(() => {}));let p9 = p1.then(() => Promise.reject());// Uncaught (in promise): undefinedsetTimeout(console.log, 0, p8); // Promise <pending>setTimeout(console.log, 0, p9); // Promise <rejected>: undefined// PromiseThenExample03.js
...let p10 = p1.then(() => { throw 'baz'; });// Uncaught (in promise) bazsetTimeout(console.log, 0, p10); // Promise <rejected> bazPromiseThenExample03.js
...let p11 = p1.then(() => Error('qux'));setTimeout(console.log, 0, p11); // Promise <resolved>: Error: quxPromiseThenExample03.js
