@@ -82,7 +82,8 @@ const kPropertyArrayHashFieldMax: constexpr int31
82
82
83
83
transitioning macro PromiseAllResolveElementClosure<F: type>(
84
84
implicit context: Context)(
85
- value: JSAny, function: JSFunction, wrapResultFunctor: F): JSAny {
85
+ value: JSAny, function: JSFunction, wrapResultFunctor: F,
86
+ hasResolveAndRejectClosures: constexpr bool): JSAny {
86
87
// We use the {function}s context as the marker to remember whether this
87
88
// resolve element closure was already called. It points to the resolve
88
89
// element context (which is a FunctionContext) until it was called the
@@ -98,10 +99,6 @@ transitioning macro PromiseAllResolveElementClosure<F: type>(
98
99
const nativeContext = LoadNativeContext(context);
99
100
function.context = nativeContext;
100
101
101
- // Update the value depending on whether Promise.all or
102
- // Promise.allSettled is called.
103
- const updatedValue = wrapResultFunctor.Call(nativeContext, value);
104
-
105
102
// Determine the index from the {function}.
106
103
assert(kPropertyArrayNoHashSentinel == 0);
107
104
const identityHash =
@@ -116,13 +113,41 @@ transitioning macro PromiseAllResolveElementClosure<F: type>(
116
113
const elements = UnsafeCast<FixedArray>(valuesArray.elements);
117
114
const valuesLength = Convert<intptr>(valuesArray.length);
118
115
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);
121
138
elements.objects[index] = updatedValue;
122
139
} else {
123
140
// 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.
124
144
const newLength = index + 1;
125
145
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
+
126
151
if (index < elementsLength) {
127
152
// The {index} is within bounds of the {elements} backing store, so
128
153
// just store the {value} and update the "length" of the {values_array}.
@@ -166,22 +191,22 @@ PromiseAllResolveElementClosure(
166
191
js-implicit context: Context, receiver: JSAny,
167
192
target: JSFunction)(value: JSAny): JSAny {
168
193
return PromiseAllResolveElementClosure(
169
- value, target, PromiseAllWrapResultAsFulfilledFunctor{});
194
+ value, target, PromiseAllWrapResultAsFulfilledFunctor{}, false );
170
195
}
171
196
172
197
transitioning javascript builtin
173
198
PromiseAllSettledResolveElementClosure(
174
199
js-implicit context: Context, receiver: JSAny,
175
200
target: JSFunction)(value: JSAny): JSAny {
176
201
return PromiseAllResolveElementClosure(
177
- value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{});
202
+ value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{}, true );
178
203
}
179
204
180
205
transitioning javascript builtin
181
206
PromiseAllSettledRejectElementClosure(
182
207
js-implicit context: Context, receiver: JSAny,
183
208
target: JSFunction)(value: JSAny): JSAny {
184
209
return PromiseAllResolveElementClosure(
185
- value, target, PromiseAllSettledWrapResultAsRejectedFunctor{});
210
+ value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}, true );
186
211
}
187
212
}
0 commit comments