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>: undefined
setTimeout(console.log, 0, Promise.resolve(3));
// Promise <resolved>: 3
// Additional arguments are ignored
setTimeout(console.log, 0, Promise.resolve(4, 5, 6));
// Promise <resolved>: 4
let p = Promise.resolve(7);
setTimeout(console.log, 0, p === Promise.resolve(p));
// true
setTimeout(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>: 3
p.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 recommended
p1.then('gobbeltygook');
// Canonical form of explicit onResolved handler skipping
p2.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 passthrough
let p2 = p1.then();
setTimeout(console.log, 0, p2); // Promise <resolved>: foo
// These are equivalent
let p3 = p1.then(() => undefined);
let p4 = p1.then(() => {});
let p5 = p1.then(() => Promise.resolve());
setTimeout(console.log, 0, p3); // Promise <resolved>: undefined
setTimeout(console.log, 0, p4); // Promise <resolved>: undefined
setTimeout(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>: bar
setTimeout(console.log, 0, p7); // Promise <resolved>: bar
// Promise.resolve() preserves the returned promise
let p8 = p1.then(() => new Promise(() => {}));
let p9 = p1.then(() => Promise.reject());
// Uncaught (in promise): undefined
setTimeout(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) baz
setTimeout(console.log, 0, p10); // Promise <rejected> baz
PromiseThenExample03.js
...
let p11 = p1.then(() => Error('qux'));
setTimeout(console.log, 0, p11); // Promise <resolved>: Error: qux
PromiseThenExample03.js