Skip to content

Instantly share code, notes, and snippets.

@dnevera
Created June 25, 2019 15:28
Show Gist options
  • Save dnevera/240b554a6f304c31ce9ebbeaf91b63ff to your computer and use it in GitHub Desktop.
Save dnevera/240b554a6f304c31ce9ebbeaf91b63ff to your computer and use it in GitHub Desktop.
Promise implementation
//
// Created by denn nevera on 2019-06-24.
//
//
// source: https://github.com/LifeWanted/liblw/tree/master/source/lw/event
//
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <type_traits>
#include "capy/amqp_exception.h"
namespace capy::event {
CAPY_DEFINE_EXCEPTION(PromiseError);
// ---------------------------------------------------------------------------------------------- //
template<typename T>
class Future;
// ---------------------------------------------------------------------------------------------- //
/// @brief Determines if the given variable is a `Future`, or derives publicly from `Future`.
///
/// @tparam T The type to check.
template<typename T>
struct IsFuture : public std::integral_constant<bool, false> {};
template<typename T>
struct IsFuture<Future<T>> : public std::integral_constant<bool, true> {};
template<template<typename> class T, typename Value>
struct IsFuture<T<Value>> :
public std::integral_constant<bool, std::is_base_of<Future<Value>, T<Value>>::value>
{};
// ---------------------------------------------------------------------------------------------- //
/// @brief Breaks down a type to the highest non-future layer.
///
/// @tparam T The type to break down.
template<typename T>
struct UnwrapFuture {
typedef T result_type; ///< The type that will be promised.
typedef Future<T> type; ///< The type of a future for this type.
};
template<typename T>
struct UnwrapFuture<Future<T>> : public UnwrapFuture<T> {};
// ---------------------------------------------------------------------------------------------- //
/// @brief Determines the result of a function as a future.
template<typename T>
struct FutureResultOf;
template<typename F, typename... Args>
struct FutureResultOf<F(Args...)> :
public UnwrapFuture<typename std::result_of<F(Args...)>::type>
{};
// ---------------------------------------------------------------------------------------------- //
/// @brief `Promise`s and `Future`s allow for chaining callbacks without nesting.
///
/// A `Promise` is the active side of the pair. Asynchronous functions create a promise and later
/// fulfill it by either resolving or rejecting the promise.
template<typename T = void>
class Promise {
public:
/// @brief Default construction.
Promise(void):
m_state(new _SharedState())
{
m_state->resolved = false;
m_state->rejected = false;
m_state->resolve = nullptr;
m_state->reject = nullptr;
}
// ------------------------------------------------------------------------------------------ //
/// @brief No copying!
Promise(const Promise&) = delete;
// ------------------------------------------------------------------------------------------ //
/// @brief Moves the promise from `other` to `this`.
Promise(Promise&& other):
m_state(std::move(other.m_state))
{
other.m_state = nullptr;
}
// ------------------------------------------------------------------------------------------ //
/// @brief Returns a future associated with this promise.
Future<T> future(void);
// ------------------------------------------------------------------------------------------ //
/// @brief Resolves the promise as a success.
void resolve(T&& value){
m_state->resolved = true;
if (m_state->resolve) {
m_state->resolve( std::move( value ) );
}
}
// ------------------------------------------------------------------------------------------ //
/// @copydoc Promise::resolve(T&&)
void resolve(const T& value){
resolve(T(value));
}
// ------------------------------------------------------------------------------------------ //
/// @brief Rejects the promise as a failure.
void reject(const error::Exception& err){
m_state->rejected = true;
if (m_state->reject) {
m_state->reject(err);
}
else {
throw err;
}
}
// ------------------------------------------------------------------------------------------ //
/// @brief Resets the promise's internal state so that it can be reused.
///
/// @throws PromiseError If the promise is in an unfinished state.
void reset(void){
if (!is_finished()) {
throw PromiseError(1, "Cannot reset an unfinished promise.");
}
m_state->resolved = false;
m_state->rejected = false;
m_state->resolve = nullptr;
m_state->reject = nullptr;
}
// ------------------------------------------------------------------------------------------ //
/// @brief Indicates if the promise has been resolved.
bool is_resolved(void) const {
return m_state->resolved;
}
// ------------------------------------------------------------------------------------------ //
/// @brief Indicates if the promise has been rejected.
bool is_rejected(void) const {
return m_state->rejected;
}
// ------------------------------------------------------------------------------------------ //
/// @brief Indicates if the promise has been either resolved or rejected.
bool is_finished(void) const {
return is_resolved() || is_rejected();
}
// ------------------------------------------------------------------------------------------ //
/// @brief No copying!
Promise& operator=(const Promise&) = delete;
// ------------------------------------------------------------------------------------------ //
/// @brief Moves the promise from `other` into `this`.
Promise& operator=(Promise&& other){
m_state = std::move(other.m_state);
other.m_state = nullptr;
return *this;
}
private:
template<typename Type>
friend class ::capy::event::Future;
// ------------------------------------------------------------------------------------------ //
/// @brief The container for the shared state between promises and futures.
struct _SharedState {
std::atomic_bool resolved;
std::atomic_bool rejected;
std::function<void(T&&)> resolve;
std::function<void(const error::Exception&)> reject;
};
typedef std::shared_ptr<_SharedState> _SharedStatePtr;
// ------------------------------------------------------------------------------------------ //
/// @brief The state of the promise.
_SharedStatePtr m_state;
};
// ---------------------------------------------------------------------------------------------- //
/// @brief The passive half of the `Promise`-`Future` pair.
///
/// `Future`s are the requester's handle on an asynchronous event. They allow callbacks to be
/// registered for after it has been started.
template<typename T = void>
class Future {
public:
/// @brief The type promised by this future.
typedef T result_type;
/// @brief The type of Promise that made this future.
typedef Promise<T> promise_type;
// ------------------------------------------------------------------------------------------ //
template<typename Func>
auto then(Func&& func){
return then(std::forward<Func>(func), nullptr);
}
// ------------------------------------------------------------------------------------------ //
template<typename Result, typename Func>
auto then(Func&& func){
return then<Result>(std::forward<Func>(func), nullptr);
}
// ------------------------------------------------------------------------------------------ //
template<typename Resolve, typename Reject>
auto then(Resolve&& resolve, Reject&& reject){
return _then(std::forward<Resolve>(resolve), std::forward<Reject>(reject));
}
// ------------------------------------------------------------------------------------------ //
template<typename Result, typename Resolve, typename Reject>
auto then(Resolve&& resolve, Reject&& reject){
return _then<Result>(std::forward<Resolve>(resolve), std::forward<Reject>(reject));
}
// ------------------------------------------------------------------------------------------ //
/// @brief Connects this promise to the one provided.
///
/// @param promise The promise to resolve/reject with this one.
void then(promise_type&& promise);
// ------------------------------------------------------------------------------------------ //
private:
template<typename Type>
friend class ::capy::event::Promise;
// ------------------------------------------------------------------------------------------ //
/// @brief Only `Promise`s can construct us.
///
/// @param state The shared state to associate with.
Future(const typename promise_type::_SharedStatePtr& state):
m_state(state)
{}
// ------------------------------------------------------------------------------------------ //
/// @brief Chaining for generic functors.
///
/// @tparam Result The type given functor promises.
/// @tparam Resolve A functor type that can take a `Promise&&` as its parameter.
///
/// @param resolve The functor to call when this one is resolved.
///
/// @return A new future, for when the provided `resolve` completes its action.
template<
typename Result,
typename Resolve,
typename Reject,
typename = typename std::result_of<Resolve(T&&,Promise<Result>&&)>::type
>
Future<Result> _then(Resolve&& resolve, Reject&& reject);
// ------------------------------------------------------------------------------------------ //
/// @brief Chaining for generic functors promising nothing.
///
/// @tparam Resolve A functor type that can take a `Promise&&` as its parameter.
///
/// @param resolve The functor to call when this one is resolved.
///
/// @return A new future, for when the provided `resolve` completes its action.
template<
typename Resolve,
typename Reject,
typename = typename std::result_of<Resolve(T&&, Promise<>&&)>::type
>
Future<> _then(Resolve&& resolve, Reject&& reject);
// ------------------------------------------------------------------------------------------ //
/// @brief Chaining for `Future`-returning functors.
///
/// @tparam Resolve A functor which returns a `Future`.
///
/// @param resolve The functor to call when this one is ready.
///
/// @return A `Future` which will be resolved by `resolve`.
template<
typename Resolve,
typename Reject,
typename ResolveResult = typename std::result_of<Resolve(T&&)>::type,
typename std::enable_if<IsFuture<ResolveResult>::value>::type* = nullptr
>
Future<typename ResolveResult::result_type> _then(Resolve&& resolve, Reject&& reject);
// ------------------------------------------------------------------------------------------ //
/// @brief Chaining for value-returning functors (i.e. synchronous ones).
///
/// @tparam Resolve A synchronous functor type.
///
/// @param resolve A synchronous functor returning some value.
///
/// @return A `Future` which will be resolved with the return value from `resolve`.
template<
typename Resolve,
typename Reject,
typename ResolveResult = typename std::result_of<Resolve(T&&)>::type,
typename std::enable_if<
!IsFuture<ResolveResult>::value &&
!std::is_void<ResolveResult>::value
>::type* = nullptr
>
Future<ResolveResult> _then(Resolve&& resolve, Reject&& reject);
// ------------------------------------------------------------------------------------------ //
/// @brief Chaining for void-returning synchronous functors.
///
/// @tparam Resolve A synchronous functor type.
///
/// @param resolve A synchronous functor with no return value.
///
/// @return A `Future` which will be resolved `resolve` runs.
template<
typename Resolve,
typename Reject,
typename ResolveResult = typename std::result_of<Resolve(T&&)>::type,
typename std::enable_if<std::is_void<ResolveResult>::value>::type* = nullptr
>
Future<> _then(Resolve&& resolve, Reject&& reject);
// ------------------------------------------------------------------------------------------ //
/// @brief Our internal shared state.
typename promise_type::_SharedStatePtr m_state;
};
template<>
class Promise< void >{
public:
/// @brief Default construction.
Promise( void ):
m_state( new _SharedState() )
{
m_state->resolved = false;
m_state->rejected = false;
m_state->resolve = nullptr;
m_state->reject = nullptr;
}
// ---------------------------------------------------------------------- //
/// @brief No copying!
Promise( const Promise& ) = delete;
// ---------------------------------------------------------------------- //
/// @brief Moves the promise from `other` to `this`.
Promise( Promise&& other ):
m_state( std::move( other.m_state ) )
{
other.m_state = nullptr;
}
// ---------------------------------------------------------------------- //
/// @brief Returns a future associated with this promise.
Future< void > future( void );
// ---------------------------------------------------------------------- //
/// @brief Resolves the promise as a success.
void resolve( void ){
m_state->resolved = true;
if( m_state->resolve ){
m_state->resolve();
}
}
// ---------------------------------------------------------------------- //
/// @brief Rejects the promise as a failure.
void reject( const error::Exception& err ){
m_state->rejected = true;
if( m_state->reject ){
m_state->reject( err );
}
else {
throw err;
}
}
// ---------------------------------------------------------------------- //
/// @brief Resets the promise's internal state so that it can be reused.
///
/// @throws PromiseError If the promise is in an unfinished state.
void reset( void ){
if( !is_finished() ){
throw PromiseError( 1, "Cannot reset an unfinished promise." );
}
m_state->resolved = false;
m_state->rejected = false;
m_state->resolve = nullptr;
m_state->reject = nullptr;
}
// ---------------------------------------------------------------------- //
/// @brief Indicates if the promise has been resolved.
bool is_resolved( void ) const {
return m_state->resolved;
}
// ---------------------------------------------------------------------- //
/// @brief Indicates if the promise has been rejected.
bool is_rejected( void ) const {
return m_state->rejected;
}
// ---------------------------------------------------------------------- //
/// @brief Indicates if the promise has been either resolved or rejected.
bool is_finished( void ) const {
return is_resolved() || is_rejected();
}
// ---------------------------------------------------------------------- //
/// @brief No copying!
Promise& operator=( const Promise& ) = delete;
// ---------------------------------------------------------------------- //
/// @brief Moves the promise from `other` into `this`.
Promise& operator=( Promise&& other ){
m_state = std::move( other.m_state );
other.m_state = nullptr;
return *this;
}
private:
template< typename Type >
friend class ::capy::event::Future;
// ---------------------------------------------------------------------- //
/// @brief The container for the shared state between promises and futures.
struct _SharedState {
std::atomic_bool resolved;
std::atomic_bool rejected;
std::function< void( void ) > resolve;
std::function< void( const error::Exception& ) > reject;
};
typedef std::shared_ptr< _SharedState > _SharedStatePtr;
// ---------------------------------------------------------------------- //
/// @brief The state of the promise.
_SharedStatePtr m_state;
};
// -------------------------------------------------------------------------- //
template<>
class Future< void >{
public:
/// @brief The type promised by this future.
typedef void result_type;
/// @brief The type of Promise that made this future.
typedef Promise< void > promise_type;
// ---------------------------------------------------------------------- //
template< typename Func >
auto then( Func&& func ){
return then( std::forward< Func >( func ), nullptr );
}
// ---------------------------------------------------------------------- //
template< typename Result, typename Func >
auto then( Func&& func ){
return then< Result >( std::forward< Func >( func ), nullptr );
}
// ---------------------------------------------------------------------- //
template< typename Resolve, typename Reject >
auto then( Resolve&& resolve, Reject&& reject ){
return _then(
std::forward< Resolve >( resolve ),
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------- //
template< typename Result, typename Resolve, typename Reject >
auto then( Resolve&& resolve, Reject&& reject ){
return _then< Result >(
std::forward< Resolve >( resolve ),
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------- //
/// @brief Connects this promise to the one provided.
///
/// @param promise The promise to resolve/reject with this one.
void then( promise_type&& promise );
// ---------------------------------------------------------------------- //
private:
template< typename Type >
friend class ::capy::event::Promise;
// ---------------------------------------------------------------------- //
/// @brief Only `Promise`s can construct us.
///
/// @param state The shared state to associate with.
Future( const typename promise_type::_SharedStatePtr& state ):
m_state( state )
{}
// ---------------------------------------------------------------------- //
/// @brief Chaining for generic functors.
///
/// @tparam Result The type given functor promises.
/// @tparam Resolve A functor type that can take a `Promise&&` as its parameter.
///
/// @param resolve The functor to call when this one is resolved.
///
/// @return A new future, for when the provided `resolve` completes its action.
template<
typename Result,
typename Resolve,
typename Reject,
typename = typename std::result_of< Resolve( Promise< Result >&& ) >::type
>
Future< Result > _then( Resolve&& resolve, Reject&& reject );
// ---------------------------------------------------------------------- //
/// @brief Chaining for generic functors promising nothing.
///
/// @tparam Resolve A functor type that can take a `Promise&&` as its parameter.
///
/// @param resolve The functor to call when this one is resolved.
///
/// @return A new future, for when the provided `resolve` completes its action.
template<
typename Resolve,
typename Reject,
typename = typename std::result_of< Resolve( Promise<>&& ) >::type
>
Future<> _then( Resolve&& resolve, Reject&& reject );
// ---------------------------------------------------------------------- //
/// @brief Chaining for `Future`-returning functors.
///
/// @tparam Resolve A functor which returns a `Future`.
///
/// @param resolve The functor to call when this one is ready.
///
/// @return A `Future` which will be resolved by `resolve`.
template<
typename Resolve,
typename Reject,
typename ResolveResult = typename std::result_of< Resolve() >::type,
typename std::enable_if< IsFuture< ResolveResult >::value >::type* = nullptr
>
Future< typename ResolveResult::result_type > _then( Resolve&& resolve, Reject&& reject );
// ---------------------------------------------------------------------- //
/// @brief Chaining for value-returning functors (i.e. synchronous ones).
///
/// @tparam Resolve A synchronous functor type.
///
/// @param resolve A synchronous functor returning some value.
///
/// @return A `Future` which will be resolved with the return value from `resolve`.
template<
typename Resolve,
typename Reject,
typename ResolveResult = typename std::result_of< Resolve() >::type,
typename std::enable_if<
!IsFuture< ResolveResult >::value &&
!std::is_void< ResolveResult >::value
>::type* = nullptr
>
Future< ResolveResult > _then( Resolve&& resolve, Reject&& reject );
// ---------------------------------------------------------------------- //
/// @brief Chaining for void-returning synchronous functors.
///
/// @tparam Resolve A synchronous functor type.
///
/// @param resolve A synchronous functor with no return value.
///
/// @return A `Future` which will be resolved `resolve` runs.
template<
typename Resolve,
typename Reject,
typename ResolveResult = typename std::result_of< Resolve() >::type,
typename std::enable_if< std::is_void< ResolveResult >::value >::type* = nullptr
>
Future<> _then( Resolve&& resolve, Reject&& reject );
// ---------------------------------------------------------------------- //
/// @brief Our internal shared state.
typename promise_type::_SharedStatePtr m_state;
};
template< typename T >
inline Future< T > Promise< T >::future( void ){
return Future< T >( m_state );
}
// ---------------------------------------------------------------------------------------------- //
inline Future< void > Promise< void >::future( void ){
return Future< void >( m_state );
}
// ---------------------------------------------------------------------------------------------- //
template< typename T >
template< typename Result, typename Resolve, typename Reject, typename >
Future< Result > Future< T >::_then( Resolve&& resolve, Reject&& reject ){
auto next = std::make_shared< Promise< Result > >();
auto prev = m_state;
m_state->resolve = [ resolve, prev, next ]( T&& value ) mutable {
resolve( std::move( value ), std::move( *next ) );
prev->reject = nullptr;
prev.reset();
};
typedef std::function< void( const error::Exception& ) > RejectHandler;
RejectHandler rejectHandler;
if( std::is_same< std::nullptr_t, Reject >::value ){
rejectHandler = [ next ]( const error::Exception& err ){
next->reject( err );
};
}
else {
rejectHandler = std::forward< Reject >( reject );
}
m_state->reject = [ rejectHandler, prev, next ](
const error::Exception& err
) mutable {
rejectHandler( err );
prev->resolve = nullptr;
prev.reset();
};
return next->future();
}
// ---------------------------------------------------------------------------------------------- //
template< typename T >
template< typename Resolve, typename Reject, typename >
inline Future<> Future< T >::_then( Resolve&& resolve, Reject&& reject ){
return then< void >(
std::forward< Resolve >( resolve ),
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------------------------------- //
template< typename T >
template<
typename Resolve,
typename Reject,
typename ResolveResult,
typename std::enable_if< IsFuture< ResolveResult >::value >::type*
>
Future< typename ResolveResult::result_type > Future< T >::_then( Resolve&& resolve, Reject&& reject ){
typedef typename ResolveResult::result_type Result;
return then< Result >(
[ resolve ]( T&& value, Promise< Result >&& promise ) mutable {
resolve( std::move( value ) ).then( std::move( promise ) );
},
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------------------------------- //
template< typename T >
template<
typename Resolve,
typename Reject,
typename ResolveResult,
typename std::enable_if<
!IsFuture< ResolveResult >::value &&
!std::is_void< ResolveResult >::value
>::type*
>
Future< ResolveResult > Future< T >::_then( Resolve&& resolve, Reject&& reject ){
return then< ResolveResult >(
[ resolve ]( T&& value, Promise< ResolveResult >&& promise ) mutable {
try {
promise.resolve( resolve( std::move( value ) ) );
}
catch( const error::Exception& err ){
promise.reject( err );
}
},
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------------------------------- //
template< typename T >
template<
typename Resolve,
typename Reject,
typename ResolveResult,
typename std::enable_if< std::is_void< ResolveResult >::value >::type*
>
Future<> Future< T >::_then( Resolve&& resolve, Reject&& reject ){
return then(
[ resolve ]( T&& value, Promise<>&& promise ){
try {
resolve( std::move( value ) );
}
catch( const error::Exception& err ){
promise.reject( err );
return;
}
promise.resolve();
},
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------------------------------- //
template< typename T >
void Future< T >::then( promise_type&& promise ){
auto next = std::make_shared< promise_type >( std::move( promise ) );
auto prev = m_state;
m_state->resolve = [ prev, next ]( T&& value ) mutable {
next->resolve( std::move( value ) );
prev->reject = nullptr;
prev.reset();
};
m_state->reject = [ prev, next ]( const error::Exception& err ) mutable {
next->reject( err );
prev->resolve = nullptr;
prev.reset();
};
}
// ---------------------------------------------------------------------------------------------- //
template< typename Result, typename Resolve, typename Reject, typename >
Future< Result > Future< void >::_then( Resolve&& resolve, Reject&& reject ){
auto next = std::make_shared< Promise< Result > >();
auto prev = m_state;
m_state->resolve = [ resolve, prev, next ]() mutable {
resolve( std::move( *next ) );
prev->reject = nullptr;
prev.reset();
};
typedef std::function< void( const error::Exception& ) > RejectHandler;
RejectHandler rejectHandler;
if( std::is_same< std::nullptr_t, Reject >::value ){
rejectHandler = [ next ]( const error::Exception& err ){
next->reject( err );
};
}
else {
rejectHandler = std::forward< Reject >( reject );
}
m_state->reject = [ rejectHandler, prev, next ](
const error::Exception& err
) mutable {
rejectHandler( err );
prev->resolve = nullptr;
prev.reset();
};
return next->future();
}
// ---------------------------------------------------------------------------------------------- //
template< typename Resolve, typename Reject, typename >
Future<> Future< void >::_then( Resolve&& resolve, Reject&& reject ){
return then< void >(
std::forward< Resolve >( resolve ),
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------------------------------- //
template<
typename Resolve,
typename Reject,
typename ResolveResult,
typename std::enable_if< IsFuture< ResolveResult >::value >::type*
>
Future< typename ResolveResult::result_type > Future< void >::_then( Resolve&& resolve, Reject&& reject ){
typedef typename ResolveResult::result_type Result;
return then< Result >(
[ resolve ]( Promise< Result >&& promise ){
resolve().then( std::move( promise ) );
},
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------------------------------- //
template<
typename Resolve,
typename Reject,
typename ResolveResult,
typename std::enable_if<
!IsFuture< ResolveResult >::value &&
!std::is_void< ResolveResult >::value
>::type*
>
Future< ResolveResult > Future< void >::_then( Resolve&& resolve, Reject&& reject ){
return then< ResolveResult >(
[ resolve ]( Promise< ResolveResult >&& promise ){
try {
promise.resolve( resolve() );
}
catch( const error::Exception& err ){
promise.reject( err );
}
},
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------------------------------- //
template<
typename Resolve,
typename Reject,
typename ResolveResult,
typename std::enable_if< std::is_void< ResolveResult >::value >::type*
>
Future< void > Future< void >::_then( Resolve&& resolve, Reject&& reject ){
return then< void >(
[ resolve ]( Promise< void >&& promise ){
try {
resolve();
}
catch( const error::Exception& err ){
promise.reject( err );
return;
}
promise.resolve();
},
std::forward< Reject >( reject )
);
}
// ---------------------------------------------------------------------------------------------- //
inline void Future< void >::then( promise_type&& promise ){
auto next = std::make_shared< promise_type >( std::move( promise ) );
auto prev = m_state;
m_state->resolve = [ prev, next ]() mutable {
next->resolve();
prev->reject = nullptr;
prev.reset();
};
m_state->reject = [ prev, next ]( const error::Exception& err ) mutable {
next->reject( err );
prev->resolve = nullptr;
prev.reset();
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment