Core-js: Promise.prototype.finally polyfill not applying to async functions

Created on 20 Jun 2019  路  5Comments  路  Source: zloirock/core-js

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.

Most helpful comment

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));
}

All 5 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sgammon picture sgammon  路  6Comments

fender picture fender  路  5Comments

koenpunt picture koenpunt  路  5Comments

Sequoia picture Sequoia  路  5Comments

blondie63 picture blondie63  路  4Comments