From 03b3bad7abb909d7980592fa1c9ff1c539dd94c2 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Tue, 30 Jul 2019 02:58:48 +0700 Subject: [PATCH] add a workaround for APIs where not possible to replace broken native `Promise`, #579 added `.finally` and patched `.then` to / on native `Promise` prototype --- CHANGELOG.md | 1 + .../internals/native-promise-constructor.js | 3 ++ .../core-js/modules/es.promise.finally.js | 8 +++++ packages/core-js/modules/es.promise.js | 32 +++++++++++++------ tests/tests/es.promise.finally.js | 17 ++++++++++ tests/tests/es.promise.js | 20 ++++++++++++ 6 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 packages/core-js/internals/native-promise-constructor.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 9571d41fbdea..23016906d8c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - `using` statement proposal moved to stage 2, added `Symbol.asyncDispose`, per July TC39 meeting - Added `Map#updateOrInsert` [stage 1 proposal](https://docs.google.com/presentation/d/1_xtrGSoN1-l2Q74eCXPHBbbrBHsVyqArWN0ebnW-pVQ/), per July TC39 meeting - Added a fix for [`Math.hypot` V8 7.7 bug](https://bugs.chromium.org/p/v8/issues/detail?id=9546), since it's still not stable without adding results to `core-js-compat` +- Added a workaround for APIs where not possible to replace broken native `Promise`, [#579](https://github.com/zloirock/core-js/issues/579) - added `.finally` and patched `.then` to / on native `Promise` prototype - Fixed incorrect early breaking of `{ Map, Set, WeakMap, WeakSet }.deleteAll` - Fixed some missed dependencies in entry points - Added compat data for Node 12.5, FF 67, Safari 13 diff --git a/packages/core-js/internals/native-promise-constructor.js b/packages/core-js/internals/native-promise-constructor.js new file mode 100644 index 000000000000..dae38224301f --- /dev/null +++ b/packages/core-js/internals/native-promise-constructor.js @@ -0,0 +1,3 @@ +var global = require('../internals/global'); + +module.exports = global.Promise; diff --git a/packages/core-js/modules/es.promise.finally.js b/packages/core-js/modules/es.promise.finally.js index ab2ce08b55c9..6c1ec7940ac5 100644 --- a/packages/core-js/modules/es.promise.finally.js +++ b/packages/core-js/modules/es.promise.finally.js @@ -1,8 +1,11 @@ 'use strict'; var $ = require('../internals/export'); +var IS_PURE = require('../internals/is-pure'); +var NativePromise = require('../internals/native-promise-constructor'); var getBuiltIn = require('../internals/get-built-in'); var speciesConstructor = require('../internals/species-constructor'); var promiseResolve = require('../internals/promise-resolve'); +var redefine = require('../internals/redefine'); // `Promise.prototype.finally` method // https://tc39.github.io/ecma262/#sec-promise.prototype.finally @@ -20,3 +23,8 @@ $({ target: 'Promise', proto: true, real: true }, { ); } }); + +// patch native Promise.prototype for native async functions +if (!IS_PURE && typeof NativePromise == 'function' && !NativePromise.prototype['finally']) { + redefine(NativePromise.prototype, 'finally', getBuiltIn('Promise').prototype['finally']); +} diff --git a/packages/core-js/modules/es.promise.js b/packages/core-js/modules/es.promise.js index 621b72d2e34d..b0d76c63aa8f 100644 --- a/packages/core-js/modules/es.promise.js +++ b/packages/core-js/modules/es.promise.js @@ -3,6 +3,8 @@ var $ = require('../internals/export'); var IS_PURE = require('../internals/is-pure'); var global = require('../internals/global'); var path = require('../internals/path'); +var NativePromise = require('../internals/native-promise-constructor'); +var redefine = require('../internals/redefine'); var redefineAll = require('../internals/redefine-all'); var setToStringTag = require('../internals/set-to-string-tag'); var setSpecies = require('../internals/set-species'); @@ -29,7 +31,7 @@ var PROMISE = 'Promise'; var getInternalState = InternalStateModule.get; var setInternalState = InternalStateModule.set; var getInternalPromiseState = InternalStateModule.getterFor(PROMISE); -var PromiseConstructor = global[PROMISE]; +var PromiseConstructor = NativePromise; var TypeError = global.TypeError; var document = global.document; var process = global.process; @@ -47,7 +49,7 @@ var FULFILLED = 1; var REJECTED = 2; var HANDLED = 1; var UNHANDLED = 2; -var Internal, OwnPromiseCapability, PromiseWrapper; +var Internal, OwnPromiseCapability, PromiseWrapper, nativeThen; var FORCED = isForced(PROMISE, function () { // correct subclassing with @@species support @@ -272,13 +274,25 @@ if (FORCED) { : newGenericPromiseCapability(C); }; - // wrap fetch result - if (!IS_PURE && typeof $fetch == 'function') $({ global: true, enumerable: true, forced: true }, { - // eslint-disable-next-line no-unused-vars - fetch: function fetch(input) { - return promiseResolve(PromiseConstructor, $fetch.apply(global, arguments)); - } - }); + if (!IS_PURE && typeof NativePromise == 'function') { + nativeThen = NativePromise.prototype.then; + + // wrap native Promise#then for native async functions + redefine(NativePromise.prototype, 'then', function then(onFulfilled, onRejected) { + var that = this; + return new PromiseConstructor(function (resolve, reject) { + nativeThen.call(that, resolve, reject); + }).then(onFulfilled, onRejected); + }); + + // wrap fetch result + if (typeof $fetch == 'function') $({ global: true, enumerable: true, forced: true }, { + // eslint-disable-next-line no-unused-vars + fetch: function fetch(input) { + return promiseResolve(PromiseConstructor, $fetch.apply(global, arguments)); + } + }); + } } $({ global: true, wrap: true, forced: FORCED }, { diff --git a/tests/tests/es.promise.finally.js b/tests/tests/es.promise.finally.js index 197d747dfc6e..0cb7bc600083 100644 --- a/tests/tests/es.promise.finally.js +++ b/tests/tests/es.promise.finally.js @@ -36,3 +36,20 @@ QUnit.test('Promise#finally, rejected', assert => { async(); }); }); + +const promise = (() => { + try { + return Function('return (async function () { /* empty */ })()')(); + } catch { /* empty */ } +})(); + +if (promise && promise.constructor !== Promise) QUnit.test('Native Promise, patched', assert => { + assert.isFunction(promise.finally); + assert.arity(promise.finally, 1); + assert.looksNative(promise.finally); + assert.nonEnumerable(promise.constructor.prototype, 'finally'); + function empty() { /* empty */ } + assert.ok(promise.finally(empty) instanceof Promise, '`.finally` returns `Promise` instance #1'); + assert.ok(new promise.constructor(empty).finally(empty) instanceof Promise, '`.finally` returns `Promise` instance #2'); +}); + diff --git a/tests/tests/es.promise.js b/tests/tests/es.promise.js index 683251f7d587..759ae57303b3 100644 --- a/tests/tests/es.promise.js +++ b/tests/tests/es.promise.js @@ -461,3 +461,23 @@ QUnit.skip('Unhandled rejection tracking', assert => { done = true; }, 3e3); }); + +const promise = (() => { + try { + return Function('return (async function () { /* empty */ })()')(); + } catch { /* empty */ } +})(); + +if (promise && promise.constructor !== Promise) QUnit.test('Native Promise, patched', assert => { + assert.isFunction(promise.then); + assert.arity(promise.then, 2); + assert.looksNative(promise.then); + assert.nonEnumerable(promise.constructor.prototype, 'then'); + function empty() { /* empty */ } + assert.ok(promise.then(empty) instanceof Promise, '`.then` returns `Promise` instance #1'); + assert.ok(new promise.constructor(empty).then(empty) instanceof Promise, '`.then` returns `Promise` instance #2'); + assert.ok(promise.catch(empty) instanceof Promise, '`.catch` returns `Promise` instance #1'); + assert.ok(new promise.constructor(empty).catch(empty) instanceof Promise, '`.catch` returns `Promise` instance #2'); + assert.ok(promise.finally(empty) instanceof Promise, '`.finally` returns `Promise` instance #1'); + assert.ok(new promise.constructor(empty).finally(empty) instanceof Promise, '`.finally` returns `Promise` instance #2'); +});