Promise combinator fast path for non-thenable elements
The ECMAScript spec mandates that Promise.all/allSettled/any/race call Promise.resolve(element) for each input, which checks for a callable .then property. For non-thenables this produces a fulfilled Promise whose sole purpose is to queue the downstream callback as a microtask — pure allocation overhead. The optimisation is only safe if the thenability check is performed without observable side effects.
This commit adds a fast path that skips allocating an intermediate Promise.resolve(value) JSPromise cell for provably non-thenable elements, instead queuing the resolver microtask directly. A new isNonThenable predicate gates the optimisation.
Significance
Benchmarks show 1.4–1.7x speedup for arrays of primitives or plain objects, but the isNonThenable predicate must be airtight: any false positive changes observable ECMAScript behaviour.
Audit directions
- Proxy objects. The spec requires
.thenproperty access to be observable. IfisNonThenableuses a structure check or fast-property lookup that bypasses Proxy interception, aProxy({ }, { get(t,p){ if(p==='then') return ()=>{}; } })would be misclassified. - Prototype chain thenability. Plain objects are non-thenable unless something on their prototype chain has
.then. The check must walk the full chain or use a structure-level guard invalidated wheneverthenis added anywhere onObject.prototypeor an intermediate prototype. - Non-callable
then. ECMAScript defines thenable as having a callable.then. An object withthen: 42is not thenable. The predicate must check callability, not mere presence. - Promise subclasses /
Symbol.speciesinteractions. Custom subclasses overridingresolvecould be affected if the fast path bypasses the subclass'sresolveentirely. - JIT compilation. If JSC later JIT-compiles paths through combinator hot loops and inlines
isNonThenable, ensure the type feedback and guards remain sound under deoptimisation.