正文
Promise.prototype.finally()
最近达到了 TC39 提案的
第 4 阶段
。这意味着
Promise.prototype.finally()
提案被采纳成为
ECMAScript 最新特性草案
的一部分,登陆 Node.js 现在只是时间问题了。这篇文章会向大家展示
Promise.prototype.finally()
的用法和简化版 Polyfill 的写法。
Promise.prototype.finally() 是什么?
假设你创建了一个新的
Promise
:
const promiseThatFulfills = new Promise((resolve) => {
// 调用 resolve() 可以让 Promised 的状态变为 fulfilled。"fulfilled" 和 "resolved" 是不同的概念:
// 如果你 resolve() 一个非 Promise 值,Promise 会变成 "fulfilled"。
// 然而, 如果 resolve() 一个 Promise,外层(原来的) Promise 会保持 "pending" 状态
// 直到内层 Promise 变为 "fulfilled" 或者 "rejected"
setTimeout(() => resolve('Hello, World'), 1000);
});
const promiseThatRejects = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('whoops!')), 1000);
});
你可以用
.then()
函数
把这些
Promise
串联在一起。
promiseThatFulfills.then(() => console.log('Will print after about 1 second'));
promiseThatRejects.then(null, () => console.log('Will print after about 1 second'));
注意
.then()
需要两个函数作为参数。第一个参数是
onFulfilled()
,当
Promise
为 fulfilled 时调用;第二个
onRejected()
则是在 rejected 的时候调用。
Promise
是一个必定处于以下三种状态之一的状态机:
-
pending(进行中): Promise 中的操作正在进行中,状态未被凝固为 fulfilled 或 rejected。
-
fulfilled(已完成,
直译:已满足
): Promise 中的操作已成功完成,现在
Promise
里面关联有该操作的返回值。
-
rejected(已失败,
直译:已回绝
): Promise 中的操作因某些原因失败,现在
Promise
里面关联有该操作的错误信息。
此外,处于 fulfilled 或者 rejected 状态的
Promise
称作“已凝固”(settled) 的
Promise
。
虽然
.then()
是串联
Promise
的核心机制,但并不独一无二。
Promise
用来处理抛出错误的
.catch()
函数
也能串联
Promise
。
promiseThatRejects.catch(() => console.log('Will print after about 1 second'));
.catch()
函数只是一个只有
onRejected()
参数的
.then()
的语法糖:
promiseThatRejects.catch(() => console.log('Will print after about 1 second'));
// 等价于
promiseThatRejects.then(null, () => console.log('Will print after about 1 second'));
类似于
.catch()
,.
finally()
也是
.then()
的一个语法糖。区别在于
.finally()
当
Promise
凝固(fulfilled / rejected)时执行一个
onFinally
函数。当前
.finally()
还没有加入 Node.js 发行版,但
npm 上的 promise.prototype.finally 模块
实现了它的 Polyfill。
const promiseFinally = require('promise.prototype.finally');
// 向 Promise.prototype 增加 finally()
promiseFinally.shim();
const promiseThatFulfills = new Promise((resolve) => {
setTimeout(() => resolve('Hello, World'), 1000);
});
const promiseThatRejects = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('whoops!')), 1000);
});
promiseThatFulfills.finally(() => console.log('fulfilled'));
promiseThatRejects.finally(() => console.log('rejected'));
上面代码的运行结果会打印 'fulfilled' 和 'rejected',因为无论是 fulfilled 还是 rejected,只要状态凝固
onFinally
都会立即执行。不过
onFinally
不
接受参数,所以你无法判断
Promise
的状态到底是两个中的哪个。
finally()
会返回一个
Promise
,所以你可以使用
.then()
/
.catch()
/
.finally()
串联它的返回值。finally() 返回的
Promise
会和它连接到的
Promise
保持相同的 fulfill 条件。 例如下面的代码,即使
onFinally
返回了 'bar',它还是会打印 5 次 'foo' 。
const promiseFinally = require('promise.prototype.finally');
// 向 Promise.prototype 增加 finally()
promiseFinally.shim();
Promise.resolve('foo').
finally(() => 'bar').
// 会打印 'foo', **不是** 'bar',因为 finally() 只起到转运的作用
// for fulfilled values and rejected errors
then(res => console.log(res));
类似地,下面代码中即使
onFinally
没有抛出任何错误,仍然会打印 'foo'。
const promiseFinally = require('promise.prototype.finally');
// 向 Promise.prototype 增加 finally()
promiseFinally.shim();
Promise.reject(new Error('foo')).
finally(() => 'bar').
// 会打印 'foo', **不是** 'bar',因为 finally() 只起到转运的作用
// 无论是 resolve 的值还是 reject 的错误
catch(err => console.log(err.message));
上面代码展示了使用
finally()
的一个重要细节:它
不会
帮你处理
Promise
的错误。如何让它能处理
Promise
错误值得更深入的研究。
错误处理
finally()
不是
用来处理
Promise
的错误的。事实上,它会在
onFinally()
执行后
显式重新抛错
。下面的代码会打印一个未被处理的
Promise
错误警告。
const promiseFinally = require('promise.prototype.finally');
// 向 Promise.prototype 增加 finally()
promiseFinally.shim();
const promiseThatRejects = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('whoops!')), 1000);
});
promiseThatRejects.finally(() => console.log('rejected'));
$ node finally.js
rejected
(node:5342) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: whoops!
(node:5342) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
$
与
try
/
catch
/
finally
类似,通常
.finally()
都会在
.catch()
后面被调用。
const promiseFinally = require('promise.prototype.finally');
// 向 Promise.prototype 增加 finally()
promiseFinally.shim();
const promiseThatRejects = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('whoops!')), 1000);
});
promiseThatRejects.
catch(() => { /* ignore the error */ }).
finally(() => console.log('done'));
然而
finally()
返回的也是
Promise
,所以你可以随意在
finally()
后面调用
.catch()
。特别地,如果
onFinally
会出错,例如 HTTP 请求,你应该在末尾添加
.catch()
以处理可能发生的错误。
const promiseFinally = require('promise.prototype.finally');
// 向 Promise.prototype 增加 finally()
promiseFinally.shim();
const promiseThatRejects = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('whoops!')), 1000);
});
promiseThatRejects.
finally(() => console.log('rejected')).
// No unhandled promise rejection because there's a .catch()
catch(() => { /* ignore the error */ });
简版 Polyfill
我觉得想要真正搞懂一个东西,最简单的方式就是
自己去实现一个
。
.finally()
是一个很好的选择,因为
官方 Polyfill
只有 45
行,而且大多数代码在验证原理时可以进一步精简。