Skip to content

Commit cd558c6

Browse files
committed
Propagate via rejections
- Propagate errors via promise rejections rather then exceptions to ensure consistent behaviour between normal and coroutine functions.
1 parent b17a172 commit cd558c6

File tree

3 files changed

+20
-36
lines changed

3 files changed

+20
-36
lines changed

system/include/emscripten/val.h

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -713,13 +713,11 @@ class val::awaiter {
713713
// - initially created with promise
714714
// - waiting with a given coroutine handle
715715
// - completed with a result
716-
// - rejected with an error
717-
std::variant<val, std::coroutine_handle<val::promise_type>, val, val> state;
716+
std::variant<val, std::coroutine_handle<val::promise_type>, val> state;
718717

719718
constexpr static std::size_t STATE_PROMISE = 0;
720719
constexpr static std::size_t STATE_CORO = 1;
721720
constexpr static std::size_t STATE_RESULT = 2;
722-
constexpr static std::size_t STATE_ERROR = 3;
723721

724722
public:
725723
awaiter(const val& promise)
@@ -732,7 +730,8 @@ class val::awaiter {
732730
bool await_ready() { return false; }
733731

734732
// On suspend, store the coroutine handle and invoke a helper that will do
735-
// a rough equivalent of `promise.then(value => this.resume_with(value))`.
733+
// a rough equivalent of
734+
// `promise.then(value => this.resume_with(value)).catch(error => this.reject_with(error))`.
736735
void await_suspend(std::coroutine_handle<val::promise_type> handle) {
737736
internal::_emval_coro_suspend(std::get<STATE_PROMISE>(state).as_handle(), this);
738737
state.emplace<STATE_CORO>(handle);
@@ -746,18 +745,14 @@ class val::awaiter {
746745
coro.resume();
747746
}
748747

749-
void reject_with(val&& error) {
750-
auto coro = std::move(std::get<STATE_CORO>(state));
751-
state.emplace<STATE_ERROR>(std::move(error));
752-
coro.resume();
753-
}
748+
// When JS invokes `reject_with` with some error value, reject currently suspended
749+
// coroutine's promise with the error value and destroy coroutine frame, because
750+
// in this scenario coroutine never reaches final_suspend point to be destroyed automatically.
751+
void reject_with(val&& error);
754752

755753
// `await_resume` finalizes the awaiter and should return the result
756754
// of the `co_await ...` expression - in our case, the stored value.
757755
val await_resume() {
758-
if (state.index() == STATE_ERROR) {
759-
throw std::get<STATE_ERROR>(state);
760-
}
761756
return std::move(std::get<STATE_RESULT>(state));
762757
}
763758
};
@@ -803,12 +798,25 @@ class val::promise_type {
803798
}
804799
}
805800

801+
// Reject the stored promise due to rejection deeper in the call chain
802+
void reject_with(val&& error) {
803+
reject(std::move(error));
804+
}
805+
806806
// Resolve the stored promise on `co_return value`.
807807
template<typename T>
808808
void return_value(T&& value) {
809809
resolve(std::forward<T>(value));
810810
}
811811
};
812+
813+
inline void val::awaiter::reject_with(val&& error) {
814+
auto coro = std::move(std::get<STATE_CORO>(state));
815+
auto& promise = coro.promise();
816+
promise.reject_with(std::move(error));
817+
coro.destroy();
818+
}
819+
812820
#endif
813821

814822
// Declare a custom type that can be used in conjunction with

test/embind/test_val_coro.cpp

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,8 @@ val failingPromise<0>() {
9494
co_return 65;
9595
}
9696

97-
val caughtException() {
98-
int result = 0;
99-
try {
100-
co_return co_await throwingCoro<2>();
101-
} catch (const val&) { // runtime_error turns into emscripten::val
102-
result += 21;
103-
}
104-
try {
105-
co_return co_await failingPromise<2>();
106-
} catch (const val&) {
107-
result += 21;
108-
}
109-
co_return result;
110-
}
111-
11297
EMSCRIPTEN_BINDINGS(test_val_coro) {
11398
function("asyncCoro", asyncCoro<3>);
11499
function("throwingCoro", throwingCoro<3>);
115100
function("failingPromise", failingPromise<3>);
116-
function("caughtException", caughtException);
117101
}

test/test_core.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7495,14 +7495,6 @@ def test_embind_val_coro_propogate_js_error(self):
74957495
self.emcc_args += ['-std=c++20', '--bind', '--post-js=post.js', '-fexceptions']
74967496
self.do_runf('embind/test_val_coro.cpp', 'rejected with: bang from JS promise!\n')
74977497

7498-
def test_embind_val_coro_caught_exception(self):
7499-
self.set_setting('EXCEPTION_STACK_TRACES')
7500-
create_file('post.js', r'''Module.onRuntimeInitialized = () => {
7501-
Module.caughtException().then(console.log);
7502-
}''')
7503-
self.emcc_args += ['-std=c++20', '--bind', '--post-js=post.js', '-fexceptions']
7504-
self.do_runf('embind/test_val_coro.cpp', '42\n')
7505-
75067498
def test_embind_dynamic_initialization(self):
75077499
self.emcc_args += ['-lembind']
75087500
self.do_run_in_out_file_test('embind/test_dynamic_initialization.cpp')

0 commit comments

Comments
 (0)