mirror of
https://github.com/BlackMATov/promise.hpp.git
synced 2025-12-13 03:46:29 +07:00
first basic implementation
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build/*
|
||||
.vscode/*
|
||||
CMakeLists.txt.user
|
||||
12
CMakeLists.txt
Normal file
12
CMakeLists.txt
Normal 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
21
LICENSE.md
Normal 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
25
README.md
Normal 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)
|
||||
242
invoke.hpp
Normal file
242
invoke.hpp
Normal 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
490
promise.hpp
Normal 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
273
tests.cpp
Normal 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([¬_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([¬_call_before_resolve](int value){
|
||||
not_call_before_resolve = false;
|
||||
return value * 2;
|
||||
}).then([&check_42_int, ¬_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([¬_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([¬_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user