/src/serenity/Userland/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/TypeCasts.h> |
8 | | #include <LibJS/Runtime/AsyncFunctionDriverWrapper.h> |
9 | | #include <LibJS/Runtime/GlobalObject.h> |
10 | | #include <LibJS/Runtime/NativeFunction.h> |
11 | | #include <LibJS/Runtime/PromiseCapability.h> |
12 | | #include <LibJS/Runtime/PromiseConstructor.h> |
13 | | #include <LibJS/Runtime/VM.h> |
14 | | #include <LibJS/Runtime/ValueInlines.h> |
15 | | |
16 | | namespace JS { |
17 | | |
18 | | JS_DEFINE_ALLOCATOR(AsyncFunctionDriverWrapper); |
19 | | |
20 | | NonnullGCPtr<Promise> AsyncFunctionDriverWrapper::create(Realm& realm, GeneratorObject* generator_object) |
21 | 0 | { |
22 | 0 | auto top_level_promise = Promise::create(realm); |
23 | | // Note: The top_level_promise is also kept alive by this Wrapper |
24 | 0 | auto wrapper = realm.heap().allocate<AsyncFunctionDriverWrapper>(realm, realm, *generator_object, *top_level_promise); |
25 | | // Prime the generator: |
26 | | // This runs until the first `await value;` |
27 | 0 | wrapper->continue_async_execution(realm.vm(), js_undefined(), true, IsInitialExecution::Yes); |
28 | |
|
29 | 0 | return top_level_promise; |
30 | 0 | } |
31 | | |
32 | | AsyncFunctionDriverWrapper::AsyncFunctionDriverWrapper(Realm& realm, NonnullGCPtr<GeneratorObject> generator_object, NonnullGCPtr<Promise> top_level_promise) |
33 | 0 | : Promise(realm.intrinsics().promise_prototype()) |
34 | 0 | , m_generator_object(generator_object) |
35 | 0 | , m_top_level_promise(top_level_promise) |
36 | 0 | { |
37 | 0 | } |
38 | | |
39 | | // 27.7.5.3 Await ( value ), https://tc39.es/ecma262/#await |
40 | | ThrowCompletionOr<void> AsyncFunctionDriverWrapper::await(JS::Value value) |
41 | 0 | { |
42 | 0 | auto& vm = this->vm(); |
43 | 0 | auto& realm = *vm.current_realm(); |
44 | | |
45 | | // 1. Let asyncContext be the running execution context. |
46 | 0 | if (!m_suspended_execution_context) |
47 | 0 | m_suspended_execution_context = vm.running_execution_context().copy(); |
48 | | |
49 | | // 2. Let promise be ? PromiseResolve(%Promise%, value). |
50 | 0 | auto* promise_object = TRY(promise_resolve(vm, realm.intrinsics().promise_constructor(), value)); |
51 | | |
52 | | // 3. Let fulfilledClosure be a new Abstract Closure with parameters (v) that captures asyncContext and performs the |
53 | | // following steps when called: |
54 | 0 | auto fulfilled_closure = [this](VM& vm) -> ThrowCompletionOr<Value> { |
55 | 0 | auto value = vm.argument(0); |
56 | | |
57 | | // a. Let prevContext be the running execution context. |
58 | 0 | auto& prev_context = vm.running_execution_context(); |
59 | | |
60 | | // FIXME: b. Suspend prevContext. |
61 | | |
62 | | // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. |
63 | 0 | TRY(vm.push_execution_context(*m_suspended_execution_context, {})); |
64 | | |
65 | | // d. Resume the suspended evaluation of asyncContext using NormalCompletion(v) as the result of the operation that |
66 | | // suspended it. |
67 | 0 | continue_async_execution(vm, value, true); |
68 | | |
69 | | // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and |
70 | | // prevContext is the currently running execution context. |
71 | 0 | VERIFY(&vm.running_execution_context() == &prev_context); |
72 | | |
73 | | // f. Return undefined. |
74 | 0 | return js_undefined(); |
75 | 0 | }; |
76 | | |
77 | | // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). |
78 | 0 | auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1, ""); |
79 | | |
80 | | // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the |
81 | | // following steps when called: |
82 | 0 | auto rejected_closure = [this](VM& vm) -> ThrowCompletionOr<Value> { |
83 | 0 | auto reason = vm.argument(0); |
84 | | |
85 | | // a. Let prevContext be the running execution context. |
86 | 0 | auto& prev_context = vm.running_execution_context(); |
87 | | |
88 | | // FIXME: b. Suspend prevContext. |
89 | | |
90 | | // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. |
91 | 0 | TRY(vm.push_execution_context(*m_suspended_execution_context, {})); |
92 | | |
93 | | // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that |
94 | | // suspended it. |
95 | 0 | continue_async_execution(vm, reason, false); |
96 | | |
97 | | // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and |
98 | | // prevContext is the currently running execution context. |
99 | 0 | VERIFY(&vm.running_execution_context() == &prev_context); |
100 | | |
101 | | // f. Return undefined. |
102 | 0 | return js_undefined(); |
103 | 0 | }; |
104 | | |
105 | | // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). |
106 | 0 | auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1, ""); |
107 | | |
108 | | // 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected). |
109 | 0 | m_current_promise = verify_cast<Promise>(promise_object); |
110 | 0 | m_current_promise->perform_then(on_fulfilled, on_rejected, {}); |
111 | | |
112 | | // 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the |
113 | | // execution context stack as the running execution context. |
114 | | // NOTE: This is done later on for us in continue_async_execution. |
115 | | |
116 | | // NOTE: None of these are necessary. 10-12 are handled by step d of the above lambdas. |
117 | | // 9. Let callerContext be the running execution context. |
118 | | // 10. Resume callerContext passing empty. If asyncContext is ever resumed again, let completion be the Completion Record with which it is resumed. |
119 | | // 11. Assert: If control reaches here, then asyncContext is the running execution context again. |
120 | | // 12. Return completion. |
121 | 0 | return {}; |
122 | 0 | } |
123 | | |
124 | | void AsyncFunctionDriverWrapper::continue_async_execution(VM& vm, Value value, bool is_successful, IsInitialExecution is_initial_execution) |
125 | 0 | { |
126 | 0 | auto generator_result = is_successful |
127 | 0 | ? m_generator_object->resume(vm, value, {}) |
128 | 0 | : m_generator_object->resume_abrupt(vm, throw_completion(value), {}); |
129 | |
|
130 | 0 | auto result = [&, this]() -> ThrowCompletionOr<void> { |
131 | 0 | while (true) { |
132 | 0 | if (generator_result.is_throw_completion()) |
133 | 0 | return generator_result.throw_completion(); |
134 | | |
135 | 0 | auto result = generator_result.release_value(); |
136 | 0 | VERIFY(result.is_object()); |
137 | | |
138 | 0 | auto promise_value = TRY(result.get(vm, vm.names.value)); |
139 | |
|
140 | 0 | if (TRY(result.get(vm, vm.names.done)).to_boolean()) { |
141 | | // When returning a promise, we need to unwrap it. |
142 | 0 | if (promise_value.is_object() && is<Promise>(promise_value.as_object())) { |
143 | 0 | auto& returned_promise = static_cast<Promise&>(promise_value.as_object()); |
144 | 0 | if (returned_promise.state() == Promise::State::Fulfilled) { |
145 | 0 | m_top_level_promise->fulfill(returned_promise.result()); |
146 | 0 | return {}; |
147 | 0 | } |
148 | 0 | if (returned_promise.state() == Promise::State::Rejected) |
149 | 0 | return throw_completion(returned_promise.result()); |
150 | | |
151 | | // The promise is still pending but there's nothing more to do here. |
152 | 0 | return {}; |
153 | 0 | } |
154 | | |
155 | | // We hit a `return value;` |
156 | 0 | m_top_level_promise->fulfill(promise_value); |
157 | 0 | return {}; |
158 | 0 | } |
159 | | |
160 | | // We hit `await Promise` |
161 | 0 | auto await_result = this->await(promise_value); |
162 | 0 | if (await_result.is_throw_completion()) { |
163 | 0 | generator_result = m_generator_object->resume_abrupt(vm, await_result.release_error(), {}); |
164 | 0 | continue; |
165 | 0 | } |
166 | 0 | return {}; |
167 | 0 | } |
168 | 0 | }(); |
169 | |
|
170 | 0 | if (result.is_throw_completion()) { |
171 | 0 | m_top_level_promise->reject(result.throw_completion().value().value_or(js_undefined())); |
172 | 0 | } |
173 | | |
174 | | // For the initial execution, the execution context will be popped for us later on by ECMAScriptFunctionObject. |
175 | 0 | if (is_initial_execution == IsInitialExecution::No) |
176 | 0 | vm.pop_execution_context(); |
177 | 0 | } |
178 | | |
179 | | void AsyncFunctionDriverWrapper::visit_edges(Cell::Visitor& visitor) |
180 | 0 | { |
181 | 0 | Base::visit_edges(visitor); |
182 | 0 | visitor.visit(m_generator_object); |
183 | 0 | visitor.visit(m_top_level_promise); |
184 | 0 | if (m_current_promise) |
185 | 0 | visitor.visit(m_current_promise); |
186 | 0 | if (m_suspended_execution_context) |
187 | 0 | m_suspended_execution_context->visit_edges(visitor); |
188 | 0 | } |
189 | | |
190 | | } |