Это было немного сложнее, чем я думал. Это должно даже обрабатывать распространение таблицы 100 * *species
в дополнение к поведению, описанному ниже:
Promise.prototype.finally = Promise.prototype.finally || {
finally (fn) {
const onFinally = cb => Promise.resolve(fn()).then(cb);
return this.then(
result => onFinally(() => result),
reason => onFinally(() => Promise.reject(reason))
);
}
}.finally;
Эта реализация основана на документированном поведении finally()
и зависит от того, * then()
соответствует спецификации:
A finally
обратный вызов не получит никакого аргумента, поскольку нет надежных средств определения того, было ли обещание выполнено или отклонено. Этот вариант использования предназначен именно для случаев, когда вас не заботит причина отклонения или значение выполнения, и поэтому нет необходимости указывать его.
В отличие от Promise.resolve(2).then(() => {}, () => {})
(который будет разрешен с undefined
), Promise.resolve(2).finally(() => {})
будет разрешен с 2
.
Аналогично, в отличие от Promise.reject(3).then(() => {}, () => {})
(который будет выполнен с undefined
), Promise.reject(3).finally(() => {})
будет отклонено с 3
.
Примечание: A throw
(или возврат отклоненного обещания) в обратном вызове finally
отклонит новое обещание с причиной отказа, указанной при вызове throw()
.
И, конечно, демонстрация эквивалентного поведения:
const logger = (label, start = Date.now()) => (...values) =>
console.log(label, ...values, `after ${Date.now() - start}ms`);
const delay = (value, ms) => new Promise(resolve => setTimeout(resolve, ms, value));
test('native');
// force Promise to use the polyfill implementation
Promise.prototype.finally = /* Promise.prototype.finally || */ {
finally (fn) {
const onFinally = cb => Promise.resolve(fn()).then(cb);
return this.then(
result => onFinally(() => result),
reason => onFinally(() => Promise.reject(reason))
);
}
}.finally;
test('polyfill');
function test (impl) {
const log = ordinal => state => logger(`${ordinal} ${impl} ${state}`);
const first = log('first');
delay(2, 1000)
.finally(first('settled'))
.then(first('fulfilled'), first('rejected'));
const second = log('second');
delay(Promise.reject(3), 2000)
.finally(second('settled'))
.then(second('fulfilled'), second('rejected'));
const third = log('third');
delay(4, 3000)
.finally(third('settled'))
.finally(() => delay(6, 500))
.then(third('fulfilled'), third('rejected'));
const fourth = log('fourth');
delay(5, 4000)
.finally(fourth('settled'))
.finally(() => delay(Promise.reject(7), 500))
.then(fourth('fulfilled'), fourth('rejected'));
}
.as-console-wrapper{max-height:100%!important}
Спасибо @ Bergi за его вклад в этот ответ. Пожалуйста, ознакомьтесь с его реализацией и добавьте комментарий, если вы нашли этот пост полезным.