期约的实例方法

  • 期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁
  • 这些方法可以访问异步操作返回的数据,处理期约成功和失败的结果,连续对期约求值,或者添加只有期约进入终止状态时才会执行的代码。

1、实现Thenable接口

  • then()方法被认为实现了Thenable接口:
1
2
3
class MyThenable { 
then() {}
}

2、Promise.prototype.then()

  • Promise.prototype.then()是为期约实例添加处理程序的主要方法。
  • 接收最多两个参数:onResolved处理程序onRejected处理程序
  • 这两个参数都是可选的,如果提供的话,则会在期约分别进入“兑现”和“拒绝”状态时执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'));
//(3 秒后)
// p1 resolved
// p2 rejected
  • 因为期约只能转换为最终状态一次,所以这两个操作一定是互斥的。
  • 如果想只提供onRejected参数,那就要在onResolved参数的位置上传入undefined。这样有助于避免在内存中创建多余的对象,对期待函数参数的类型系统也是一个交代。
  • Promise.prototype.then ()方法返回一个新的期约实例
1
2
3
4
5
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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let p1 = Promise.resolve('foo'); 
// 若调用 then()时不传处理程序,则原样向后传
let p2 = p1.then();
setTimeout(console.log, 0, p2); // Promise <resolved>: foo
// 这些都一样
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
// 如果有显式的返回值,则 Promise.resolve()会包装这个值:
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()保留返回的期约
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
//抛出异常会返回拒绝的期约:
let p10 = p1.then(() => { throw 'baz'; });
// Uncaught (in promise) baz
setTimeout(console.log, 0, p10); // Promise <rejected> baz
//返回错误值不会触发上面的ધ绝行为,而会把错误对象包装在一个解决的期约中
let p11 = p1.then(() => Error('qux'));
setTimeout(console.log, 0, p11); // Promise <resolved>: Error: qux
  • onRejected处理程序也与之类似: onRejected 处理程序返回的值也会被Promise.resolve()包装。
  • 拒绝处理程序在捕获错误后不抛出异常是符合期约的行为,应该返回一个解决期约。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let p1 = Promise.reject('foo'); 
// 调用 then()时不传处理程序则原样向后传
let p2 = p1.then();
// Uncaught (in promise) foo
setTimeout(console.log, 0, p2); // Promise <rejected>: foo
// 这些都一样
let p3 = p1.then(null, () => undefined);
let p4 = p1.then(null, () => {});
let p5 = p1.then(null, () => 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
// 这些都一样
let p6 = p1.then(null, () => 'bar');
let p7 = p1.then(null, () => Promise.resolve('bar'));
setTimeout(console.log, 0, p6); // Promise <resolved>: bar
setTimeout(console.log, 0, p7); // Promise <resolved>: bar
// Promise.resolve()保留返回的期约
let p8 = p1.then(null, () => new Promise(() => {}));
let p9 = p1.then(null, () => Promise.reject());
// Uncaught (in promise): undefined
setTimeout(console.log, 0, p8); // Promise <pending>
setTimeout(console.log, 0, p9); // Promise <rejected>: undefined
let p10 = p1.then(null, () => { throw 'baz'; });
// Uncaught (in promise) baz
setTimeout(console.log, 0, p10); // Promise <rejected>: baz
let p11 = p1.then(null, () => Error('qux'));
setTimeout(console.log, 0, p11); // Promise <resolved>: Error: qux

3、Promise.prototype.catch()

  • 该方法用于给期约添加拒绝处理程序。
  • 只接收一个参数:onRejected处理程序。
  • 这个方法就是一个语法糖,调用它就相当于调用Promise.prototype.then(null, onRejected)。
  • Promise.prototype.catch()返回一个新的期约实例:
1
2
3
4
5
6
7
8
9
10
11
12
let p = Promise.reject(); 
let onRejected = function(e) {
setTimeout(console.log, 0, 'rejected');
};
// 这两种添加拒绝处理程序的方式是一样的:
p.then(null, onRejected); // rejected
p.catch(onRejected); // rejected
let p1 = new Promise(() => {});
let p2 = p1.catch();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false

4、Promise.prototype.finally()

  • 方法用于给期约添加onFinally处理程序,这个处理程序在期约转换为解决或拒绝状态时都会执行。
  • 可以避免onResolved和onRejected处理程序中出现冗余代码。
  • onFinally 处理程序没有办法知道期约的状态是解决还是拒绝,故这个方法主要用于添加清理代码
  • 方法返回一个新的期约实例。
1
2
3
4
5
6
7
let p1 = Promise.resolve(); 
let p2 = Promise.reject();
let onFinally = function() {
setTimeout(console.log, 0, 'Finally!')
}
p1.finally(onFinally); // Finally
p2.finally(onFinally); // Finally
1
2
3
4
5
let p1 = new Promise(() => {}); 
let p2 = p1.finally();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false
  • onFinally被设计为一个状态无关的方法,所以在大多数情况下它将表现为父期约的传递。
  • 如果返回的是一个待定的期约,或者onFinally处理程序抛出了错误(显式抛出或返回了一个拒绝期约),则会返回相应的期约(待定或拒绝)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let p1 = Promise.resolve('foo'); 
// 这里都会原样后传
let p2 = p1.finally();
let p3 = p1.finally(() => undefined);
let p4 = p1.finally(() => {});
let p5 = p1.finally(() => Promise.resolve());
let p6 = p1.finally(() => 'bar');
let p7 = p1.finally(() => Promise.resolve('bar'));
let p8 = p1.finally(() => Error('qux'));
setTimeout(console.log, 0, p2); // Promise <resolved>: foo
setTimeout(console.log, 0, p3); // Promise <resolved>: foo
setTimeout(console.log, 0, p4); // Promise <resolved>: foo
setTimeout(console.log, 0, p5); // Promise <resolved>: foo
setTimeout(console.log, 0, p6); // Promise <resolved>: foo
setTimeout(console.log, 0, p7); // Promise <resolved>: foo
setTimeout(console.log, 0, p8); // Promise <resolved>: foo
// Promise.resolve()保留返回的期约
let p9 = p1.finally(() => new Promise(() => {}));
let p10 = p1.finally(() => Promise.reject());
// Uncaught (in promise): undefined
setTimeout(console.log, 0, p9); // Promise <pending>
setTimeout(console.log, 0, p10); // Promise <rejected>: undefined
let p11 = p1.finally(() => { throw 'baz'; });
// Uncaught (in promise) baz
setTimeout(console.log, 0, p11); // Promise <rejected>: baz

非重入期约方法

  • 非重入:当期约进入落定状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。跟在添加这个处理程序的代码之后的同步代码一定会在处理程序之前先执行。即使期约一开始就是与附加处理程序关联的状态,执行顺序也是这样的。
1
2
3
4
5
6
7
8
9
10
// 创建解决的期约
let p = Promise.resolve();
// 添加解决处理程序
// 直觉上,这个处理程序会等期约一解决就执行
p.then(() => console.log('onResolved handler'));
// 同步输出,证明 then()已经返回
console.log('then() returns');
// 实际的输出:
// then() returns
// onResolved handler
  • 先添加处理程序后解决期约也是一样的。如果添加处理程序后,同步代码才改变期约状态,那么处理程序仍然会基于该状态变化表现出非重入特性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let synchronousResolve; 
// 创建一个期约并将解决函数保存在一个局部变量中
let p = new Promise((resolve) => {
synchronousResolve = function() {
console.log('1: invoking resolve()');
resolve();
console.log('2: resolve() returns');
};
});
p.then(() => console.log('4: then() handler executes'));
synchronousResolve();
console.log('3: synchronousResolve() returns');
// 实际的输出:
// 1: invoking resolve()
// 2: resolve() returns
// 3: synchronousResolve() returns
// 4: then() handler executes
  • 非重人适用于onResolved/onRejected处理程序、catch()处理程序和finally()处理程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let p1 = Promise.resolve(); 
p1.then(() => console.log('p1.then() onResolved'));
console.log('p1.then() returns');
let p2 = Promise.reject();
p2.then(null, () => console.log('p2.then() onRejected'));
console.log('p2.then() returns');
let p3 = Promise.reject();
p3.catch(() => console.log('p3.catch() onRejected'));
console.log('p3.catch() returns');
let p4 = Promise.resolve();
p4.finally(() => console.log('p4.finally() onFinally'));
console.log('p4.finally() returns');
// p1.then() returns
// p2.then() returns
// p3.catch() returns
// p4.finally() returns
// p1.then() onResolved
// p2.then() onRejected
// p3.catch() onRejected
// p4.finally() onFinally

6、邻近处理程序的执行顺序

  • 如果给期约添加了多个处理程序,当期约状态变化时,相关处理程序会按照添加它们的顺序依次执行。
  • 无论是then()、catch()还是 finally()添加的处理程序都是如此。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let p1 = Promise.resolve(); 
let p2 = Promise.reject();
p1.then(() => setTimeout(console.log, 0, 1));
p1.then(() => setTimeout(console.log, 0, 2));
// 1
// 2
p2.then(null, () => setTimeout(console.log, 0, 3));
p2.then(null, () => setTimeout(console.log, 0, 4));
// 3
// 4
p2.catch(() => setTimeout(console.log, 0, 5));
p2.catch(() => setTimeout(console.log, 0, 6));
// 5
// 6
p1.finally(() => setTimeout(console.log, 0, 7));
p1.finally(() => setTimeout(console.log, 0, 8));
// 7
// 8

7、传递解决值和拒绝理由

  • 到了落定状态后,期约会提供其解决值(如果兑现)或其拒绝理由(如果拒绝)给相关状态的处理程序。拿到返回值后,就可以进一步对这个值进行操作。
  • 在执行函数中,解决的值和拒绝的理由是分别作为resolve() 和reject() 的第一个参数往后传的。然后,这些值又会传给它们各自的处理程序,作为onResolved或onRejected处理程序的唯一参数。
1
2
3
4
let p1 = new Promise((resolve, reject) => resolve('foo')); 
p1.then((value) => console.log(value)); // foo
let p2 = new Promise((resolve, reject) => reject('bar'));
p2.catch((reason) => console.log(reason)); // bar
  • Promise.resolve()和Promise.reject()在被调用时就会接收解决值和拒绝理由。同样地,它们返回的期约也会像执行器一样把这些值传给onResolved或onRejected处理程序。
1
2
3
4
let p1 = Promise.resolve('foo'); 
p1.then((value) => console.log(value)); // foo
let p2 = Promise.reject('bar');
p2.catch((reason) => console.log(reason)); // bar

拒绝期约与拒绝错误处理

  • 拒绝期约类似于throw()表达式,因为它们都代表一种程序状态,即需要中断或者特殊处理
  • 在期约的执行函数或处理程序中抛出错误会导致拒绝,对应的错误对象会成为拒绝的理由。
1
2
3
4
5
6
7
8
let p1 = new Promise((resolve, reject) => reject(Error('foo'))); 
let p2 = new Promise((resolve, reject) => { throw Error('foo'); });
let p3 = Promise.resolve().then(() => { throw Error('foo'); });
let p4 = Promise.reject(Error('foo'));
setTimeout(console.log, 0, p1); // Promise <rejected>: Error: foo
setTimeout(console.log, 0, p2); // Promise <rejected>: Error: foo
setTimeout(console.log, 0, p3); // Promise <rejected>: Error: foo
setTimeout(console.log, 0, p4); // Promise <rejected>: Error: foo
  • 期约可以以任何理由拒绝,包括undefined, 但最好统一使用错误对象。这样做主要是因为创建错误对象可以让浏览器捕获错误对象中的栈追踪信息,而这些信息对调试是非常关键的。
  • 在期约中抛出错误时,因为错误实际上是从消息队列中异步抛出的,所以并不会阻止运行时继续执行同步指令。
  • 异步错误只能通过异步的onRejected处理程序捕获;这不包括捕获执行函数中的错误,在解决或拒绝期约之前,仍然可以使用try/catch 在执行函数中捕获错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 正确 
Promise.reject(Error('foo')).catch((e) => {});
// 不正确
try {
Promise.reject(Error('foo'));
} catch(e) {}

let p = new Promise((resolve, reject) => {
try {
throw Error('foo');
} catch(e) {}
resolve('bar');
});
setTimeout(console.log, 0, p); // Promise <resolved>: bar
  • then()和catch()的onRejected处理程序在语义上相当于try/catch。
  • onRejected处理程序的任务应该是在捕获异步错误之后返回一个解决的期约。