first basic implementation

This commit is contained in:
2018-12-09 00:41:58 +07:00
commit 3ef27c68e9
8 changed files with 15428 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
build/*
.vscode/*
CMakeLists.txt.user

12
CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(promise)
file(GLOB test_sources "*.cpp" "*.hpp")
add_executable(${PROJECT_NAME} ${test_sources})
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO)
enable_testing()
add_test(${PROJECT_NAME} ${PROJECT_NAME})

21
LICENSE.md Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Matvey Cherevko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

25
README.md Normal file
View File

@@ -0,0 +1,25 @@
# promise.hpp
[![language][badge.language]][language]
[![license][badge.license]][license]
[![paypal][badge.paypal]][paypal]
[badge.language]: https://img.shields.io/badge/language-C%2B%2B14-red.svg?style=for-the-badge
[badge.license]: https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge
[badge.paypal]: https://img.shields.io/badge/donate-PayPal-orange.svg?logo=paypal&colorA=00457C&style=for-the-badge
[language]: https://en.wikipedia.org/wiki/C%2B%2B14
[license]: https://en.wikipedia.org/wiki/MIT_License
[paypal]: https://www.paypal.me/matov
[promise]: https://github.com/BlackMATov/promise.hpp
## Installation
[promise.hpp][promise] is a single header library. All you need to do is copy the header file into your project and include this file:
```cpp
#include "promise.hpp"
```
## [License (MIT)](./LICENSE.md)

14362
catch.hpp Normal file

File diff suppressed because it is too large Load Diff

242
invoke.hpp Normal file
View File

@@ -0,0 +1,242 @@
#pragma once
#include <tuple>
#include <utility>
#include <functional>
#include <type_traits>
#define INVOKE_HPP_NOEXCEPT_RETURN(...) \
noexcept(noexcept(__VA_ARGS__)) { return __VA_ARGS__; }
#define INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(...) \
noexcept(noexcept(__VA_ARGS__)) -> decltype (__VA_ARGS__) { return __VA_ARGS__; }
//
// void_t
//
namespace invoke_hpp
{
namespace impl
{
template < typename... Args >
struct make_void {
using type = void;
};
}
template < typename... Args >
using void_t = typename impl::make_void<Args...>::type;
}
//
// is_reference_wrapper
//
namespace invoke_hpp
{
namespace impl
{
template < typename T >
struct is_reference_wrapper_impl
: std::false_type {};
template < typename U >
struct is_reference_wrapper_impl<std::reference_wrapper<U>>
: std::true_type {};
}
template < typename T >
struct is_reference_wrapper
: impl::is_reference_wrapper_impl<std::remove_cv_t<T>> {};
}
//
// invoke
//
namespace invoke_hpp
{
namespace impl
{
//
// invoke_member_object_impl
//
template
<
typename Base, typename F, typename Derived,
typename std::enable_if_t<std::is_base_of<Base, std::decay_t<Derived>>::value, int> = 0
>
constexpr auto invoke_member_object_impl(F Base::* f, Derived&& ref)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
std::forward<Derived>(ref).*f)
template
<
typename Base, typename F, typename RefWrap,
typename std::enable_if_t<is_reference_wrapper<std::decay_t<RefWrap>>::value, int> = 0
>
constexpr auto invoke_member_object_impl(F Base::* f, RefWrap&& ref)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
ref.get().*f)
template
<
typename Base, typename F, typename Pointer,
typename std::enable_if_t<
!std::is_base_of<Base, std::decay_t<Pointer>>::value &&
!is_reference_wrapper<std::decay_t<Pointer>>::value
, int> = 0
>
constexpr auto invoke_member_object_impl(F Base::* f, Pointer&& ptr)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
(*std::forward<Pointer>(ptr)).*f)
//
// invoke_member_function_impl
//
template
<
typename Base, typename F, typename Derived, typename... Args,
typename std::enable_if_t<std::is_base_of<Base, std::decay_t<Derived>>::value, int> = 0
>
constexpr auto invoke_member_function_impl(F Base::* f, Derived&& ref, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
(std::forward<Derived>(ref).*f)(std::forward<Args>(args)...))
template
<
typename Base, typename F, typename RefWrap, typename... Args,
typename std::enable_if_t<is_reference_wrapper<std::decay_t<RefWrap>>::value, int> = 0
>
constexpr auto invoke_member_function_impl(F Base::* f, RefWrap&& ref, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
(ref.get().*f)(std::forward<Args>(args)...))
template
<
typename Base, typename F, typename Pointer, typename... Args,
typename std::enable_if_t<
!std::is_base_of<Base, std::decay_t<Pointer>>::value &&
!is_reference_wrapper<std::decay_t<Pointer>>::value
, int> = 0
>
constexpr auto invoke_member_function_impl(F Base::* f, Pointer&& ptr, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
((*std::forward<Pointer>(ptr)).*f)(std::forward<Args>(args)...))
}
template
<
typename F, typename... Args,
typename std::enable_if_t<!std::is_member_pointer<std::decay_t<F>>::value, int> = 0
>
constexpr auto invoke(F&& f, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
std::forward<F>(f)(std::forward<Args>(args)...))
template
<
typename F, typename T,
typename std::enable_if_t<std::is_member_object_pointer<std::decay_t<F>>::value, int> = 0
>
constexpr auto invoke(F&& f, T&& t)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
impl::invoke_member_object_impl(std::forward<F>(f), std::forward<T>(t)))
template
<
typename F, typename... Args,
typename std::enable_if_t<std::is_member_function_pointer<std::decay_t<F>>::value, int> = 0
>
constexpr auto invoke(F&& f, Args&&... args)
INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN(
impl::invoke_member_function_impl(std::forward<F>(f), std::forward<Args>(args)...))
}
//
// invoke_result
//
namespace invoke_hpp
{
namespace impl
{
struct invoke_result_impl_tag {};
template < typename Void, typename F, typename... Args >
struct invoke_result_impl {};
template < typename F, typename... Args >
struct invoke_result_impl<void_t<invoke_result_impl_tag, decltype(invoke_hpp::invoke(std::declval<F>(), std::declval<Args>()...))>, F, Args...> {
using type = decltype(invoke_hpp::invoke(std::declval<F>(), std::declval<Args>()...));
};
}
template < typename F, typename... Args >
struct invoke_result
: impl::invoke_result_impl<void, F, Args...> {};
template < typename F, typename... Args >
using invoke_result_t = typename invoke_result<F, Args...>::type;
}
//
// is_invocable
//
namespace invoke_hpp
{
namespace impl
{
struct is_invocable_r_impl_tag {};
template < typename Void, typename R, typename F, typename... Args >
struct is_invocable_r_impl
: std::false_type {};
template < typename R, typename F, typename... Args >
struct is_invocable_r_impl<void_t<is_invocable_r_impl_tag, invoke_result_t<F, Args...>>, R, F, Args...>
: std::conditional_t<
std::is_void<R>::value,
std::true_type,
std::is_convertible<invoke_result_t<F, Args...>, R>> {};
}
template < typename R, typename F, typename... Args >
struct is_invocable_r
: impl::is_invocable_r_impl<void, R, F, Args...> {};
template < typename F, typename... Args >
using is_invocable = is_invocable_r<void, F, Args...>;
}
//
// apply
//
namespace invoke_hpp
{
namespace impl
{
template < typename F, typename Tuple, std::size_t... I >
constexpr decltype(auto) apply_impl(F&& f, Tuple&& args, std::index_sequence<I...>)
INVOKE_HPP_NOEXCEPT_RETURN(
invoke_hpp::invoke(
std::forward<F>(f),
std::get<I>(std::forward<Tuple>(args))...))
}
template < typename F, typename Tuple >
constexpr decltype(auto) apply(F&& f, Tuple&& args)
INVOKE_HPP_NOEXCEPT_RETURN(
impl::apply_impl(
std::forward<F>(f),
std::forward<Tuple>(args),
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>()))
}
#undef INVOKE_HPP_NOEXCEPT_RETURN
#undef INVOKE_HPP_NOEXCEPT_DECLTYPE_RETURN

490
promise.hpp Normal file
View File

@@ -0,0 +1,490 @@
#pragma once
#include <cstdint>
#include <cassert>
#include <new>
#include <mutex>
#include <memory>
#include <vector>
#include <utility>
#include <exception>
#include <stdexcept>
#include <functional>
#include <type_traits>
#include "invoke.hpp"
namespace promise_hpp
{
//
// forward declaration
//
template < typename T = void >
class promise;
//
// make_promise
//
template < typename R, typename F >
promise<R> make_promise(F&& f) {
promise<R> result;
auto resolver = std::bind([](promise<R>& p, auto&& v){
p.resolve(std::forward<decltype(v)>(v));
}, result, std::placeholders::_1);
auto rejector = std::bind([](promise<R>& p, auto&& e){
p.reject(std::forward<decltype(e)>(e));
}, result, std::placeholders::_1);
try {
invoke_hpp::invoke(
std::forward<F>(f),
std::move(resolver),
std::move(rejector));
} catch (...) {
result.reject(std::current_exception());
}
return result;
}
//
// promise<T>
//
template < typename T >
class promise final {
public:
enum class status : std::uint8_t {
pending,
resolved,
rejected
};
public:
promise()
: state_(std::make_shared<state>()) {}
template < typename ResolveF
, typename ResolveFR = invoke_hpp::invoke_result_t<ResolveF,T> >
promise<ResolveFR> then(ResolveF&& on_resolve) {
promise<ResolveFR> next;
state_->attach(
next,
std::forward<ResolveF>(on_resolve),
[](std::exception_ptr){});
return next;
}
template < typename ResolveF
, typename RejectF
, typename ResolveFR = invoke_hpp::invoke_result_t<ResolveF,T> >
promise<ResolveFR> then(ResolveF&& on_resolve, RejectF&& on_reject) {
promise<ResolveFR> next;
state_->attach(
next,
std::forward<ResolveF>(on_resolve),
std::forward<RejectF>(on_reject));
return next;
}
template < typename RejectF >
promise<T> fail(RejectF&& on_reject) {
promise<T> next;
state_->attach(
next,
[](const T& value) { return value; },
std::forward<RejectF>(on_reject));
return next;
}
template < typename U >
promise& resolve(U&& value) {
state_->resolve(std::forward<U>(value));
return *this;
}
promise& reject(std::exception_ptr e) {
state_->reject(e);
return *this;
}
template < typename E >
promise& reject(E&& e) {
state_->reject(std::make_exception_ptr(std::forward<E>(e)));
return *this;
}
private:
class state;
std::shared_ptr<state> state_;
private:
class storage final {
public:
storage() = default;
storage(const storage&) = delete;
storage& operator=(const storage&) = delete;
~storage() noexcept(std::is_nothrow_destructible<T>::value) {
if ( initialized_ ) {
ptr_()->~T();
}
}
template < typename U >
void set(U&& value) {
assert(!initialized_);
::new(ptr_()) T(std::forward<U>(value));
initialized_ = true;
}
const T& value() const noexcept {
assert(initialized_);
return *ptr_();
}
private:
T* ptr_() noexcept {
return reinterpret_cast<T*>(&data_);
}
const T* ptr_() const noexcept {
return reinterpret_cast<const T*>(&data_);
}
private:
std::aligned_storage_t<sizeof(T), alignof(T)> data_;
bool initialized_ = false;
};
class state final {
public:
state() = default;
state(const state&) = delete;
state& operator=(const state&) = delete;
template < typename U >
void resolve(U&& value) {
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ != status::pending ) {
throw std::logic_error("do not try to resolve a resolved or rejected promise");
}
storage_.set(std::forward<U>(value));
status_ = status::resolved;
invoke_resolve_handlers_();
}
void reject(std::exception_ptr e) {
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ != status::pending ) {
throw std::logic_error("do not try to reject a resolved or rejected promise");
}
exception_ = e;
status_ = status::rejected;
invoke_reject_handlers_();
}
template < typename U, typename ResolveF, typename RejectF >
std::enable_if_t<!std::is_void<U>::value, void>
attach(promise<U>& other, ResolveF&& resolve, RejectF&& reject) {
auto resolve_h = std::bind([](promise<U>& p, const ResolveF& f, const T& v){
try {
p.resolve(invoke_hpp::invoke(f, v));
} catch (...) {
p.reject(std::current_exception());
}
}, other, std::forward<ResolveF>(resolve), std::placeholders::_1);
auto reject_h = std::bind([](promise<U>& p, const RejectF& f, std::exception_ptr e){
try {
invoke_hpp::invoke(f, e);
p.reject(e);
} catch (...) {
p.reject(std::current_exception());
}
}, other, std::forward<RejectF>(reject), std::placeholders::_1);
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ == status::resolved ) {
resolve_h(storage_.value());
} else if ( status_ == status::rejected ) {
reject_h(exception_);
} else {
handlers_.emplace_back(
std::move(resolve_h),
std::move(reject_h));
}
}
template < typename U, typename ResolveF, typename RejectF >
std::enable_if_t<std::is_void<U>::value, void>
attach(promise<U>& other, ResolveF&& resolve, RejectF&& reject) {
auto resolve_h = std::bind([](promise<U>& p, const ResolveF& f, const T& v){
try {
invoke_hpp::invoke(f, v);
p.resolve();
} catch (...) {
p.reject(std::current_exception());
}
}, other, std::forward<ResolveF>(resolve), std::placeholders::_1);
auto reject_h = std::bind([](promise<U>& p, const RejectF& f, std::exception_ptr e){
try {
invoke_hpp::invoke(f, e);
p.reject(e);
} catch (...) {
p.reject(std::current_exception());
}
}, other, std::forward<RejectF>(reject), std::placeholders::_1);
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ == status::resolved ) {
resolve_h(storage_.value());
} else if ( status_ == status::rejected ) {
reject_h(exception_);
} else {
handlers_.emplace_back(
std::move(resolve_h),
std::move(reject_h));
}
}
private:
void invoke_resolve_handlers_() noexcept {
const T& value = storage_.value();
for ( const auto& h : handlers_ ) {
h.resolve_(value);
}
handlers_.clear();
}
void invoke_reject_handlers_() noexcept {
for ( const auto& h : handlers_ ) {
h.reject_(exception_);
}
handlers_.clear();
}
private:
storage storage_;
status status_ = status::pending;
std::exception_ptr exception_ = nullptr;
std::mutex mutex_;
struct handler {
using resolve_t = std::function<void(const T&)>;
using reject_t = std::function<void(std::exception_ptr)>;
resolve_t resolve_;
reject_t reject_;
template < typename ResolveF, typename RejectF >
handler(ResolveF&& resolve, RejectF&& reject)
: resolve_(std::forward<ResolveF>(resolve))
, reject_(std::forward<RejectF>(reject)) {}
};
std::vector<handler> handlers_;
};
};
//
// promise<void>
//
template <>
class promise<void> final {
public:
enum class status : std::uint8_t {
pending,
resolved,
rejected
};
public:
promise()
: state_(std::make_shared<state>()) {}
template < typename ResolveF
, typename ResolveFR = invoke_hpp::invoke_result_t<ResolveF> >
promise<ResolveFR> then(ResolveF&& on_resolve) {
promise<ResolveFR> next;
state_->attach(
next,
std::forward<ResolveF>(on_resolve),
[](std::exception_ptr){});
return next;
}
template < typename ResolveF
, typename RejectF
, typename ResolveFR = invoke_hpp::invoke_result_t<ResolveF> >
promise<ResolveFR> then(ResolveF&& on_resolve, RejectF&& on_reject) {
promise<ResolveFR> next;
state_->attach(
next,
std::forward<ResolveF>(on_resolve),
std::forward<RejectF>(on_reject));
return next;
}
template < typename RejectF >
promise<void> fail(RejectF&& on_reject) {
promise<void> next;
state_->attach(
next,
[]{},
std::forward<RejectF>(on_reject));
return next;
}
promise& resolve() {
state_->resolve();
return *this;
}
promise& reject(std::exception_ptr e) {
state_->reject(e);
return *this;
}
template < typename E >
promise& reject(E&& e) {
state_->reject(std::make_exception_ptr(std::forward<E>(e)));
return *this;
}
private:
class state;
std::shared_ptr<state> state_;
private:
class state final {
public:
state() = default;
state(const state&) = delete;
state& operator=(const state&) = delete;
void resolve() {
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ != status::pending ) {
throw std::logic_error("do not try to resolve a resolved or rejected promise");
}
status_ = status::resolved;
invoke_resolve_handlers_();
}
void reject(std::exception_ptr e) {
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ != status::pending ) {
throw std::logic_error("do not try to reject a resolved or rejected promise");
}
exception_ = e;
status_ = status::rejected;
invoke_reject_handlers_();
}
template < typename U, typename ResolveF, typename RejectF >
std::enable_if_t<!std::is_void<U>::value, void>
attach(promise<U>& other, ResolveF&& resolve, RejectF&& reject) {
auto resolve_h = std::bind([](promise<U>& p, const ResolveF& f){
try {
p.resolve(invoke_hpp::invoke(f));
} catch (...) {
p.reject(std::current_exception());
}
}, other, std::forward<ResolveF>(resolve));
auto reject_h = std::bind([](promise<U>& p, const RejectF& f, std::exception_ptr e){
try {
invoke_hpp::invoke(f, e);
p.reject(e);
} catch (...) {
p.reject(std::current_exception());
}
}, other, std::forward<RejectF>(reject), std::placeholders::_1);
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ == status::resolved ) {
resolve_h();
} else if ( status_ == status::rejected ) {
reject_h(exception_);
} else {
handlers_.emplace_back(
std::move(resolve_h),
std::move(reject_h));
}
}
template < typename U, typename ResolveF, typename RejectF >
std::enable_if_t<std::is_void<U>::value, void>
attach(promise<U>& other, ResolveF&& resolve, RejectF&& reject) {
auto resolve_h = std::bind([](promise<U>& p, const ResolveF& f){
try {
invoke_hpp::invoke(f);
p.resolve();
} catch (...) {
p.reject(std::current_exception());
}
}, other, std::forward<ResolveF>(resolve));
auto reject_h = std::bind([](promise<U>& p, const RejectF& f, std::exception_ptr e){
try {
invoke_hpp::invoke(f, e);
p.reject(e);
} catch (...) {
p.reject(std::current_exception());
}
}, other, std::forward<RejectF>(reject), std::placeholders::_1);
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ == status::resolved ) {
resolve_h();
} else if ( status_ == status::rejected ) {
reject_h(exception_);
} else {
handlers_.emplace_back(
std::move(resolve_h),
std::move(reject_h));
}
}
private:
void invoke_resolve_handlers_() noexcept {
for ( const auto& h : handlers_ ) {
h.resolve_();
}
handlers_.clear();
}
void invoke_reject_handlers_() noexcept {
for ( const auto& h : handlers_ ) {
h.reject_(exception_);
}
handlers_.clear();
}
private:
status status_ = status::pending;
std::exception_ptr exception_ = nullptr;
std::mutex mutex_;
struct handler {
using resolve_t = std::function<void()>;
using reject_t = std::function<void(std::exception_ptr)>;
resolve_t resolve_;
reject_t reject_;
template < typename ResolveF, typename RejectF >
handler(ResolveF&& resolve, RejectF&& reject)
: resolve_(std::forward<ResolveF>(resolve))
, reject_(std::forward<RejectF>(reject)) {}
};
std::vector<handler> handlers_;
};
};
}

273
tests.cpp Normal file
View File

@@ -0,0 +1,273 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "promise.hpp"
namespace pr = promise_hpp;
namespace
{
bool check_hello_fail_exception(std::exception_ptr e) {
try {
std::rethrow_exception(e);
} catch (std::logic_error& ee) {
return 0 == std::strcmp(ee.what(), "hello fail");
} catch (...) {
return false;
}
}
}
TEST_CASE("promise") {
SECTION("resolved") {
{
int check_42_int = 0;
pr::promise<int>()
.resolve(42)
.then([&check_42_int](int value){
check_42_int = value;
});
REQUIRE(check_42_int == 42);
}
{
int check_42_int = 0;
pr::promise<int>()
.resolve(42)
.fail([](std::exception_ptr){
}).then([&check_42_int](int value){
check_42_int = value;
});
REQUIRE(check_42_int == 42);
}
{
int check_42_int = 0;
pr::promise<int>()
.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;
int check_100500_transform = 0;
pr::promise<int>()
.resolve(42)
.then([](int value){
return value * 2;
}).then([&check_84_int](int value){
check_84_int = value;
}).then([&check_void_call](){
check_void_call = true;
}).then([](){
return 100500;
}).then([&check_100500_transform](int value){
check_100500_transform = value;
});
REQUIRE(check_84_int == 84);
REQUIRE(check_void_call);
REQUIRE(check_100500_transform == 100500);
}
}
SECTION("rejected") {
{
bool call_fail_with_logic_error = false;
bool not_call_then_on_reject = true;
pr::promise<int>()
.reject(std::logic_error("hello fail"))
.then([&not_call_then_on_reject](int value) {
(void)value;
not_call_then_on_reject = false;
}).fail([&call_fail_with_logic_error](std::exception_ptr e){
call_fail_with_logic_error = check_hello_fail_exception(e);
});
REQUIRE(not_call_then_on_reject);
REQUIRE(call_fail_with_logic_error);
}
{
std::logic_error ee("hello fail");
bool call_fail_with_logic_error = false;
pr::promise<int>()
.reject(ee)
.then([](int){
}).fail([&call_fail_with_logic_error](const std::exception_ptr& e){
call_fail_with_logic_error = check_hello_fail_exception(e);
});
REQUIRE(call_fail_with_logic_error);
}
{
std::logic_error ee("hello fail");
bool call_fail_with_logic_error = false;
pr::promise<int>()
.reject(std::make_exception_ptr(ee))
.then([](int){
}).fail([&call_fail_with_logic_error](const std::exception_ptr& e){
call_fail_with_logic_error = check_hello_fail_exception(e);
});
REQUIRE(call_fail_with_logic_error);
}
{
int check_multi_fail = 0;
pr::promise<>()
.reject(std::logic_error("hello fail"))
.fail([&check_multi_fail](std::exception_ptr){
++check_multi_fail;
}).fail([&check_multi_fail](std::exception_ptr){
++check_multi_fail;
});
REQUIRE(check_multi_fail == 2);
}
}
SECTION("unresolved") {
{
int check_42_int = 0;
bool not_call_before_resolve = true;
auto p = pr::promise<int>();
p.then([&not_call_before_resolve](int value){
not_call_before_resolve = false;
return value * 2;
}).then([&check_42_int, &not_call_before_resolve](int value){
not_call_before_resolve = false;
check_42_int = value;
});
REQUIRE(check_42_int == 0);
REQUIRE(not_call_before_resolve);
p.resolve(42);
REQUIRE(check_42_int == 84);
REQUIRE_FALSE(not_call_before_resolve);
}
{
bool not_call_then_on_reject = true;
bool call_fail_with_logic_error = false;
auto p = pr::promise<int>();
p.then([&not_call_then_on_reject](int){
not_call_then_on_reject = false;
}).fail([&call_fail_with_logic_error](std::exception_ptr e){
call_fail_with_logic_error = check_hello_fail_exception(e);
});
REQUIRE(not_call_then_on_reject);
REQUIRE_FALSE(call_fail_with_logic_error);
p.reject(std::make_exception_ptr(std::logic_error("hello fail")));
REQUIRE(not_call_then_on_reject);
REQUIRE(call_fail_with_logic_error);
}
}
SECTION("make_promise") {
{
int check_84_int = 0;
auto p = pr::make_promise<int>([](auto resolve, auto reject){
(void)reject;
resolve(42);
});
p.then([](int value){
return value * 2;
}).then([&check_84_int](int value){
check_84_int = value;
});
REQUIRE(check_84_int == 84);
}
{
bool call_fail_with_logic_error = false;
auto p = pr::make_promise<int>([](auto resolve, auto reject){
(void)resolve;
reject(std::logic_error("hello fail"));
});
p.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 p = pr::make_promise<int>([](auto resolve, auto reject){
(void)resolve;
(void)reject;
throw std::logic_error("hello fail");
});
p.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;
bool call_fail_with_logic_error = false;
pr::promise<int>()
.resolve(42)
.then([](int){
throw std::logic_error("hello fail");
}).then([&not_call_then_on_reject](){
not_call_then_on_reject = false;
}).fail([&call_fail_with_logic_error](std::exception_ptr e){
call_fail_with_logic_error = check_hello_fail_exception(e);
});
REQUIRE(not_call_then_on_reject);
REQUIRE(call_fail_with_logic_error);
}
}
SECTION("multi_then") {
{
auto p = pr::promise<int>();
int pa_value = 0;
{
auto pa = p.then([](int value){
return value * 2;
}).then([&pa_value](int value){
pa_value = value;
});
}
int pb_value = 0;
{
auto pb = p.then([](int value){
return value / 2;
}).then([&pb_value](int value){
pb_value = value;
});
}
REQUIRE(pa_value == 0);
REQUIRE(pb_value == 0);
p.resolve(42);
REQUIRE(pa_value == 84);
REQUIRE(pb_value == 21);
}
{
auto p = pr::promise<int>();
int pa_value = 0;
{
auto pa = p.then([](int){
throw std::logic_error("hello fail");
}).fail([&pa_value](std::exception_ptr e){
if ( check_hello_fail_exception(e) ) {
pa_value = 84;
}
});
}
int pb_value = 0;
{
auto pb = p.then([](int value){
return value / 2;
}).then([&pb_value](int value){
pb_value = value;
});
}
REQUIRE(pa_value == 0);
REQUIRE(pb_value == 0);
p.resolve(42);
REQUIRE(pa_value == 84);
REQUIRE(pb_value == 21);
}
}
}