From e28762790447e4e300f6f7d570071fa72f1212a5 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 23 Dec 2021 01:09:55 +0700 Subject: [PATCH] fix the order of calling reactions of fulfilled / rejected promises in `Promise.prototype.then`, close #1026 --- CHANGELOG.md | 1 + package.json | 4 +- packages/core-js/modules/es.promise.js | 82 ++++++++++++++------------ 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba299cfb4282..6ff3a1c3d455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Changelog ##### Unreleased +- Fixed the order of calling reactions of fulfilled / rejected promises in `Promise.prototype.then`, [#1026](https://github.com/zloirock/core-js/issues/1026) - Fixed some missed dependencies of entries - Added Deno 1.18 compat data mapping diff --git a/package.json b/package.json index 0b1e55c24496..683b5daf9196 100644 --- a/package.json +++ b/package.json @@ -116,9 +116,9 @@ "test-entries-content": "zx tests/commonjs-entries-content.mjs", "test-entries-standalone": "run-s init test-entries", "test-targets-parser": "zx tests/targets-parser.mjs", - "test": "run-s init test-lint bundle test-unit test-promises-aplus test-observables test-entries test-targets-parser check", + "test": "run-s init test-lint bundle test-unit test-promises test-observables test-entries test-targets-parser check", "ci-karma": "run-s init bundle test-unit-karma", - "ci-tests": "run-s init bundle test-unit-node test-promises-aplus test-observables test-entries test-targets-parser", + "ci-tests": "run-s init bundle test-unit-node test-promises test-observables test-entries test-targets-parser", "clean-dependencies": "node scripts/clean-dependencies.mjs", "refresh": "npm run clean-dependencies && npm it", "downloads": "zx scripts/downloads-by-versions.mjs", diff --git a/packages/core-js/modules/es.promise.js b/packages/core-js/modules/es.promise.js index d24a34023e14..fc7ad03f1558 100644 --- a/packages/core-js/modules/es.promise.js +++ b/packages/core-js/modules/es.promise.js @@ -4,6 +4,7 @@ var IS_PURE = require('../internals/is-pure'); var global = require('../internals/global'); var getBuiltIn = require('../internals/get-built-in'); var call = require('../internals/function-call'); +var uncurryThis = require('../internals/function-uncurry-this'); var NativePromise = require('../internals/native-promise-constructor'); var redefine = require('../internals/redefine'); var redefineAll = require('../internals/redefine-all'); @@ -43,6 +44,7 @@ var PromisePrototype = NativePromisePrototype; var TypeError = global.TypeError; var document = global.document; var process = global.process; +var push = uncurryThis([].push); var newPromiseCapability = newPromiseCapabilityModule.f; var newGenericPromiseCapability = newPromiseCapability; @@ -95,47 +97,50 @@ var isThenable = function (it) { return isObject(it) && isCallable(then = it.then) ? then : false; }; +var callReaction = function (reaction, state) { + var value = state.value; + var ok = state.state == FULFILLED; + var handler = ok ? reaction.ok : reaction.fail; + var resolve = reaction.resolve; + var reject = reaction.reject; + var domain = reaction.domain; + var result, then, exited; + try { + if (handler) { + if (!ok) { + if (state.rejection === UNHANDLED) onHandleUnhandled(state); + state.rejection = HANDLED; + } + if (handler === true) result = value; + else { + if (domain) domain.enter(); + result = handler(value); // can throw + if (domain) { + domain.exit(); + exited = true; + } + } + if (result === reaction.promise) { + reject(TypeError('Promise-chain cycle')); + } else if (then = isThenable(result)) { + call(then, result, resolve, reject); + } else resolve(result); + } else reject(value); + } catch (error) { + if (domain && !exited) domain.exit(); + reject(error); + } +}; + var notify = function (state, isReject) { if (state.notified) return; state.notified = true; - var chain = state.reactions; microtask(function () { - var value = state.value; - var ok = state.state == FULFILLED; + var chain = state.reactions; var index = 0; // variable length - can't use forEach while (chain.length > index) { - var reaction = chain[index++]; - var handler = ok ? reaction.ok : reaction.fail; - var resolve = reaction.resolve; - var reject = reaction.reject; - var domain = reaction.domain; - var result, then, exited; - try { - if (handler) { - if (!ok) { - if (state.rejection === UNHANDLED) onHandleUnhandled(state); - state.rejection = HANDLED; - } - if (handler === true) result = value; - else { - if (domain) domain.enter(); - result = handler(value); // can throw - if (domain) { - domain.exit(); - exited = true; - } - } - if (result === reaction.promise) { - reject(TypeError('Promise-chain cycle')); - } else if (then = isThenable(result)) { - call(then, result, resolve, reject); - } else resolve(result); - } else reject(value); - } catch (error) { - if (domain && !exited) domain.exit(); - reject(error); - } + callReaction(chain[index++], state); } state.reactions = []; state.notified = false; @@ -265,14 +270,15 @@ if (FORCED) { // https://tc39.es/ecma262/#sec-promise.prototype.then then: function then(onFulfilled, onRejected) { var state = getInternalPromiseState(this); - var reactions = state.reactions; var reaction = newPromiseCapability(speciesConstructor(this, PromiseConstructor)); + state.parent = true; reaction.ok = isCallable(onFulfilled) ? onFulfilled : true; reaction.fail = isCallable(onRejected) && onRejected; reaction.domain = IS_NODE ? process.domain : undefined; - state.parent = true; - reactions[reactions.length] = reaction; - if (state.state != PENDING) notify(state, false); + if (state.state == PENDING) push(state.reactions, reaction); + else microtask(function () { + callReaction(reaction, state); + }); return reaction.promise; }, // `Promise.prototype.catch` method