Фон
TC39 proposal-promise-finally
, который теперь является частью спецификации ES2018 , перечисляет следующие ключевые точки также перефразировано в MDN для точного описания того, что делает метод.
promise.finally(func)
аналогично promise.then(func, func)
, но отличается в нескольких важных аспектах:
- При создании встроенной функции вы можете передать ее один раз, вместо того, чтобы принудительно либо объявить ее дважды, либо создать для нее переменную
- A
finally
Обратный вызов не получит никакого аргумента, посколькунет надежных средств определения, было ли обещание выполнено или отклонено.Этот вариант использования предназначен именно для случаев, когда вам не важно о причине отклонения или о значении выполнения, и поэтому нет необходимости указывать его. - В отличие от
Promise.resolve(2).then(() => {}, () => {})
(что будетразрешается с помощью undefined
), Promise.resolve(2).finally(() => {})
будет разрешаться с 2
. - Аналогично, в отличие от
Promise.reject(3).then(() => {}, () => {})
(который будет разрешен с undefined
), Promise.reject(3).finally(() => {})
будет отклоняться с 3
.
Однако обратите внимание: throw
(или возврат отклоненного обещания) в обратном вызове finally
отклонит новое обещание с этой причиной отклонения.
Другими словами, краткий polyfill, использующий реализацию Promise
, которая соответствует спецификации Promises / A + , выглядит следующим образом (на основе ответов @ Bergi и @ PatrickRoberts ).
Promise.prototype.finally = {
finally (fn) {
const onFulfilled = () => this;
const onFinally = () => Promise.resolve(fn()).then(onFulfilled);
return this.then(onFinally, onFinally);
}
}.finally;
Если мы сопоставим цепочку обещаний, используя Promise#then
и Promise#finally
с async function
, содержащим блок try...finally
, мы можемопределить некоторые ключевые различия, которые также упоминаются, но не уточняются здесь .
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
, так что особый случай для обратного вызова, который был разрешен до undefined
с использованием процедура разрешения обещания , был единственным условием, для которого разрешено finally()
re-принял состояние исходного обещания?
При использовании следующего полизаполнения поведение будет более точно соответствовать аналоговому блоку 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
, которые были бы болеегромоздко писать, если он использовал это вместо?