不对 Promise#finally 中未定义的解析值进行特殊封装的原因是什么?

What was the reasoning behind not special-casing a resolved value of undefined from Promise#finally?

背景

TC39 proposal-promise-finally, which is now part of the ES2018 specification, lists the following key points also paraphrased on MDN 准确描述了该方法的作用。

promise.finally(func) is similar to promise.then(func, func), but is different in a few critical ways:

  • When creating a function inline, you can pass it once, instead of being forced to either declare it twice, or create a variable for it
  • A finally callback will not receive any argument, since there's no reliable means of determining if the promise was fulfilled or rejected. This use case is for precisely when you do not care about the rejection reason, or the fulfillment value, and so there's no need to provide it.
  • Unlike Promise.resolve(2).then(() => {}, () => {}) (which will be resolved with undefined), Promise.resolve(2).finally(() => {}) will be resolved with 2.
  • Similarly, unlike Promise.reject(3).then(() => {}, () => {}) (which will be resolved with undefined), Promise.reject(3).finally(() => {}) will be rejected with 3.

However, please note: a throw (or returning a rejected promise) in the finally callback will reject the new promise with that rejection reason.

换句话说,使用 Promise implementation that conforms to the Promises/A+ specification is as follows (based on the answers by and ).

的简洁 polyfill
Promise.prototype.finally = {
  finally (fn) {
    const onFulfilled = () => this;
    const onFinally = () => Promise.resolve(fn()).then(onFulfilled);
    return this.then(onFinally, onFinally);
  }
}.finally;

如果我们将使用 Promise#thenPromise#finally 的 promise 链与包含 try...finally 块的 async function 进行对比,我们可以确定一些关键差异,这些差异也已提及但没有详细说明 here.

const completions = {
  return (label) { return `return from ${label}`; },
  throw (label) { throw `throw from ${label}`; }
};

function promise (tryBlock, finallyBlock) {
  return Promise.resolve()
    .then(() => completions[tryBlock]('try'))
    .finally(() => completions[finallyBlock]('finally'));
}

async function async (tryBlock, finallyBlock) {
  try { return completions[tryBlock]('try'); }
  finally { return completions[finallyBlock]('finally'); }
}

async function test (tryBlock, finallyBlock) {
  const onSettled = fn => result => console.log(`${fn}() settled with '${result}'`);
  const promiseSettled = onSettled('promise');
  const asyncSettled = onSettled('async');
  console.log(`testing try ${tryBlock} finally ${finallyBlock}`);
  await promise(tryBlock, finallyBlock).then(promiseSettled, promiseSettled);
  await async(tryBlock, finallyBlock).then(asyncSettled, asyncSettled);
}

[['return', 'return'], ['return', 'throw'], ['throw', 'return'], ['throw', 'throw']]
  .reduce((p, args) => p.then(() => test(...args)), Promise.resolve());
.as-console-wrapper{max-height:100%!important}

这表明结果承诺的稳定状态的语义不同于模拟 try...finally 块。

问题

没有实施 Promise#finally 的原因是什么,以至于使用 the promise resolution procedure 解析为 undefined 的回调的特殊情况是解析 [=36] 的唯一条件=]重新采用了原来promise的状态?

使用以下 polyfill,行为将更接近模拟 try...finally 块,除非 finally 块包含显式 return;return undefined;声明。

Promise.prototype.finally = {
  finally (fn) {
    const onFulfilled = value => value === undefined ? this : value;
    const onFinally = () => Promise.resolve(fn()).then(onFulfilled);
    return this.then(onFinally, onFinally);
  }
}.finally;

作为后续问题,如果一致认为当前规范比上面的建议更可口,是否有任何 Promise#finally 的规范用法,如果使用它会更麻烦这个呢?

This demonstrates that the semantics for the settled state of the resulting promise differ from the analogue try...finally block.

不是,您只是在比较中使用了 "wrong" try/finally 语法。 运行 再用

async function async (tryBlock, finallyBlock) {
  try { return completions[tryBlock]('try'); }
  finally {        completions[finallyBlock]('finally'); }
//          ^^^^^^ no `return` here
}

你会发现它等同于 .finally().

What was the reason for not implementing Promise#finally such that a special case for a callback that resolved to undefined using the promise resolution procedure was the only condition for which a resolved finally() re-adopted the state of the original promise?

提案给出了following rationale:“Promise#finally将无法修改return值[…] - 因为没有办法区分在“正常完成”和早期 return undefined 之间,与句法 finally 的平行必须有轻微的一致性差距。"

举一个明确的例子,try/catch语法在

之间存在语义差异
finally {
}

finally {
    return undefined;
}

但是promise方法无法实现区分

.finally(() => {
})

.finally(() => {
    return undefined;
})

不,在 undefined 周围引入任何特殊外壳根本没有意义。语义鸿沟总是存在的,用不同的值来实现无论如何都不是常见的用例。你也很少在正常的 finally 块中看到任何 return 语句,大多数人甚至会认为它们是代码味道。