RESOLVED FIXED312466
Promise.prototype.finally does not seem to observably assert whether IsConstructor(C) is true
https://bugs.webkit.org/show_bug.cgi?id=312466
Summary Promise.prototype.finally does not seem to observably assert whether IsConstr...
Francisco Tolmasky
Reported 2026-04-16 05:39:22 PDT
The following line does not throw any errors in Safari (and just returns undefined), but *does* throw an error in Chrome and Firefox: `Promise.prototype.finally.call({ constructor: { [Symbol.species]: () => 10 }, then() { } })` If you run that line in Chrome you'll see that you get: ``` VM619:1 Uncaught TypeError: object.constructor[Symbol.species] is not a constructor at Object.finally (<anonymous>) at <anonymous>:1:27 ``` In Firefox you get: ``` Uncaught TypeError: @@species property of object's constructor is not a constructor <anonymous> debugger eval code:1 ``` The relevant part of the spec is found at step 4 of the promise.prototype.finally definition (https://tc39.es/ecma262/#sec-promise.prototype.finally), copied here: "4. Assert: IsConstructor(C) is true." I believe that Safari's choice to not throw *is* technically allowed by the spec since the spec says that "Asserts" do not necessarily need to have any semantic effect, and thus both strategies seem acceptable. However I think it would be preferable for Safari to match Chrome and Firefox's behavior, both for interoperability reasons, but also because throwing when the assertion is false is actually quite useful. In particular, this is currently employed in the wild as a means to implement an "IsConstructor" function that is side-effect-free. That is to say, you can call IsConstructor(F) and be assured that F will never itself be called as a result. Unfortunately, techniques like using Reflect.construct(F, []) and checking for an error are *not* side-effect free in this way. They WILL call F F in the case where F *is* a constructor.
Attachments
Francisco Tolmasky
Comment 1 2026-04-16 05:53:06 PDT
Another quick comment: it also arguably makes implementing a rock solid IsConstructor possible at all. Without it, you need to do the difficult task of filtering non-Reflect.construct not a constructor errors. Just as a quick example, Reflect.construct(Element, []) throws with "illegal constructor" just to show you how quickly you run into this sort of thing. Element is however detected perfectly fine with the Promise.prototype.finally method in the browsers that throw.
Tetsuharu Ohzeki [UTC+9]
Comment 2 2026-04-16 09:36:59 PDT
I seem this case is covered by https://github.com/tc39/test262/blob/1775ee48c9d71fbd1ec6a1a66ac23efcbd627f8f/test/built-ins/Promise/prototype/finally/species-constructor-throws.js and it was imported in https://commits.webkit.org/308596@main, but the above test just checks what the thrown is just a TypeError, but not checking the detail of it. Its TypeError is caused by that mimic |this| lacks |then|.
Tetsuharu Ohzeki [UTC+9]
Comment 3 2026-04-16 09:39:44 PDT
EWS
Comment 4 2026-04-21 16:05:47 PDT
Committed 311725@main (e704581e3ddb): <https://commits.webkit.org/311725@main> Reviewed commits have been landed. Closing PR #62901 and removing active labels.
Radar WebKit Bug Importer
Comment 5 2026-04-21 16:06:14 PDT
Note You need to log in before you can comment on or make changes to this bug.