
Previously, throw and return completions would not be executed inside the generator. This is incorrect, as throw and return need to perform unwinds which can potentially execute more code inside the generator, such as finally blocks. This is done by also passing the completion type alongside the passed in value. The continuation block will immediately extract and type and value and perform the appropriate operation for the given type. For normal completions, this is continuing as normal. For throw completions, it will perform `throw <value>`. For return completions, it will perform `return <value>`, which is a `Yield return` in this case due to being inside a generator. This also refactors GeneratorObject to properly send across the completion type and value to the generator inside of trying to operate on the completions itself. This is a prerequisite for yield*, as it performs special iterator operations when receiving a throw/return completion and does not complete the generator like the regular yield would. There's still more work to be done to make GeneratorObject::execute be closer to the spec. It's mostly a restructuring of the existing GeneratorObject::next_impl.
75 lines
2.7 KiB
C++
75 lines
2.7 KiB
C++
/*
|
|
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/TypeCasts.h>
|
|
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
|
|
#include <LibJS/Runtime/GlobalObject.h>
|
|
#include <LibJS/Runtime/NativeFunction.h>
|
|
#include <LibJS/Runtime/PromiseCapability.h>
|
|
#include <LibJS/Runtime/VM.h>
|
|
|
|
namespace JS {
|
|
|
|
ThrowCompletionOr<Value> AsyncFunctionDriverWrapper::create(Realm& realm, GeneratorObject* generator_object)
|
|
{
|
|
auto wrapper = realm.heap().allocate<AsyncFunctionDriverWrapper>(realm, realm, generator_object);
|
|
return wrapper->react_to_async_task_completion(realm.vm(), js_undefined(), true);
|
|
}
|
|
|
|
AsyncFunctionDriverWrapper::AsyncFunctionDriverWrapper(Realm& realm, GeneratorObject* generator_object)
|
|
: Promise(*realm.intrinsics().promise_prototype())
|
|
, m_generator_object(generator_object)
|
|
, m_on_fulfillment(NativeFunction::create(realm, "async.on_fulfillment"sv, [this](VM& vm) {
|
|
return react_to_async_task_completion(vm, vm.argument(0), true);
|
|
}))
|
|
, m_on_rejection(NativeFunction::create(realm, "async.on_rejection"sv, [this](VM& vm) {
|
|
return react_to_async_task_completion(vm, vm.argument(0), false);
|
|
}))
|
|
{
|
|
}
|
|
|
|
ThrowCompletionOr<Value> AsyncFunctionDriverWrapper::react_to_async_task_completion(VM& vm, Value value, bool is_successful)
|
|
{
|
|
auto& realm = *vm.current_realm();
|
|
|
|
auto generator_result = is_successful
|
|
? m_generator_object->resume(vm, value, {})
|
|
: m_generator_object->resume_abrupt(vm, throw_completion(value), {});
|
|
|
|
if (generator_result.is_throw_completion()) {
|
|
VERIFY(generator_result.throw_completion().type() == Completion::Type::Throw);
|
|
auto promise = Promise::create(realm);
|
|
promise->reject(*generator_result.throw_completion().value());
|
|
return promise;
|
|
}
|
|
|
|
auto result = generator_result.release_value();
|
|
VERIFY(result.is_object());
|
|
|
|
auto promise_value = TRY(result.get(vm, vm.names.value));
|
|
if (!promise_value.is_object() || !is<Promise>(promise_value.as_object())) {
|
|
auto promise = Promise::create(realm);
|
|
promise->fulfill(promise_value);
|
|
return promise;
|
|
}
|
|
|
|
auto* promise = static_cast<Promise*>(&promise_value.as_object());
|
|
if (TRY(result.get(vm, vm.names.done)).to_boolean())
|
|
return promise;
|
|
|
|
auto promise_capability = PromiseCapability::create(vm, promise, m_on_fulfillment, m_on_rejection);
|
|
return promise->perform_then(m_on_fulfillment, m_on_rejection, promise_capability);
|
|
}
|
|
|
|
void AsyncFunctionDriverWrapper::visit_edges(Cell::Visitor& visitor)
|
|
{
|
|
Base::visit_edges(visitor);
|
|
visitor.visit(m_generator_object);
|
|
visitor.visit(m_on_fulfillment);
|
|
visitor.visit(m_on_rejection);
|
|
}
|
|
|
|
}
|