diff --git a/promise.hpp b/promise.hpp index 7370c39..3eb8a4c 100644 --- a/promise.hpp +++ b/promise.hpp @@ -25,31 +25,92 @@ namespace promise_hpp class promise; // - // make_promise + // is_promise // - template < typename R, typename F > - promise make_promise(F&& f) { - promise result; + namespace impl + { + template < typename T > + struct is_promise_impl + : std::false_type {}; - auto resolver = std::bind([](promise& p, auto&& v){ - p.resolve(std::forward(v)); - }, result, std::placeholders::_1); + template < typename R > + struct is_promise_impl> + : std::true_type {}; + } - auto rejector = std::bind([](promise& p, auto&& e){ - p.reject(std::forward(e)); - }, result, std::placeholders::_1); + template < typename T > + struct is_promise + : impl::is_promise_impl> {}; - try { - invoke_hpp::invoke( - std::forward(f), - std::move(resolver), - std::move(rejector)); - } catch (...) { - result.reject(std::current_exception()); - } + // + // is_promise_r + // - return result; + namespace impl + { + template < typename R, typename T > + struct is_promise_r_impl + : std::false_type {}; + + template < typename R, typename PR > + struct is_promise_r_impl> + : std::is_convertible {}; + } + + template < typename R, typename T > + struct is_promise_r + : impl::is_promise_r_impl> {}; + + // + // detail + // + + namespace detail + { + class noncopyable { + public: + noncopyable(const noncopyable&) = delete; + noncopyable& operator=(const noncopyable&) = delete; + protected: + noncopyable() = default; + ~noncopyable() = default; + }; + + template < typename T > + class storage final : private noncopyable { + public: + storage() = default; + + ~storage() noexcept(std::is_nothrow_destructible::value) { + if ( initialized_ ) { + ptr_()->~T(); + } + } + + template < typename U > + void set(U&& value) noexcept(std::is_nothrow_constructible::value) { + assert(!initialized_); + ::new(ptr_()) T(std::forward(value)); + initialized_ = true; + } + + const T& value() const noexcept { + assert(initialized_); + return *ptr_(); + } + private: + T* ptr_() noexcept { + return reinterpret_cast(&data_); + } + + const T* ptr_() const noexcept { + return reinterpret_cast(&data_); + } + private: + std::aligned_storage_t data_; + bool initialized_ = false; + }; } // @@ -59,6 +120,8 @@ namespace promise_hpp template < typename T > class promise final { public: + using value_type = T; + enum class status : std::uint8_t { pending, resolved, @@ -70,7 +133,64 @@ namespace promise_hpp template < typename ResolveF , typename ResolveFR = invoke_hpp::invoke_result_t > - promise then(ResolveF&& on_resolve) { + std::enable_if_t< + is_promise::value && !std::is_void::value, + promise> + then(ResolveF&& on_resolve) { + promise next; + + then([ + n = next, + f = std::forward(on_resolve) + ](const T& v) mutable { + auto np = invoke_hpp::invoke( + std::forward(f), + v); + np.then([n = n](const typename ResolveFR::value_type& nv) mutable { + n.resolve(nv); + }).fail([n = n](std::exception_ptr e) mutable { + n.reject(e); + }); + }).fail([n = next](std::exception_ptr e) mutable { + n.reject(e); + }); + + return next; + } + + template < typename ResolveF + , typename ResolveFR = invoke_hpp::invoke_result_t > + std::enable_if_t< + is_promise::value && std::is_void::value, + promise> + then(ResolveF&& on_resolve) { + promise next; + + then([ + n = next, + f = std::forward(on_resolve) + ](const T& v) mutable { + auto np = invoke_hpp::invoke( + std::forward(f), + v); + np.then([n = n]() mutable { + n.resolve(); + }).fail([n = n](std::exception_ptr e) mutable { + n.reject(e); + }); + }).fail([n = next](std::exception_ptr e) mutable { + n.reject(e); + }); + + return next; + } + + template < typename ResolveF + , typename ResolveFR = invoke_hpp::invoke_result_t > + std::enable_if_t< + !is_promise::value, + promise> + then(ResolveF&& on_resolve) { promise next; state_->attach( next, @@ -79,18 +199,6 @@ namespace promise_hpp return next; } - template < typename ResolveF - , typename RejectF - , typename ResolveFR = invoke_hpp::invoke_result_t > - promise then(ResolveF&& on_resolve, RejectF&& on_reject) { - promise next; - state_->attach( - next, - std::forward(on_resolve), - std::forward(on_reject)); - return next; - } - template < typename RejectF > promise fail(RejectF&& on_reject) { promise next; @@ -121,50 +229,10 @@ namespace promise_hpp class state; std::shared_ptr state_; private: - class storage final { - public: - storage() = default; - - storage(const storage&) = delete; - storage& operator=(const storage&) = delete; - - ~storage() noexcept(std::is_nothrow_destructible::value) { - if ( initialized_ ) { - ptr_()->~T(); - } - } - - template < typename U > - void set(U&& value) { - assert(!initialized_); - ::new(ptr_()) T(std::forward(value)); - initialized_ = true; - } - - const T& value() const noexcept { - assert(initialized_); - return *ptr_(); - } - private: - T* ptr_() noexcept { - return reinterpret_cast(&data_); - } - - const T* ptr_() const noexcept { - return reinterpret_cast(&data_); - } - private: - std::aligned_storage_t data_; - bool initialized_ = false; - }; - - class state final { + class state final : private detail::noncopyable { public: state() = default; - state(const state&) = delete; - state& operator=(const state&) = delete; - template < typename U > void resolve(U&& value) { std::lock_guard guard(mutex_); @@ -188,30 +256,45 @@ namespace promise_hpp template < typename U, typename ResolveF, typename RejectF > std::enable_if_t::value, void> - attach(promise& other, ResolveF&& resolve, RejectF&& reject) { - auto resolve_h = std::bind([](promise& p, const ResolveF& f, const T& v){ + attach(promise& next, ResolveF&& resolve, RejectF&& reject) { + auto resolve_h = [ + n = next, + f = std::forward(resolve) + ](const T& v) mutable { try { - p.resolve(invoke_hpp::invoke(f, v)); + auto r = invoke_hpp::invoke( + std::forward(f), + v); + n.resolve(std::move(r)); } catch (...) { - p.reject(std::current_exception()); + n.reject(std::current_exception()); } - }, other, std::forward(resolve), std::placeholders::_1); + }; - auto reject_h = std::bind([](promise& p, const RejectF& f, std::exception_ptr e){ + auto reject_h = [ + n = next, + f = std::forward(reject) + ](std::exception_ptr e) mutable { try { - invoke_hpp::invoke(f, e); - p.reject(e); + invoke_hpp::invoke( + std::forward(f), + e); + n.reject(e); } catch (...) { - p.reject(std::current_exception()); + n.reject(std::current_exception()); } - }, other, std::forward(reject), std::placeholders::_1); + }; std::lock_guard guard(mutex_); if ( status_ == status::resolved ) { - resolve_h(storage_.value()); + invoke_hpp::invoke( + std::move(resolve_h), + storage_.value()); } else if ( status_ == status::rejected ) { - reject_h(exception_); + invoke_hpp::invoke( + std::move(reject_h), + exception_); } else { handlers_.emplace_back( std::move(resolve_h), @@ -221,31 +304,45 @@ namespace promise_hpp template < typename U, typename ResolveF, typename RejectF > std::enable_if_t::value, void> - attach(promise& other, ResolveF&& resolve, RejectF&& reject) { - auto resolve_h = std::bind([](promise& p, const ResolveF& f, const T& v){ + attach(promise& next, ResolveF&& resolve, RejectF&& reject) { + auto resolve_h = [ + n = next, + f = std::forward(resolve) + ](const T& v) mutable { try { - invoke_hpp::invoke(f, v); - p.resolve(); + invoke_hpp::invoke( + std::forward(f), + v); + n.resolve(); } catch (...) { - p.reject(std::current_exception()); + n.reject(std::current_exception()); } - }, other, std::forward(resolve), std::placeholders::_1); + }; - auto reject_h = std::bind([](promise& p, const RejectF& f, std::exception_ptr e){ + auto reject_h = [ + n = next, + f = std::forward(reject) + ](std::exception_ptr e) mutable { try { - invoke_hpp::invoke(f, e); - p.reject(e); + invoke_hpp::invoke( + std::forward(f), + e); + n.reject(e); } catch (...) { - p.reject(std::current_exception()); + n.reject(std::current_exception()); } - }, other, std::forward(reject), std::placeholders::_1); + }; std::lock_guard guard(mutex_); if ( status_ == status::resolved ) { - resolve_h(storage_.value()); + invoke_hpp::invoke( + std::move(resolve_h), + storage_.value()); } else if ( status_ == status::rejected ) { - reject_h(exception_); + invoke_hpp::invoke( + std::move(reject_h), + exception_); } else { handlers_.emplace_back( std::move(resolve_h), @@ -268,7 +365,7 @@ namespace promise_hpp handlers_.clear(); } private: - storage storage_; + detail::storage storage_; status status_ = status::pending; std::exception_ptr exception_ = nullptr; @@ -298,6 +395,8 @@ namespace promise_hpp template <> class promise final { public: + using value_type = void; + enum class status : std::uint8_t { pending, resolved, @@ -309,7 +408,62 @@ namespace promise_hpp template < typename ResolveF , typename ResolveFR = invoke_hpp::invoke_result_t > - promise then(ResolveF&& on_resolve) { + std::enable_if_t< + is_promise::value && !std::is_void::value, + promise> + then(ResolveF&& on_resolve) { + promise next; + + then([ + n = next, + f = std::forward(on_resolve) + ]() mutable { + auto np = invoke_hpp::invoke( + std::forward(f)); + np.then([n = n](const typename ResolveFR::value_type& nv) mutable { + n.resolve(nv); + }).fail([n = n](std::exception_ptr e) mutable { + n.reject(e); + }); + }).fail([n = next](std::exception_ptr e) mutable { + n.reject(e); + }); + + return next; + } + + template < typename ResolveF + , typename ResolveFR = invoke_hpp::invoke_result_t > + std::enable_if_t< + is_promise::value && std::is_void::value, + promise> + then(ResolveF&& on_resolve) { + promise next; + + then([ + n = next, + f = std::forward(on_resolve) + ]() mutable { + auto np = invoke_hpp::invoke( + std::forward(f)); + np.then([n = n]() mutable { + n.resolve(); + }).fail([n = n](std::exception_ptr e) mutable { + n.reject(e); + }); + }).fail([n = next](std::exception_ptr e) mutable { + n.reject(e); + }); + + return next; + } + + template < typename ResolveF + , typename ResolveFR = invoke_hpp::invoke_result_t > + std::enable_if_t< + !is_promise::value, + promise> + then(ResolveF&& on_resolve) { promise next; state_->attach( next, @@ -318,18 +472,6 @@ namespace promise_hpp return next; } - template < typename ResolveF - , typename RejectF - , typename ResolveFR = invoke_hpp::invoke_result_t > - promise then(ResolveF&& on_resolve, RejectF&& on_reject) { - promise next; - state_->attach( - next, - std::forward(on_resolve), - std::forward(on_reject)); - return next; - } - template < typename RejectF > promise fail(RejectF&& on_reject) { promise next; @@ -359,13 +501,10 @@ namespace promise_hpp class state; std::shared_ptr state_; private: - class state final { + class state final : private detail::noncopyable { public: state() = default; - state(const state&) = delete; - state& operator=(const state&) = delete; - void resolve() { std::lock_guard guard(mutex_); if ( status_ != status::pending ) { @@ -387,30 +526,43 @@ namespace promise_hpp template < typename U, typename ResolveF, typename RejectF > std::enable_if_t::value, void> - attach(promise& other, ResolveF&& resolve, RejectF&& reject) { - auto resolve_h = std::bind([](promise& p, const ResolveF& f){ + attach(promise& next, ResolveF&& resolve, RejectF&& reject) { + auto resolve_h = [ + n = next, + f = std::forward(resolve) + ]() mutable { try { - p.resolve(invoke_hpp::invoke(f)); + auto r = invoke_hpp::invoke( + std::forward(f)); + n.resolve(std::move(r)); } catch (...) { - p.reject(std::current_exception()); + n.reject(std::current_exception()); } - }, other, std::forward(resolve)); + }; - auto reject_h = std::bind([](promise& p, const RejectF& f, std::exception_ptr e){ + auto reject_h = [ + n = next, + f = std::forward(reject) + ](std::exception_ptr e) mutable { try { - invoke_hpp::invoke(f, e); - p.reject(e); + invoke_hpp::invoke( + std::forward(f), + e); + n.reject(e); } catch (...) { - p.reject(std::current_exception()); + n.reject(std::current_exception()); } - }, other, std::forward(reject), std::placeholders::_1); + }; std::lock_guard guard(mutex_); if ( status_ == status::resolved ) { - resolve_h(); + invoke_hpp::invoke( + std::move(resolve_h)); } else if ( status_ == status::rejected ) { - reject_h(exception_); + invoke_hpp::invoke( + std::move(reject_h), + exception_); } else { handlers_.emplace_back( std::move(resolve_h), @@ -420,31 +572,43 @@ namespace promise_hpp template < typename U, typename ResolveF, typename RejectF > std::enable_if_t::value, void> - attach(promise& other, ResolveF&& resolve, RejectF&& reject) { - auto resolve_h = std::bind([](promise& p, const ResolveF& f){ + attach(promise& next, ResolveF&& resolve, RejectF&& reject) { + auto resolve_h = [ + n = next, + f = std::forward(resolve) + ]() mutable { try { - invoke_hpp::invoke(f); - p.resolve(); + invoke_hpp::invoke( + std::forward(f)); + n.resolve(); } catch (...) { - p.reject(std::current_exception()); + n.reject(std::current_exception()); } - }, other, std::forward(resolve)); + }; - auto reject_h = std::bind([](promise& p, const RejectF& f, std::exception_ptr e){ + auto reject_h = [ + n = next, + f = std::forward(reject) + ](std::exception_ptr e) mutable { try { - invoke_hpp::invoke(f, e); - p.reject(e); + invoke_hpp::invoke( + std::forward(f), + e); + n.reject(e); } catch (...) { - p.reject(std::current_exception()); + n.reject(std::current_exception()); } - }, other, std::forward(reject), std::placeholders::_1); + }; std::lock_guard guard(mutex_); if ( status_ == status::resolved ) { - resolve_h(); + invoke_hpp::invoke( + std::move(resolve_h)); } else if ( status_ == status::rejected ) { - reject_h(exception_); + invoke_hpp::invoke( + std::move(reject_h), + exception_); } else { handlers_.emplace_back( std::move(resolve_h), @@ -487,4 +651,63 @@ namespace promise_hpp std::vector handlers_; }; }; + + // + // make_promise + // + + template < typename R, typename F > + promise make_promise(F&& f) { + promise result; + + auto resolver = [ + p = result + ](auto&& v) mutable { + p.resolve(std::forward(v)); + }; + + auto rejector = [ + p = result + ](auto&& e) mutable { + p.reject(std::forward(e)); + }; + + try { + invoke_hpp::invoke( + std::forward(f), + std::move(resolver), + std::move(rejector)); + } catch (...) { + result.reject(std::current_exception()); + } + + return result; + } + + inline promise make_resolved_promise() { + promise result; + result.resolve(); + return result; + } + + template < typename R > + promise> make_resolved_promise(R&& v) { + promise> result; + result.resolve(std::forward(v)); + return result; + } + + template < typename E > + promise make_rejected_promise(E&& e) { + promise result; + result.reject(std::forward(e)); + return result; + } + + template < typename R, typename E > + promise make_rejected_promise(E&& e) { + promise result; + result.reject(std::forward(e)); + return result; + } } diff --git a/tests.cpp b/tests.cpp index cf705e6..8ce75c1 100644 --- a/tests.cpp +++ b/tests.cpp @@ -6,6 +6,9 @@ namespace pr = promise_hpp; namespace { + struct obj_t { + }; + bool check_hello_fail_exception(std::exception_ptr e) { try { std::rethrow_exception(e); @@ -17,6 +20,79 @@ namespace } } +TEST_CASE("is_promise") { + SECTION("positive") { + static_assert( + pr::is_promise>::value, + "unit test fail"); + static_assert( + pr::is_promise>::value, + "unit test fail"); + static_assert( + pr::is_promise>::value, + "unit test fail"); + + static_assert( + pr::is_promise>::value, + "unit test fail"); + static_assert( + pr::is_promise>::value, + "unit test fail"); + static_assert( + pr::is_promise>::value, + "unit test fail"); + } + SECTION("negative") { + static_assert( + !pr::is_promise&>::value, + "unit test fail"); + static_assert( + !pr::is_promise*>::value, + "unit test fail"); + static_assert( + !pr::is_promise&>::value, + "unit test fail"); + + static_assert( + !pr::is_promise::value, + "unit test fail"); + static_assert( + !pr::is_promise::value, + "unit test fail"); + static_assert( + !pr::is_promise::value, + "unit test fail"); + } +} + +TEST_CASE("is_promise_r") { + SECTION("positive") { + static_assert( + pr::is_promise_r>::value, + "unit test fail"); + static_assert( + pr::is_promise_r>::value, + "unit test fail"); + static_assert( + pr::is_promise_r>::value, + "unit test fail"); + } + SECTION("negative") { + static_assert( + !pr::is_promise_r>::value, + "unit test fail"); + static_assert( + !pr::is_promise_r>::value, + "unit test fail"); + static_assert( + !pr::is_promise_r>::value, + "unit test fail"); + static_assert( + !pr::is_promise_r::value, + "unit test fail"); + } +} + TEST_CASE("promise") { SECTION("resolved") { { @@ -38,16 +114,6 @@ TEST_CASE("promise") { }); REQUIRE(check_42_int == 42); } - { - int check_42_int = 0; - pr::promise() - .resolve(42) - .then([&check_42_int](int value){ - check_42_int = value; - }, [](std::exception_ptr){ - }); - REQUIRE(check_42_int == 42); - } { int check_84_int = 0; bool check_void_call = false; @@ -91,7 +157,7 @@ TEST_CASE("promise") { pr::promise() .reject(ee) .then([](int){ - }).fail([&call_fail_with_logic_error](const std::exception_ptr& e){ + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); @@ -102,7 +168,7 @@ TEST_CASE("promise") { pr::promise() .reject(std::make_exception_ptr(ee)) .then([](int){ - }).fail([&call_fail_with_logic_error](const std::exception_ptr& e){ + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ call_fail_with_logic_error = check_hello_fail_exception(e); }); REQUIRE(call_fail_with_logic_error); @@ -191,6 +257,42 @@ TEST_CASE("promise") { REQUIRE(call_fail_with_logic_error); } } + SECTION("make_resolved_promise") { + { + bool call_check = false; + pr::make_resolved_promise() + .then([&call_check]{ + call_check = true; + }); + REQUIRE(call_check); + } + { + int check_42_int = 0; + pr::make_resolved_promise(42) + .then([&check_42_int](int value){ + check_42_int = value; + }); + REQUIRE(check_42_int == 42); + } + } + SECTION("make_rejected_promise") { + { + bool call_fail_with_logic_error = false; + pr::make_rejected_promise(std::logic_error("hello fail")) + .fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + REQUIRE(call_fail_with_logic_error); + } + { + bool call_fail_with_logic_error = 0; + pr::make_rejected_promise(std::logic_error("hello fail")) + .fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + REQUIRE(call_fail_with_logic_error); + } + } SECTION("exceptions") { { bool not_call_then_on_reject = true; @@ -270,4 +372,186 @@ TEST_CASE("promise") { REQUIRE(pb_value == 21); } } + SECTION("chaining") { + { + int check_84_int = 0; + auto p1 = pr::make_resolved_promise(42); + auto p2 = pr::make_resolved_promise(84); + + p1.then([&p2](int v){ + return p2; + }).then([&check_84_int](int v2){ + check_84_int = v2; + }); + + REQUIRE(check_84_int == 84); + } + { + int check_84_int = 0; + auto p1 = pr::make_resolved_promise(); + auto p2 = pr::make_resolved_promise(84); + + p1.then([&p2](){ + return p2; + }).then([&check_84_int](int v){ + check_84_int = v; + }); + + REQUIRE(check_84_int == 84); + } + { + int check_84_int = 0; + auto p1 = pr::make_resolved_promise(42); + auto p2 = pr::make_resolved_promise(); + + p1.then([&p2](int v){ + return p2; + }).then([&check_84_int](){ + check_84_int = 84; + }); + + REQUIRE(check_84_int == 84); + } + { + int check_84_int = 0; + auto p1 = pr::make_resolved_promise(); + auto p2 = pr::make_resolved_promise(); + + p1.then([&p2](){ + return p2; + }).then([&check_84_int](){ + check_84_int = 84; + }); + + REQUIRE(check_84_int == 84); + } + } + SECTION("typed_chaining_fails") { + { + bool call_fail_with_logic_error = false; + auto p1 = pr::make_resolved_promise(42); + auto p2 = pr::make_resolved_promise(84); + + p1.then([&p2](int v){ + (void)v; + throw std::logic_error("hello fail"); + return p2; + }).then([&call_fail_with_logic_error](int v2){ + (void)v2; + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + + REQUIRE(call_fail_with_logic_error); + } + { + bool call_fail_with_logic_error = false; + auto p1 = pr::make_resolved_promise(42); + auto p2 = pr::make_resolved_promise(84); + + p1.then([&p2](int v){ + (void)v; + return p2; + }).then([&call_fail_with_logic_error](int v2){ + (void)v2; + throw std::logic_error("hello fail"); + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + + REQUIRE(call_fail_with_logic_error); + } + { + bool call_fail_with_logic_error = false; + auto p1 = pr::make_rejected_promise(std::logic_error("hello fail")); + auto p2 = pr::make_resolved_promise(84); + + p1.then([&p2](int v){ + (void)v; + return p2; + }).then([&call_fail_with_logic_error](int v2){ + (void)v2; + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + + REQUIRE(call_fail_with_logic_error); + } + { + bool call_fail_with_logic_error = false; + auto p1 = pr::make_resolved_promise(42); + auto p2 = pr::make_rejected_promise(std::logic_error("hello fail")); + + p1.then([&p2](int v){ + (void)v; + return p2; + }).then([&call_fail_with_logic_error](int v2){ + (void)v2; + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + + REQUIRE(call_fail_with_logic_error); + } + } + SECTION("void_chaining_fails") { + { + bool call_fail_with_logic_error = false; + auto p1 = pr::make_resolved_promise(); + auto p2 = pr::make_resolved_promise(); + + p1.then([&p2](){ + throw std::logic_error("hello fail"); + return p2; + }).then([&call_fail_with_logic_error](){ + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + + REQUIRE(call_fail_with_logic_error); + } + { + bool call_fail_with_logic_error = false; + auto p1 = pr::make_resolved_promise(); + auto p2 = pr::make_resolved_promise(); + + p1.then([&p2](){ + return p2; + }).then([&call_fail_with_logic_error](){ + throw std::logic_error("hello fail"); + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + + REQUIRE(call_fail_with_logic_error); + } + { + bool call_fail_with_logic_error = false; + auto p1 = pr::make_rejected_promise(std::logic_error("hello fail")); + auto p2 = pr::make_resolved_promise(); + + p1.then([&p2](){ + return p2; + }).then([&call_fail_with_logic_error](){ + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + + REQUIRE(call_fail_with_logic_error); + } + { + bool call_fail_with_logic_error = false; + auto p1 = pr::make_resolved_promise(); + auto p2 = pr::make_rejected_promise(std::logic_error("hello fail")); + + p1.then([&p2](){ + return p2; + }).then([&call_fail_with_logic_error](){ + }).fail([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + + REQUIRE(call_fail_with_logic_error); + } + } }