精读《JavaScript 高级程序设计 第4版》第11章 期约与异步函数(二)
11.1.2 期约的实例方法
期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁。这些方法可以访问异步操作返回的数据,处理期约成功和失败的结果,连续对期约求值,或添加只有期约进入终止状态时才会执行的代码。
1、Promise.prototype.then()
这是Promise最核心、最基础的方法。
作用:为Promise实例添加兑现状态和拒绝状态的回调函数。它返回一个新的Promise实例。
语法:
p.then(onFulfilled?, onRejected?);
onFulfilled(可选):当Promise变为兑现状态时异步执行的函数。该函数接收一个参数,即成功的值(fulfillment value)。onRejected(可选):当Promise变为拒绝状态时异步执行的函数。该函数接收一个参数,即失败的原因(rejection reason)。- 如果都提供的话会在期约分别进入兑现和拒绝状态时执行。
详细解析与现代实践:
返回值与链式调用:then方法返回一个新的Promise。这个新Promise的解决值(或拒绝原因)由onFulfilled或onRejected函数的返回值决定。这是Promise链式调用的基础。
-
- 如果处理函数返回一个值,新Promise会以该值被兑现。
- 如果处理函数没有返回值(即返回
undefined),新Promise会以undefined被兑现。 - 如果处理函数抛出异常,新Promise会以该异常为原因被拒绝。
- 如果处理函数返回一个Promise,那么
then返回的新Promise将“跟随”这个被返回的Promise。具体来说,它会等待这个内部Promise解决,然后采用其最终状态和值。
const p1 = new Promise((resolve, reject) => resolve(100));
p1.then(result => {
console.log(result); // 100
return result * 2; // 返回一个值
}).then(result => {
console.log(result); // 200 (上一个then返回的值)
return new Promise(resolve => setTimeout(() => resolve(result + 50), 1000)); // 返回一个Promise
}).then(result => {
console.log(result); // 250 (等待1秒后,得到内部Promise的解决值)
throw new Error(‘出错了!’); // 抛出异常
}).then(
result => console.log(‘这个不会执行’, result),
reason => console.error(‘捕获错误:’, reason.message) // 捕获错误:出错了!
);
参数可选:
你可以只传onFulfilled,或者将onRejected设为null/undefined。如果某个状态没有对应的处理函数,Promise链会进入一种“跳过”机制,直到找到对应的处理函数为止。这对于错误传播非常重要。
Promise.reject(‘error’)
.then(result => console.log(‘成功’, result)) // 被跳过,因为初始是rejected
.then(null, reason => console.log(‘捕获到:’, reason)); // 捕获到:error
2、Promise.prototype.catch()
作用:用于给期约附加一个拒绝状态的处理程序。它是.then(null, onRejected)或.then(undefined, onRejected)的语法糖,使代码更清晰。
语法:
p.catch(onRejected);
详细解析与现代实践:
错误冒泡:catch会捕获它之前所有Promise链中(包括它所在的Promise本身)未被处理的拒绝。这是Promise错误处理的黄金法则。
someAsyncFunction()
.then(result => {
// ... 处理结果
return anotherAsyncFunction();
})
.then(result => {
// ... 如果anotherAsyncFunction返回的Promise被拒绝,这里会被跳过
})
.catch(reason => {
// 捕获上面所有步骤中发生的任何错误!
console.error(‘链条中某处出错:’, reason);
});
它返回的也是一个Promise:
和then一样,catch也返回一个新的Promise。如果onRejected函数正常执行(没有抛出错误或返回一个被拒绝的Promise),那么这个新Promise将会被兑现。这允许你在捕获错误后继续链式调用。
Promise.reject(‘初始错误’)
.catch(reason => {
console.log(‘捕获到:’, reason); // 捕获到:初始错误
return ‘从错误中恢复的值’;
})
.then(result => {
console.log(result); // 从错误中恢复的值 (catch返回的Promise被兑现了)
});
3、 Promise.prototype.finally()
作用:用于指定一个无论Promise最终状态是兑现还是拒绝都会执行的回调函数。但这个处理程序没有办法知道期约的状态时解决还是拒绝,所以该方法主要用于添加清理代码,例如隐藏加载动画、关闭数据库连接等。
语法:
p.finally(onFinally);
详细解析与现代实践:
- 不接收参数:
onFinally回调函数不接收任何参数,因为你不知道Promise最终是成功还是失败。它只表明操作已经结束。
传递原始状态和值:finally方法返回一个新的Promise。这个新Promise通常会“继承”原Promise的最终状态和值。
-
- 如果原Promise被兑现为
’foo‘,finally返回的Promise也会被兑现为’foo‘。 - 如果原Promise被拒绝为
’bar‘,finally返回的Promise也会被拒绝为’bar‘。 - 例外:如果
onFinally函数抛出了错误或返回了一个被拒绝的Promise,那么返回的Promise会以这个新错误原因被拒绝。
- 如果原Promise被兑现为
let p1 = Promise.resolve('foo');
//以下都会原样后传
let p2 = p1.finally();
let p3 = p1.finally(() => undefind);
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'));
//例外情况
let p9 = p1.finally(() => new Promise(() => {}));
let p10 = p1.finally(() => Promise.reject());
let p11 = p1.finally(() => { throw 'baz' });
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
setTimeout(console.log, 0, p9); //Promise <pending>
setTimeout(console.log, 0, p10); //Promise <rejected>: undefined
setTimeout(console.log, 0, p11); //Promise <rejected>: baz
常规用法:
let isLoading = true;
fetch(‘/api/data’)
.then(response => response.json())
.then(data => {
// 处理数据
})
.catch(error => {
// 处理错误
})
.finally(() => {
isLoading = false; // 无论成功失败,都隐藏加载动画
console.log(‘请求结束’);
});
// finally返回的Promise状态与上面的catch(或then)返回的状态一致
4、非重入期约方法
当期约进入落定状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。跟在添加这个处理程序的代码之后的同步代码一定会在处理程序之前先执行。即使期约一开始就是与附加处理程序关联的状态,执行顺序也是这样的。这个特性由JavaScript运行时保证,被称为“非重入”特性。
let synchronousResolve;
let p = new Promise((resolve) => {
synchronousResolve = function(){
console.log('1: invoking reslove()');
resolve();
console.log('2: reslove() returns');
}
})
p.then(() => {
console.log('4: then() hander executes');
})
synchronousResolve();
console.log('3: synchronousResolve() returns');
//实际输出:
//1: invoking reslove()
//2: reslove() returns
//3: synchronousResolve() returns
//4: then() hander executes
5、邻近处理程序的执行顺序
如果给期约添加了多个处理程序,当期约状态变化时,相关处理程序会按照添加它们的顺序依次执行。无论时then()、catch()还是finally()添加的处理程序都是如此。
6、传递解决值和拒绝理由
到了落定状态后,期约会提供其解决值(如果兑现)或者拒绝理由(如果拒绝)给相关状态的处理程序。拿到该返回值后,就可以进一步对这个值进行操作。在执行函数中,解决值和拒绝理由分别作为resolve()和reject()的第一个参数往后传的。然后,这些值又传给他们各自的处理程序,作为处理程序的唯一参数。
let p1 = Promise.resolve('foo');
let p2 = Promise.reject('bar');
p1.then((value) => console.log(value)); //foo
p2.catch((reason) => console.log(reason)); //bar
7、拒绝期约与拒绝错误处理
拒绝期约类似于throw()表达式,因为它们都代表一种程序状态,即表示需要中断或特殊处理。在期约的执行函数或者处理程序中抛出错误会导致拒绝,对应的错误对象会成为拒绝的理由:
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
//同时也会抛出四个未捕获错误
//...
new Promise((resolve, reject) => {
console.log('begin asynchronous execution');
reject(Error('bar'));
}).catch((e) => {
console.log('caught error', e);
}).then(() => {
console.log('continue asynchronous execution');
})
// begin asynchronous execution
// caught error Error: bar
// continue asynchronous execution更多推荐



所有评论(0)