Skip to content

Commit efbf14a

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 9a8c12f commit efbf14a

File tree

3 files changed

+20
-35
lines changed

3 files changed

+20
-35
lines changed

system/include/emscripten/val.h

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

719719
constexpr static std::size_t STATE_PROMISE = 0;
720720
constexpr static std::size_t STATE_CORO = 1;
721721
constexpr static std::size_t STATE_RESULT = 2;
722-
constexpr static std::size_t STATE_ERROR = 3;
723722

724723
public:
725724
awaiter(const val& promise)
@@ -732,7 +731,8 @@ class val::awaiter {
732731
bool await_ready() { return false; }
733732

734733
// 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))`.
734+
// a rough equivalent of
735+
// `promise.then(value => this.resume_with(value)).catch(error => this.reject_with(error))`.
736736
void await_suspend(std::coroutine_handle<val::promise_type> handle) {
737737
internal::_emval_coro_suspend(std::get<STATE_PROMISE>(state).as_handle(), this);
738738
state.emplace<STATE_CORO>(handle);
@@ -746,18 +746,14 @@ class val::awaiter {
746746
coro.resume();
747747
}
748748

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-
}
749+
// When JS invokes `reject_with` with some error value, reject currently suspended
750+
// coroutine's promise with that error value and destroy coroutine frame, because
751+
// in this case coroutine never reaches final_suspend point to be destroyed automatically.
752+
void reject_with(val&& error);
754753

755754
// `await_resume` finalizes the awaiter and should return the result
756755
// of the `co_await ...` expression - in our case, the stored value.
757756
val await_resume() {
758-
if (state.index() == STATE_ERROR) {
759-
throw std::get<STATE_ERROR>(state);
760-
}
761757
return std::move(std::get<STATE_RESULT>(state));
762758
}
763759
};
@@ -803,12 +799,25 @@ class val::promise_type {
803799
}
804800
}
805801

802+
// Reject the stored promise due to rejection deeper in the call chain
803+
void reject_with(val&& error) {
804+
reject(std::move(error));
805+
}
806+
806807
// Resolve the stored promise on `co_return value`.
807808
template<typename T>
808809
void return_value(T&& value) {
809810
resolve(std::forward<T>(value));
810811
}
811812
};
813+
814+
inline void val::awaiter::reject_with(val&& error) {
815+
auto coro = std::move(std::get<STATE_CORO>(state));
816+
auto& promise = coro.promise();
817+
promise.reject_with(std::move(error));
818+
coro.destroy();
819+
}
820+
812821
#endif
813822

814823
// 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
@@ -7509,14 +7509,6 @@ def test_embind_val_coro_propogate_js_error(self):
75097509
self.emcc_args += ['-std=c++20', '--bind', '--post-js=post.js', '-fexceptions']
75107510
self.do_runf('embind/test_val_coro.cpp', 'rejected with: bang from JS promise!\n')
75117511

7512-
def test_embind_val_coro_caught_exception(self):
7513-
self.set_setting('EXCEPTION_STACK_TRACES')
7514-
create_file('post.js', r'''Module.onRuntimeInitialized = () => {
7515-
Module.caughtException().then(console.log);
7516-
}''')
7517-
self.emcc_args += ['-std=c++20', '--bind', '--post-js=post.js', '-fexceptions']
7518-
self.do_runf('embind/test_val_coro.cpp', '42\n')
7519-
75207512
def test_embind_dynamic_initialization(self):
75217513
self.emcc_args += ['-lembind']
75227514
self.do_run_in_out_file_test('embind/test_dynamic_initialization.cpp')

0 commit comments

Comments
 (0)