Skip to content

Commit 01c2570

Browse files
committed
LibJS: Annotate Promise implementation with spec comments
I wanted to do this for a long time. The guts of Promise are pretty complex, and it's easier to understand with the spec right next to it. Also found a couple of issues along the way :^)
1 parent df06552 commit 01c2570

File tree

7 files changed

+522
-40
lines changed

7 files changed

+522
-40
lines changed

Userland/Libraries/LibJS/Runtime/Promise.cpp

Lines changed: 163 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,24 @@ namespace JS {
2222
ThrowCompletionOr<Object*> promise_resolve(GlobalObject& global_object, Object& constructor, Value value)
2323
{
2424
auto& vm = global_object.vm();
25+
26+
// 1. If IsPromise(x) is true, then
2527
if (value.is_object() && is<Promise>(value.as_object())) {
28+
// a. Let xConstructor be ? Get(x, "constructor").
2629
auto value_constructor = TRY(value.as_object().get(vm.names.constructor));
30+
31+
// b. If SameValue(xConstructor, C) is true, return x.
2732
if (same_value(value_constructor, &constructor))
2833
return &static_cast<Promise&>(value.as_object());
2934
}
35+
36+
// 2. Let promiseCapability be ? NewPromiseCapability(C).
3037
auto promise_capability = TRY(new_promise_capability(global_object, &constructor));
38+
39+
// 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
3140
(void)TRY(vm.call(*promise_capability.resolve, js_undefined(), value));
41+
42+
// 4. Return promiseCapability.[[Promise]].
3243
return promise_capability.promise;
3344
}
3445

@@ -37,6 +48,7 @@ Promise* Promise::create(GlobalObject& global_object)
3748
return global_object.heap().allocate<Promise>(global_object, *global_object.promise_prototype());
3849
}
3950

51+
// 27.2 Promise Objects, https://tc39.es/ecma262/#sec-promise-objects
4052
Promise::Promise(Object& prototype)
4153
: Object(prototype)
4254
{
@@ -48,70 +60,147 @@ Promise::ResolvingFunctions Promise::create_resolving_functions()
4860
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / create_resolving_functions()]", this);
4961
auto& vm = this->vm();
5062

63+
// 1. Let alreadyResolved be the Record { [[Value]]: false }.
5164
auto* already_resolved = vm.heap().allocate_without_global_object<AlreadyResolved>();
5265

66+
// 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions.
67+
// 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions.
68+
// 4. Let resolve be ! CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »).
69+
// 5. Set resolve.[[Promise]] to promise.
70+
// 6. Set resolve.[[AlreadyResolved]] to alreadyResolved.
71+
5372
// 27.2.1.3.2 Promise Resolve Functions, https://tc39.es/ecma262/#sec-promise-resolve-functions
5473
auto* resolve_function = PromiseResolvingFunction::create(global_object(), *this, *already_resolved, [](auto& vm, auto& global_object, auto& promise, auto& already_resolved) {
5574
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Resolve function was called", &promise);
75+
76+
auto resolution = vm.argument(0);
77+
78+
// 1. Let F be the active function object.
79+
// 2. Assert: F has a [[Promise]] internal slot whose value is an Object.
80+
// 3. Let promise be F.[[Promise]].
81+
82+
// 4. Let alreadyResolved be F.[[AlreadyResolved]].
83+
// 5. If alreadyResolved.[[Value]] is true, return undefined.
5684
if (already_resolved.value) {
5785
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Promise is already resolved, returning undefined", &promise);
5886
return js_undefined();
5987
}
88+
89+
// 6. Set alreadyResolved.[[Value]] to true.
6090
already_resolved.value = true;
61-
auto resolution = vm.argument(0);
91+
92+
// 7. If SameValue(resolution, promise) is true, then
6293
if (resolution.is_object() && &resolution.as_object() == &promise) {
6394
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Promise can't be resolved with itself, rejecting with error", &promise);
95+
96+
// a. Let selfResolutionError be a newly created TypeError object.
6497
auto* self_resolution_error = TypeError::create(global_object, "Cannot resolve promise with itself");
98+
99+
// b. Return RejectPromise(promise, selfResolutionError).
65100
return promise.reject(self_resolution_error);
66101
}
102+
103+
// 8. If Type(resolution) is not Object, then
67104
if (!resolution.is_object()) {
105+
// a. Return FulfillPromise(promise, resolution).
68106
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Resolution is not an object, fulfilling with {}", &promise, resolution);
69107
return promise.fulfill(resolution);
70108
}
109+
110+
// 9. Let then be Get(resolution, "then").
71111
auto then = resolution.as_object().get(vm.names.then);
112+
113+
// 10. If then is an abrupt completion, then
72114
if (then.is_throw_completion()) {
115+
// a. Return RejectPromise(promise, then.[[Value]]).
73116
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Exception while getting 'then' property, rejecting with error", &promise);
74117
vm.clear_exception();
75118
vm.stop_unwind();
76119
return promise.reject(then.throw_completion().value());
77120
}
121+
122+
// 11. Let thenAction be then.[[Value]].
78123
auto then_action = then.release_value();
124+
125+
// 12. If IsCallable(thenAction) is false, then
79126
if (!then_action.is_function()) {
127+
// a. Return FulfillPromise(promise, resolution).
80128
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Then action is not a function, fulfilling with {}", &promise, resolution);
81129
return promise.fulfill(resolution);
82130
}
131+
132+
// 13. Let thenJobCallback be HostMakeJobCallback(thenAction).
83133
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Creating JobCallback for then action @ {}", &promise, &then_action.as_function());
84134
auto then_job_callback = make_job_callback(then_action.as_function());
135+
136+
// 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback).
85137
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Creating PromiseResolveThenableJob for thenable {}", &promise, resolution);
86138
auto* job = PromiseResolveThenableJob::create(global_object, promise, resolution, then_job_callback);
139+
140+
// 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
87141
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Enqueuing job @ {}", &promise, job);
88142
vm.enqueue_promise_job(*job);
143+
144+
// 16. Return undefined.
89145
return js_undefined();
90146
});
91147
resolve_function->define_direct_property(vm.names.name, js_string(vm, String::empty()), Attribute::Configurable);
92148

149+
// 7. Let stepsReject be the algorithm steps defined in Promise Reject Functions.
150+
// 8. Let lengthReject be the number of non-optional parameters of the function definition in Promise Reject Functions.
151+
// 9. Let reject be ! CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »).
152+
// 10. Set reject.[[Promise]] to promise.
153+
// 11. Set reject.[[AlreadyResolved]] to alreadyResolved.
154+
93155
// 27.2.1.3.1 Promise Reject Functions, https://tc39.es/ecma262/#sec-promise-reject-functions
94156
auto* reject_function = PromiseResolvingFunction::create(global_object(), *this, *already_resolved, [](auto& vm, auto&, auto& promise, auto& already_resolved) {
95157
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Reject function was called", &promise);
158+
159+
auto reason = vm.argument(0);
160+
161+
// 1. Let F be the active function object.
162+
// 2. Assert: F has a [[Promise]] internal slot whose value is an Object.
163+
// 3. Let promise be F.[[Promise]].
164+
165+
// 4. Let alreadyResolved be F.[[AlreadyResolved]].
166+
// 5. If alreadyResolved.[[Value]] is true, return undefined.
96167
if (already_resolved.value)
97168
return js_undefined();
169+
170+
// 6. Set alreadyResolved.[[Value]] to true.
98171
already_resolved.value = true;
99-
auto reason = vm.argument(0);
172+
173+
// 7. Return RejectPromise(promise, reason).
100174
return promise.reject(reason);
101175
});
102176
reject_function->define_direct_property(vm.names.name, js_string(vm, String::empty()), Attribute::Configurable);
103177

178+
// 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }.
104179
return { *resolve_function, *reject_function };
105180
}
106181

107182
// 27.2.1.4 FulfillPromise ( promise, value ), https://tc39.es/ecma262/#sec-fulfillpromise
108183
Value Promise::fulfill(Value value)
109184
{
110185
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / fulfill()]: Fulfilling promise with value {}", this, value);
186+
187+
// 1. Assert: The value of promise.[[PromiseState]] is pending.
111188
VERIFY(m_state == State::Pending);
112189
VERIFY(!value.is_empty());
113-
m_state = State::Fulfilled;
190+
191+
// 2. Let reactions be promise.[[PromiseFulfillReactions]].
192+
// NOTE: This is a noop, we do these steps in a slightly different order.
193+
194+
// 3. Set promise.[[PromiseResult]] to value.
114195
m_result = value;
196+
197+
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
198+
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
199+
200+
// 6. Set promise.[[PromiseState]] to fulfilled.
201+
m_state = State::Fulfilled;
202+
203+
// 7. Return TriggerPromiseReactions(reactions, value).
115204
trigger_reactions();
116205
m_fulfill_reactions.clear();
117206
m_reject_reactions.clear();
@@ -121,14 +210,31 @@ Value Promise::fulfill(Value value)
121210
// 27.2.1.7 RejectPromise ( promise, reason ), https://tc39.es/ecma262/#sec-rejectpromise
122211
Value Promise::reject(Value reason)
123212
{
213+
124214
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / reject()]: Rejecting promise with reason {}", this, reason);
215+
auto& vm = this->vm();
216+
217+
// 1. Assert: The value of promise.[[PromiseState]] is pending.
125218
VERIFY(m_state == State::Pending);
126219
VERIFY(!reason.is_empty());
127-
auto& vm = this->vm();
128-
m_state = State::Rejected;
220+
221+
// 2. Let reactions be promise.[[PromiseRejectReactions]].
222+
// NOTE: This is a noop, we do these steps in a slightly different order.
223+
224+
// 3. Set promise.[[PromiseResult]] to reason.
129225
m_result = reason;
226+
227+
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
228+
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
229+
230+
// 6. Set promise.[[PromiseState]] to rejected.
231+
m_state = State::Rejected;
232+
233+
// 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
130234
if (!m_is_handled)
131235
vm.promise_rejection_tracker(*this, RejectionOperation::Reject);
236+
237+
// 8. Return TriggerPromiseReactions(reactions, reason).
132238
trigger_reactions();
133239
m_fulfill_reactions.clear();
134240
m_reject_reactions.clear();
@@ -143,58 +249,104 @@ void Promise::trigger_reactions() const
143249
auto& reactions = m_state == State::Fulfilled
144250
? m_fulfill_reactions
145251
: m_reject_reactions;
252+
253+
// 1. For each element reaction of reactions, do
146254
for (auto& reaction : reactions) {
255+
// a. Let job be NewPromiseReactionJob(reaction, argument).
147256
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / trigger_reactions()]: Creating PromiseReactionJob for PromiseReaction @ {} with argument {}", this, &reaction, m_result);
148257
auto* job = PromiseReactionJob::create(global_object(), *reaction, m_result);
258+
259+
// b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
149260
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / trigger_reactions()]: Enqueuing job @ {}", this, job);
150261
vm.enqueue_promise_job(*job);
151262
}
263+
152264
if constexpr (PROMISE_DEBUG) {
153265
if (reactions.is_empty())
154266
dbgln("[Promise @ {} / trigger_reactions()]: No reactions!", this);
155267
}
268+
269+
// 2. Return undefined.
156270
}
157271

158272
// 27.2.5.4.1 PerformPromiseThen ( promise, onFulfilled, onRejected [ , resultCapability ] ), https://tc39.es/ecma262/#sec-performpromisethen
159273
Value Promise::perform_then(Value on_fulfilled, Value on_rejected, Optional<PromiseCapability> result_capability)
160274
{
161275
auto& vm = this->vm();
162276

277+
// 1. Assert: IsPromise(promise) is true.
278+
// 2. If resultCapability is not present, then
279+
// a. Set resultCapability to undefined.
280+
281+
// 3. If IsCallable(onFulfilled) is false, then
282+
// a. Let onFulfilledJobCallback be empty.
163283
Optional<JobCallback> on_fulfilled_job_callback;
284+
285+
// 4. Else,
164286
if (on_fulfilled.is_function()) {
287+
// a. Let onFulfilledJobCallback be HostMakeJobCallback(onFulfilled).
165288
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / perform_then()]: Creating JobCallback for on_fulfilled function @ {}", this, &on_fulfilled.as_function());
166289
on_fulfilled_job_callback = make_job_callback(on_fulfilled.as_function());
167290
}
168291

292+
// 5. If IsCallable(onRejected) is false, then
293+
// a. Let onRejectedJobCallback be empty.
169294
Optional<JobCallback> on_rejected_job_callback;
295+
296+
// 6. Else,
170297
if (on_rejected.is_function()) {
298+
// a. Let onRejectedJobCallback be HostMakeJobCallback(onRejected).
171299
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / perform_then()]: Creating JobCallback for on_rejected function @ {}", this, &on_rejected.as_function());
172300
on_rejected_job_callback = make_job_callback(on_rejected.as_function());
173301
}
174302

303+
// 7. Let fulfillReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Fulfill, [[Handler]]: onFulfilledJobCallback }.
175304
auto* fulfill_reaction = PromiseReaction::create(vm, PromiseReaction::Type::Fulfill, result_capability, on_fulfilled_job_callback);
305+
306+
// 8. Let rejectReaction be the PromiseReaction { [[Capability]]: resultCapability, [[Type]]: Reject, [[Handler]]: onRejectedJobCallback }.
176307
auto* reject_reaction = PromiseReaction::create(vm, PromiseReaction::Type::Reject, result_capability, on_rejected_job_callback);
177308

178309
switch (m_state) {
310+
// 9. If promise.[[PromiseState]] is pending, then
179311
case Promise::State::Pending:
180312
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / perform_then()]: state is State::Pending, adding fulfill/reject reactions", this);
313+
314+
// a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]].
181315
m_fulfill_reactions.append(fulfill_reaction);
316+
317+
// b. Append rejectReaction as the last element of the List that is promise.[[PromiseRejectReactions]].
182318
m_reject_reactions.append(reject_reaction);
183319
break;
320+
// 10. Else if promise.[[PromiseState]] is fulfilled, then
184321
case Promise::State::Fulfilled: {
322+
// a. Let value be promise.[[PromiseResult]].
185323
auto value = m_result;
324+
325+
// b. Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value).
186326
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / perform_then()]: State is State::Fulfilled, creating PromiseReactionJob for PromiseReaction @ {} with argument {}", this, fulfill_reaction, value);
187327
auto* fulfill_job = PromiseReactionJob::create(global_object(), *fulfill_reaction, value);
328+
329+
// c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]).
188330
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / perform_then()]: Enqueuing job @ {}", this, fulfill_job);
189331
vm.enqueue_promise_job(*fulfill_job);
190332
break;
191333
}
334+
// 11. Else,
192335
case Promise::State::Rejected: {
336+
// a. Assert: The value of promise.[[PromiseState]] is rejected.
337+
338+
// b. Let reason be promise.[[PromiseResult]].
193339
auto reason = m_result;
340+
341+
// c. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "handle").
194342
if (!m_is_handled)
195343
vm.promise_rejection_tracker(*this, RejectionOperation::Handle);
344+
345+
// d. Let rejectJob be NewPromiseReactionJob(rejectReaction, reason).
196346
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / perform_then()]: State is State::Rejected, creating PromiseReactionJob for PromiseReaction @ {} with argument {}", this, reject_reaction, reason);
197347
auto* reject_job = PromiseReactionJob::create(global_object(), *reject_reaction, reason);
348+
349+
// e. Perform HostEnqueuePromiseJob(rejectJob.[[Job]], rejectJob.[[Realm]]).
198350
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / perform_then()]: Enqueuing job @ {}", this, reject_job);
199351
vm.enqueue_promise_job(*reject_job);
200352
break;
@@ -203,12 +355,18 @@ Value Promise::perform_then(Value on_fulfilled, Value on_rejected, Optional<Prom
203355
VERIFY_NOT_REACHED();
204356
}
205357

358+
// 12. Set promise.[[PromiseIsHandled]] to true.
206359
m_is_handled = true;
207360

361+
// 13. If resultCapability is undefined, then
208362
if (!result_capability.has_value()) {
363+
// a. Return undefined.
209364
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / perform_then()]: No ResultCapability, returning undefined", this);
210365
return js_undefined();
211366
}
367+
368+
// 14. Else,
369+
// a. Return resultCapability.[[Promise]].
212370
auto* promise = result_capability.value().promise;
213371
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / perform_then()]: Returning Promise @ {} from ResultCapability @ {}", this, promise, &result_capability.value());
214372
return promise;

Userland/Libraries/LibJS/Runtime/Promise.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,12 @@ class Promise : public Object {
5353

5454
void trigger_reactions() const;
5555

56-
State m_state { State::Pending };
57-
Value m_result;
58-
Vector<PromiseReaction*> m_fulfill_reactions;
59-
Vector<PromiseReaction*> m_reject_reactions;
60-
bool m_is_handled { false };
56+
// 27.2.6 Properties of Promise Instances, https://tc39.es/ecma262/#sec-properties-of-promise-instances
57+
State m_state { State::Pending }; // [[PromiseState]]
58+
Value m_result; // [[PromiseResult]]
59+
Vector<PromiseReaction*> m_fulfill_reactions; // [[PromiseFulfillReactions]]
60+
Vector<PromiseReaction*> m_reject_reactions; // [[PromiseRejectReactions]]
61+
bool m_is_handled { false }; // [[PromiseIsHandled]]
6162
};
6263

6364
}

0 commit comments

Comments
 (0)