@@ -82,7 +82,8 @@ const kPropertyArrayHashFieldMax: constexpr int31
8282
8383transitioning macro PromiseAllResolveElementClosure<F: type>(
8484 implicit context: Context)(
85- value: JSAny, function: JSFunction, wrapResultFunctor: F): JSAny {
85+ value: JSAny, function: JSFunction, wrapResultFunctor: F,
86+ hasResolveAndRejectClosures: constexpr bool): JSAny {
8687 // We use the {function}s context as the marker to remember whether this
8788 // resolve element closure was already called. It points to the resolve
8889 // element context (which is a FunctionContext) until it was called the
@@ -98,10 +99,6 @@ transitioning macro PromiseAllResolveElementClosure<F: type>(
9899 const nativeContext = LoadNativeContext(context);
99100 function.context = nativeContext;
100101
101- // Update the value depending on whether Promise.all or
102- // Promise.allSettled is called.
103- const updatedValue = wrapResultFunctor.Call(nativeContext, value);
104-
105102 // Determine the index from the {function}.
106103 assert(kPropertyArrayNoHashSentinel == 0);
107104 const identityHash =
@@ -116,13 +113,41 @@ transitioning macro PromiseAllResolveElementClosure<F: type>(
116113 const elements = UnsafeCast<FixedArray>(valuesArray.elements);
117114 const valuesLength = Convert<intptr>(valuesArray.length);
118115 if (index < valuesLength) {
119- // The {index} is in bounds of the {values_array},
120- // just store the {value} and continue.
116+ // The {index} is in bounds of the {values_array}, check if this element has
117+ // already been resolved, and store the {value} if not.
118+ //
119+ // Promise.allSettled, for each input element, has both a resolve and a
120+ // reject closure that share an [[AlreadyCalled]] boolean. That is, the
121+ // input element can only be settled once: after resolve is called, reject
122+ // returns early, and vice versa. Using {function}'s context as the marker
123+ // only tracks per-closure instead of per-element. When the second
124+ // resolve/reject closure is called on the same index, values.object[index]
125+ // will already exist and will not be the hole value. In that case, return
126+ // early. Everything up to this point is not yet observable to user code.
127+ // This is not a problem for Promise.all since Promise.all has a single
128+ // resolve closure (no reject) per element.
129+ if (hasResolveAndRejectClosures) {
130+ if (elements.objects[index] != TheHole) deferred {
131+ return Undefined;
132+ }
133+ }
134+
135+ // Update the value depending on whether Promise.all or
136+ // Promise.allSettled is called.
137+ const updatedValue = wrapResultFunctor.Call(nativeContext, value);
121138 elements.objects[index] = updatedValue;
122139 } else {
123140 // Check if we need to grow the backing store.
141+ //
142+ // There's no need to check if this element has already been resolved for
143+ // Promise.allSettled if {values_array} has not yet grown to the index.
124144 const newLength = index + 1;
125145 const elementsLength = elements.length_intptr;
146+
147+ // Update the value depending on whether Promise.all or
148+ // Promise.allSettled is called.
149+ const updatedValue = wrapResultFunctor.Call(nativeContext, value);
150+
126151 if (index < elementsLength) {
127152 // The {index} is within bounds of the {elements} backing store, so
128153 // just store the {value} and update the "length" of the {values_array}.
@@ -166,22 +191,22 @@ PromiseAllResolveElementClosure(
166191 js-implicit context: Context, receiver: JSAny,
167192 target: JSFunction)(value: JSAny): JSAny {
168193 return PromiseAllResolveElementClosure(
169- value, target, PromiseAllWrapResultAsFulfilledFunctor{});
194+ value, target, PromiseAllWrapResultAsFulfilledFunctor{}, false );
170195}
171196
172197transitioning javascript builtin
173198PromiseAllSettledResolveElementClosure(
174199 js-implicit context: Context, receiver: JSAny,
175200 target: JSFunction)(value: JSAny): JSAny {
176201 return PromiseAllResolveElementClosure(
177- value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{});
202+ value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{}, true );
178203}
179204
180205transitioning javascript builtin
181206PromiseAllSettledRejectElementClosure(
182207 js-implicit context: Context, receiver: JSAny,
183208 target: JSFunction)(value: JSAny): JSAny {
184209 return PromiseAllResolveElementClosure(
185- value, target, PromiseAllSettledWrapResultAsRejectedFunctor{});
210+ value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}, true );
186211}
187212}
0 commit comments