专栏名称: 众成翻译
翻译,求知的另一种表达
目录
相关文章推荐
51好读  ›  专栏  ›  众成翻译

在 Node.js 中使用 Promise.prototype.finally

众成翻译  · 掘金  ·  · 2018-06-09 02:28

正文

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 行,而且大多数代码在验证原理时可以进一步精简。







请到「今天看啥」查看全文