瀏覽代碼

LibJS: Spin the event loop while waiting for async completion in `await`

Ali Mohammad Pur 3 年之前
父節點
當前提交
ccf713bf23
共有 1 個文件被更改,包括 25 次插入13 次删除
  1. 25 13
      Userland/Libraries/LibJS/Runtime/Completion.cpp

+ 25 - 13
Userland/Libraries/LibJS/Runtime/Completion.cpp

@@ -6,6 +6,7 @@
  */
 
 #include <AK/TypeCasts.h>
+#include <LibCore/EventLoop.h>
 #include <LibJS/Runtime/Completion.h>
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/NativeFunction.h>
@@ -33,15 +34,15 @@ ThrowCompletionOr<Value> 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<bool> 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<Value> {
+    auto fulfilled_closure = [&success, &result](VM& vm, GlobalObject&) -> ThrowCompletionOr<Value> {
         // a. Let prevContext be the running execution context.
         // b. Suspend prevContext.
         // FIXME: We don't have this concept yet.
@@ -51,7 +52,7 @@ ThrowCompletionOr<Value> 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<Value> 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<Value> {
+    auto rejected_closure = [&success, &result](VM& vm, GlobalObject&) -> ThrowCompletionOr<Value> {
         // a. Let prevContext be the running execution context.
         // b. Suspend prevContext.
         // FIXME: We don't have this concept yet.
@@ -75,7 +76,7 @@ ThrowCompletionOr<Value> 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<Value> 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>(promise)->perform_then(on_fulfilled, on_rejected, {});
+    auto* promise = verify_cast<Promise>(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);
 }
 
 }