Что послужило причиной отсутствия специального определения окончательного значения undefined из Promise # finally? - PullRequest
0 голосов
/ 09 мая 2019

Фон

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, которые были бы болеегромоздко писать, если он использовал это вместо?

1 Ответ

2 голосов
/ 09 мая 2019

Это демонстрирует, что семантика для установленного состояния результирующего обещания отличается от аналогового блока try...finally.

Не совсем, вы просто использовали "неправильный" синтаксис try / finally в своем сравнении. Запустите его снова с

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

и вы увидите, что это эквивалентно .finally().

По какой причине не было реализовано Promise#finally, так что особый случай для обратного вызова, который был разрешен до undefined с использованием процедуры разрешения обещания, был единственным условием, для которого разрешенный finally() повторно принимал состояние оригинальное обещание?

В предложении приводится после логического обоснования : «Promise#finally не сможет изменить возвращаемое значение […] - поскольку нет способа провести различие между« нормальным завершением »и в начале return undefined, параллель с синтаксическим окончанием должна иметь небольшой разрыв в согласованности."

Чтобы привести явный пример, с синтаксисом try / catch существует семантическая разница между

finally {
}

и

finally {
    return undefined;
}

но метод обещания не может быть реализован для различения

.finally(() => {
})

и

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

И нет, просто не было никакого смысла вводить какой-либо специальный кожух вокруг undefined. Всегда есть семантическая пропасть, и выполнение с другим значением в любом случае не является распространенным случаем. Вы также редко видите какие-либо операторы return в обычных finally блоках, большинство людей даже считают их запахом кода.

...