-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Promise.prototype.finally should use onFinally's realm for newly creating functions #2222
Comments
Well, this is fascinating. cc @domenic |
You’re correct that the choice of realm isn’t intentional - it makes sense to me to use onFinally’s realm. |
Nice find! I agree with @ljharb. The internal functions created are mostly specification artifacts, from my point of view (e.g. they could be replaced with spec closures if we had a coherent way of doing PerformPromiseThen with spec closures). So they should be changed in whatever way is necessary to give reasonable behavior, and matching the realm of onFinally seems great in that regard. |
…rototype.finally (fixes tc39#2222)
cc @erights To follow up on our meeting yesterday: The bug I mentioned is referenced at the top of this issue thread ( https://bugzilla.mozilla.org/show_bug.cgi?id=1674505). I was mistaken in bringing up incumbent realms as I thought this was browser specific and had tied it in my head to that. It may be browse specific but it looks more general. Context: we use JS to write many components of the browser UI. We also use JS in testing the browser. The problem arose when we found that tests using await were hanging indefinitely and that we could work around those hanging promises by using onFinally. We have a realm, where a promise object "promise" was created. The browser is shutting down, so the realm is dying. The bug was that our test cases were relying on running functions as the browser was closing, but promise.then and promise.catch check the dying global and never run. However, a work around for this was to use onFinally. onFinally, unlike then and catch, would resolve -- because its realm was not the promise's realm (the window). It was instead the test's realm -- which was currently running and not dying. Here is what that looks like in practice: async function deleteme1(promise) { // Promise object is from Realm A. Realm A is dying. function deleteme1 is in Realm B
console.log("waiting for promise\n");
await promise;
console.log( "waited for promise\n"); // never runs, Promise.then checks if Realm A is alive -- if it is not it doesn't get queued.
return 42;
} However, this will work: async function deleteme1(promise) {
console.log("waiting for promise\n");
promise.finally(() => {
console.log("waited for promise\n"); // runs, because finally is running in Realm B, which is not dying.
});
return 42;
} But this will not: async function deleteme1(promise) {
console.log("waiting for promise\n");
await promise.finally(() => {
console.log("waited for promise\n"); // fails to run -- await changes the context of finally to run in Realm A
});
return 42;
} While onFinally "works" for getting around the dying global problem, it doesn't seem to be right to run a function associated with a promise from realm A in realm B as this is inconsistent with how promise then/catch methods work. It also feels unintentional as I would expect promise.then to run in its original realm just later. This indicates that we likely want to align the |
I'm still not sure I fully understand how dying realms tying into this, but I think the main concern we have is that as proposed, |
The issue i think here is consistent across browser, but it is likely that we have a more general issue across browsers with regards to how realms work in asynchronous contexts. There is a web compat risk, outside of what we are discussing in this issue -- which is that the browsers are not agreeing on the constructor's realm. I am not sure which one is correct in that case, but for this issue we should likely focus on the disparity found between then and onFinally. We can resolve the other issue separately. |
For context: the issue with the dying global was how we identified the issue and is also a real world example of where this might be a problem -- in testing code. The problem itself is more general than that. |
derived from https://bugzilla.mozilla.org/show_bug.cgi?id=1674505#c6
promise.finally(onFinally)
andpromise.then(onFinally, onFinally)
behaves differently in unexpected way ifpromise
andonFinally
comes from different realm.Here's the details.
If
Promise.prototype.finally
is called with a function, it creates new functions and callsthen
with them.https://tc39.es/ecma262/#sec-promise.prototype.finally
CreateBuiltinFunction
uses current realm becauserealm
parameter isn't passed here.And the current realm is
Promise.prototype.finally
function's realm.https://tc39.es/ecma262/#sec-createbuiltinfunction
At this point, handler function passed to
Promise.prototype.then
comes from different realm forpromise.finally(onFinally)
andpromise.then(onFinally, onFinally)
.For
promise.finally(onFinally)
case, the handler belongs tofinally
's function's realm.For
promise.then(onFinally, onFinally)
case, the handler isonFinally
itself.And this difference is propagated to embedding as following:
Promise.prototype.then
callsPerformPromiseThen
https://tc39.es/ecma262/#sec-promise.prototype.then
And
PerformPromiseThen
callsNewPromiseReactionJob
withonFulfilledJobCallback
, that contains theonFulfilled
in[[Callback]]
field.https://tc39.es/ecma262/#sec-performpromisethen
https://tc39.es/ecma262/#sec-hostmakejobcallback
And
NewPromiseReactionJob
creates a job withonFulfilled
's realm.https://tc39.es/ecma262/#sec-newpromisereactionjob
and the realm is passed to
HostEnqueuePromiseJob
.It checks if the realm is "fully active" or not.
https://html.spec.whatwg.org/#hostenqueuepromisejob
https://html.spec.whatwg.org/#check-if-we-can-run-script
So, the difference in realm affects here.
If
onFinally
's global is dying (= not fully active),promise.finally(onFinally)
runs the job, butpromise.then(onFinally, onFinally)
doesn't run the job.IIUC, creating new functions in
finally
is mostly to avoid passingvalueOrReason
toonFinally
,and using
finally
's realm isn't much intentional?If that's the case, it should use
onFinally
's realm when creating those functions.The text was updated successfully, but these errors were encountered: