Skip to content

Commit

Permalink
add a workaround for APIs where not possible to replace broken native…
Browse files Browse the repository at this point in the history
… `Promise`, #579

added `.finally` and patched `.then` to / on native `Promise` prototype
  • Loading branch information
zloirock committed Jul 29, 2019
1 parent c9e1adb commit 03b3bad
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions packages/core-js/internals/native-promise-constructor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var global = require('../internals/global');

module.exports = global.Promise;
8 changes: 8 additions & 0 deletions packages/core-js/modules/es.promise.finally.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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']);
}
32 changes: 23 additions & 9 deletions packages/core-js/modules/es.promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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 }, {
Expand Down
17 changes: 17 additions & 0 deletions tests/tests/es.promise.finally.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

20 changes: 20 additions & 0 deletions tests/tests/es.promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

0 comments on commit 03b3bad

Please sign in to comment.