I spotted an issue with using native async functions with the Promise.prototype.finally polyfill. The issue comes about when using core-js in conjunction with @babel/preset-env with a browser that supports native async functions but not Promise.prototype.finally (e.g. Edge 17). It appears that the global Promise object is polyfilled but the Promise returned from an async function is not.
Repo with example to reproduce: https://github.com/vetruvet/async-finally-promise-bug
In that repo, master has a demo of the issue (open in Edge to see it fail).
Interestingly, simply setting Promise.prototype.finally to a dummy function propagates that function to the Promise returned by async functions (not-a-polyfill-demo branch in the above repo demonstrates this). Obviously the callback passed in does not get called but the intention is to demonstrate that setting finally on Promise.prototype affects the promise returned by async functions.
I am currently working around the bug in a hacky way (workaround branch in the above repo) by copying finally to the async function's Promise prototype and that seems to be working but obviously I think there should be a proper fix for this.
Also of note is that try { await ...; } catch (e) { ... } finally { ... } works just fine in Edge, just not calling .finally(...) on the return value of an async function.
It's a known issue, but it's deeper than just missed .finally on async functions. It's a subset of this issue.
Edge does not pass core-js Promise feature detection, so Promise completely replaced in this engine. So, Promise.prototype.finally added to polyfilled Promise, but native async functions return a native Promise.
In corejs@3 added a workaround of this problem for fetch - a wrapper. But we can't do the same for async functions since it's syntax.
The best solution for correct work here - add a transform-wrapper to Babel for environments which do not support Promise completely, but support async functions:
async function () { /* ... */ }
// =>
function () {
return Promise.resolve(async function () { /* ... */ }.apply(this, arguments));
}
I added a workaround which should fix a big part of cases by patching .than and adding .finally on / to native Promise prototype.
We used this general function to normalize native promises returned from Stripe.js;
/**
* Normalize promises that are not feature complete.
*
* @param original {Promise} Native or normalized promise.
* @returns {Promise} Normalized feature-complete core-js promise with support for .finally().
*/
export function normalizePromise(original) {
return new Promise((resolve, reject) => {
original.then(resolve).catch(reject)
})
}
E.g.
return normalizePromise(window.stripe.createToken(element, params))
@BevanR That seems like a longer version of Promise.resolve(original).
@loganfsmyth Indeed. Very nice.
Most helpful comment
It's a known issue, but it's deeper than just missed
.finallyon async functions. It's a subset of this issue.Edge does not pass
core-jsPromisefeature detection, soPromisecompletely replaced in this engine. So,Promise.prototype.finallyadded to polyfilledPromise, but native async functions return a nativePromise.In
corejs@3added a workaround of this problem forfetch- a wrapper. But we can't do the same for async functions since it's syntax.The best solution for correct work here - add a transform-wrapper to Babel for environments which do not support
Promisecompletely, but support async functions: