This should handle the propagation of the thenable's species
in addition to the behaviors detailed below:
Promise.prototype.finally = Promise.prototype.finally || {
finally (fn) {
const onFinally = callback => Promise.resolve(fn()).then(callback);
return this.then(
result => onFinally(() => result),
reason => onFinally(() => Promise.reject(reason))
);
}
}.finally;
This implementation is based on the documented behavior of finally()
and depends on then()
being compliant to the specification:
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 fulfilled with undefined
), Promise.reject(3).finally(() => {})
will be rejected with 3
.
Note: A throw
(or returning a rejected promise) in the finally
callback will reject the new promise with the rejection reason specified when calling throw()
.
And of course a demonstration of equivalent behavior:
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);
});
// run test on native implementation
test('native');
// force Promise to use the polyfill implementation
Promise.prototype.finally = /* Promise.prototype.finally || */ {
finally (fn) {
const onFinally = callback => Promise.resolve(fn()).then(callback);
return this.then(
result => onFinally(() => result),
reason => onFinally(() => Promise.reject(reason))
);
}
}.finally;
// run test on polyfill implementation
test('polyfill');
function test (impl) {
const log = ordinal => state => logger(`${ordinal} ${impl} ${state}`);
const first = log('first');
// test propagation of resolved value
delay(2, 1000)
.finally(first('settled'))
.then(first('fulfilled'), first('rejected'));
const second = log('second');
// test propagation of rejected value
delay(Promise.reject(3), 2000)
.finally(second('settled'))
.then(second('fulfilled'), second('rejected'));
const third = log('third');
// test adoption of resolved promise
delay(4, 3000)
.finally(third('settled'))
.finally(() => delay(6, 500))
.then(third('fulfilled'), third('rejected'));
const fourth = log('fourth');
// test adoption of rejected promise
delay(5, 4000)
.finally(fourth('settled'))
.finally(() => delay(Promise.reject(7), 500))
.then(fourth('fulfilled'), fourth('rejected'));
}
.as-console-wrapper{max-height:100%!important}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…