@@ -107,6 +107,7 @@ bool _emval_is_number(EM_VAL object);
107107bool _emval_is_string (EM_VAL object);
108108bool _emval_in (EM_VAL item, EM_VAL object);
109109bool _emval_delete (EM_VAL object, EM_VAL property);
110+ bool _emval_is_catchable_cpp_exception_object (EM_VAL object);
110111[[noreturn]] bool _emval_throw (EM_VAL object);
111112EM_VAL _emval_await (EM_VAL promise);
112113EM_VAL _emval_iter_begin (EM_VAL iterable);
@@ -670,40 +671,63 @@ inline val::iterator val::begin() const {
670671// of the type of the parent coroutine).
671672// This one is used for Promises represented by the `val` type.
672673class val ::awaiter {
674+ struct state_promise { val promise; };
675+ struct state_coro {
676+ std::coroutine_handle<> handle;
677+ // Is std::coroutine_handle<val::promise_type>?
678+ // In other words, are we also enclosed by a JS Promise?
679+ bool is_val_promise = false ;
680+ };
681+ struct state_result { val result; };
682+ struct state_error { val error; };
683+
673684 // State machine holding awaiter's current state. One of:
674- // - initially created with promise
675- // - waiting with a given coroutine handle
676- // - completed with a result
677- std::variant<val, std::coroutine_handle<val::promise_type>, val> state;
685+ std::variant<
686+ state_promise, // Initially created with the JS Promise we're awaiting
687+ state_coro, // Waiting with a given coroutine handle
688+ state_result, // Resolved with result
689+ state_error // Rejected with error
690+ > state;
678691
679- constexpr static std::size_t STATE_PROMISE = 0 ;
680- constexpr static std::size_t STATE_CORO = 1 ;
681- constexpr static std::size_t STATE_RESULT = 2 ;
692+ void await_suspend_impl (state_coro coro) {
693+ // Use get_if instead of get because we want it to work with exceptions disabled.
694+ auto * promise_ptr = std::get_if<state_promise>(&state);
695+ assert (promise_ptr && " Invalid awaiter state: expected JS Promise. An awaiter cannot be awaited multiple times." );
696+ internal::_emval_coro_suspend (promise_ptr->promise .as_handle (), this );
697+ state.emplace <state_coro>(coro);
698+ }
682699
683700public:
684- awaiter (const val& promise)
685- : state(std::in_place_index< STATE_PROMISE >, promise) {}
701+ awaiter (val promise)
702+ : state(std::in_place_type<state_promise >, std::move( promise) ) {}
686703
687704 // just in case, ensure nobody moves / copies this type around
688- awaiter (awaiter&&) = delete ;
705+ awaiter (const awaiter&) = delete ;
706+ awaiter& operator =(const awaiter&) = delete ;
689707
690708 // Promises don't have a synchronously accessible "ready" state.
691- bool await_ready () { return false ; }
709+ bool await_ready () const { return false ; }
692710
693711 // On suspend, store the coroutine handle and invoke a helper that will do
694712 // a rough equivalent of
695713 // `promise.then(value => this.resume_with(value)).catch(error => this.reject_with(error))`.
714+
696715 void await_suspend (std::coroutine_handle<val::promise_type> handle) {
697- internal::_emval_coro_suspend (std::get<STATE_PROMISE >(state).as_handle (), this );
698- state.emplace <STATE_CORO >(handle);
716+ await_suspend_impl ({handle, true });
717+ }
718+
719+ void await_suspend (std::coroutine_handle<> handle) {
720+ await_suspend_impl ({handle, false });
699721 }
700722
701723 // When JS invokes `resume_with` with some value, store that value and resume
702724 // the coroutine.
703725 void resume_with (val&& result) {
704- auto coro = std::move (std::get<STATE_CORO >(state));
705- state.emplace <STATE_RESULT >(std::move (result));
706- coro.resume ();
726+ auto * coro_ptr = std::get_if<state_coro>(&state);
727+ assert (coro_ptr && " Invalid awaiter state: expected suspended coroutine handle." );
728+ auto coro = *coro_ptr;
729+ state.emplace <state_result>(std::move (result));
730+ coro.handle .resume ();
707731 }
708732
709733 // When JS invokes `reject_with` with some error value, reject currently suspended
@@ -714,7 +738,13 @@ class val::awaiter {
714738 // `await_resume` finalizes the awaiter and should return the result
715739 // of the `co_await ...` expression - in our case, the stored value.
716740 val await_resume () {
717- return std::move (std::get<STATE_RESULT >(state));
741+ if (auto * result = std::get_if<state_result>(&state)) {
742+ return std::move (result->result );
743+ }
744+ // If a JS exception ended up here, it will be uncaught as C++ code cannot catch it
745+ auto * error_ptr = std::get_if<state_error>(&state);
746+ assert (error_ptr && " Invalid awaiter state: expected result or error." );
747+ error_ptr->error .throw_ ();
718748 }
719749};
720750
@@ -746,12 +776,10 @@ class val::promise_type {
746776 auto initial_suspend () noexcept { return std::suspend_never{}; }
747777 auto final_suspend () noexcept { return std::suspend_never{}; }
748778
749- // When exceptions are disabled we don't define unhandled_exception and rely
750- // on the default terminate behavior.
751- #ifdef __cpp_exceptions
752779 // On an unhandled exception, reject the stored promise instead of throwing
753780 // it asynchronously where it can't be handled.
754781 void unhandled_exception () {
782+ #ifdef __cpp_exceptions
755783 try {
756784 std::rethrow_exception (std::current_exception ());
757785 } catch (const val& error) {
@@ -760,8 +788,10 @@ class val::promise_type {
760788 val error = val (internal::_emval_from_current_cxa_exception ());
761789 reject (error);
762790 }
763- }
791+ #else
792+ std::terminate ();
764793#endif
794+ }
765795
766796 // Reject the stored promise due to rejection deeper in the call chain
767797 void reject_with (val&& error) {
@@ -776,10 +806,23 @@ class val::promise_type {
776806};
777807
778808inline void val::awaiter::reject_with (val&& error) {
779- auto coro = std::move (std::get<STATE_CORO >(state));
780- auto & promise = coro.promise ();
781- promise.reject_with (std::move (error));
782- coro.destroy ();
809+ auto * coro_ptr = std::get_if<state_coro>(&state);
810+ assert (coro_ptr && " Invalid awaiter state: expected suspended coroutine handle." );
811+ auto coro = *coro_ptr;
812+
813+ if (coro.is_val_promise ) {
814+ if (!internal::_emval_is_catchable_cpp_exception_object (error.as_handle ())) {
815+ // C++ code cannot catch JS exceptions.
816+ // Thus, we can just reject an enclosing JS Promise.
817+ auto & promise = std::coroutine_handle<promise_type>::from_address (coro.handle .address ()).promise ();
818+ promise.reject_with (std::move (error));
819+ coro.handle .destroy ();
820+ return ;
821+ }
822+ }
823+
824+ state.emplace <state_error>(std::move (error));
825+ coro.handle .resume ();
783826}
784827
785828#endif
0 commit comments