mirror of
https://github.com/BlackMATov/promise.hpp.git
synced 2025-12-15 12:29:50 +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