From ccf713bf237a53679c5c7be24a4667647ece1d49 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Tue, 16 Nov 2021 02:41:18 +0330 Subject: LibJS: Spin the event loop while waiting for async completion in `await` --- Userland/Libraries/LibJS/Runtime/Completion.cpp | 38 ++++++++++++++++--------- 1 file changed, 25 insertions(+), 13 deletions(-) (limited to 'Userland/Libraries/LibJS/Runtime/Completion.cpp') diff --git a/Userland/Libraries/LibJS/Runtime/Completion.cpp b/Userland/Libraries/LibJS/Runtime/Completion.cpp index df29a93ab9..0376b5a481 100644 --- a/Userland/Libraries/LibJS/Runtime/Completion.cpp +++ b/Userland/Libraries/LibJS/Runtime/Completion.cpp @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -33,15 +34,15 @@ ThrowCompletionOr await(GlobalObject& global_object, Value value) auto& vm = global_object.vm(); // 1. Let asyncContext be the running execution context. - auto& async_context = vm.running_execution_context(); + // NOTE: This is not needed, as we don't suspend anything. // 2. Let promise be ? PromiseResolve(%Promise%, value). - auto* promise = TRY(promise_resolve(global_object, *global_object.promise_constructor(), value)); + auto* promise_object = TRY(promise_resolve(global_object, *global_object.promise_constructor(), value)); - bool success = false; + Optional success; Value result; // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: - auto fulfilled_closure = [&async_context, &success, &result](VM& vm, GlobalObject& global_object) -> ThrowCompletionOr { + auto fulfilled_closure = [&success, &result](VM& vm, GlobalObject&) -> ThrowCompletionOr { // a. Let prevContext be the running execution context. // b. Suspend prevContext. // FIXME: We don't have this concept yet. @@ -51,7 +52,7 @@ ThrowCompletionOr await(GlobalObject& global_object, Value value) result = vm.argument(0); // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. - TRY(vm.push_execution_context(async_context, global_object)); + // NOTE: This is not done, because we're not suspending anything (see above). // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. @@ -65,7 +66,7 @@ ThrowCompletionOr await(GlobalObject& global_object, Value value) auto on_fulfilled = NativeFunction::create(global_object, "", move(fulfilled_closure)); // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called: - auto rejected_closure = [&async_context, &success, &result](VM& vm, GlobalObject& global_object) -> ThrowCompletionOr { + auto rejected_closure = [&success, &result](VM& vm, GlobalObject&) -> ThrowCompletionOr { // a. Let prevContext be the running execution context. // b. Suspend prevContext. // FIXME: We don't have this concept yet. @@ -75,7 +76,7 @@ ThrowCompletionOr await(GlobalObject& global_object, Value value) result = vm.argument(0); // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. - TRY(vm.push_execution_context(async_context, global_object)); + // NOTE: This is not done, because we're not suspending anything (see above). // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. @@ -89,21 +90,32 @@ ThrowCompletionOr await(GlobalObject& global_object, Value value) auto on_rejected = NativeFunction::create(global_object, "", move(rejected_closure)); // 7. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected). - verify_cast(promise)->perform_then(on_fulfilled, on_rejected, {}); + auto* promise = verify_cast(promise_object); + promise->perform_then(on_fulfilled, on_rejected, {}); + + // FIXME: Since we don't support context suspension, we attempt to "wait" for the promise to resolve + // by letting the event loop spin until our promise is no longer pending, and then synchronously + // running all queued promise jobs. + // Note: This is not used by LibJS itself, and is performed for the embedder (i.e. LibWeb). + if (Core::EventLoop::has_been_instantiated()) + Core::EventLoop::current().spin_until([&] { return promise->state() != Promise::State::Pending; }); // 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. - vm.pop_execution_context(); + // NOTE: Since we don't push any EC, this step is not performed. // 9. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion completion, the following steps of the algorithm that invoked Await will be performed, with completion available. // 10. Return. // 11. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of asyncContext. - // FIXME: Since we don't support context suspension, we synchronously execute the promise + vm.run_queued_promise_jobs(); - if (success) + // Make sure that the promise _actually_ resolved. + // Note that this is checked down the chain (result.is_empty()) anyway, but let's make the source of the issue more clear. + VERIFY(success.has_value()); + + if (success.value()) return result; - else - return throw_completion(result); + return throw_completion(result); } } -- cgit v1.2.3