@@ -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 =
@@ -123,6 +120,27 @@ transitioning macro PromiseAllResolveElementClosure<F: type>(
123120 context.elements[PromiseAllResolveElementContextSlots::
124121 kPromiseAllResolveElementValuesSlot] = values;
125122 }
123+
124+ // Promise.allSettled, for each input element, has both a resolve and a reject
125+ // closure that share an [[AlreadyCalled]] boolean. That is, the input element
126+ // can only be settled once: after resolve is called, reject returns early,
127+ // and vice versa. Using {function}'s context as the marker only tracks
128+ // per-closure instead of per-element. When the second resolve/reject closure
129+ // is called on the same index, values.object[index] will already exist and
130+ // will not be the hole value. In that case, return early. Everything up to
131+ // this point is not yet observable to user code. This is not a problem for
132+ // Promise.all since Promise.all has a single resolve closure (no reject) per
133+ // element.
134+ if (hasResolveAndRejectClosures) {
135+ if (values.objects[index] != TheHole) deferred {
136+ return Undefined;
137+ }
138+ }
139+
140+ // Update the value depending on whether Promise.all or
141+ // Promise.allSettled is called.
142+ const updatedValue = wrapResultFunctor.Call(nativeContext, value);
143+
126144 values.objects[index] = updatedValue;
127145
128146 remainingElementsCount = remainingElementsCount - 1;
@@ -148,22 +166,22 @@ PromiseAllResolveElementClosure(
148166 js-implicit context: Context, receiver: JSAny,
149167 target: JSFunction)(value: JSAny): JSAny {
150168 return PromiseAllResolveElementClosure(
151- value, target, PromiseAllWrapResultAsFulfilledFunctor{});
169+ value, target, PromiseAllWrapResultAsFulfilledFunctor{}, false );
152170}
153171
154172transitioning javascript builtin
155173PromiseAllSettledResolveElementClosure(
156174 js-implicit context: Context, receiver: JSAny,
157175 target: JSFunction)(value: JSAny): JSAny {
158176 return PromiseAllResolveElementClosure(
159- value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{});
177+ value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{}, true );
160178}
161179
162180transitioning javascript builtin
163181PromiseAllSettledRejectElementClosure(
164182 js-implicit context: Context, receiver: JSAny,
165183 target: JSFunction)(value: JSAny): JSAny {
166184 return PromiseAllResolveElementClosure(
167- value, target, PromiseAllSettledWrapResultAsRejectedFunctor{});
185+ value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}, true );
168186}
169187}
0 commit comments