#ifndef _CPPROMISE_HPP #define _CPPROMISE_HPP /** * MIT License * Copyright (c) 2018 Ted Yin * * 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. */ #include #include #include #if __has_include("any") #include #endif #if !defined(__cpp_lib_any) #include #endif /** * Implement type-safe Promise primitives similar to the ones specified by * Javascript Promise/A+. */ namespace promise { #if defined(__cpp_lib_any) using pm_any_t = std::any; template constexpr auto any_cast = static_cast(std::any_cast); using bad_any_cast = std::bad_any_cast; #else #warning "using any type from boost" using pm_any_t = boost::any; template constexpr auto any_cast = static_cast(boost::any_cast); using bad_any_cast = boost::bad_any_cast; #endif struct None {}; const auto none = None(); using callback_t = std::function; using values_t = std::vector; /* match lambdas */ template struct function_traits: public function_traits {}; template struct function_traits { using ret_type = ReturnType; using arg_type = None; using empty_arg = void; }; /* match plain functions */ template struct function_traits { using ret_type = ReturnType; using arg_type = ArgType; using non_empty_arg = void; }; /* match function pointers */ template struct function_traits: public function_traits {}; /* match const member functions */ template struct function_traits: public function_traits {}; /* match member functions */ template struct function_traits: public function_traits {}; class Promise; class promise_t: std::shared_ptr { public: friend Promise; template friend promise_t all(PList promise_list); template promise_t(Func callback): std::shared_ptr(std::make_shared()) { callback(*this); } template inline void resolve(T result) const; template inline void reject(T reason) const; template inline promise_t then(FuncFulfilled on_fulfilled) const; template inline promise_t then(FuncFulfilled on_fulfilled, FuncRejected on_rejected) const; template inline promise_t fail(FuncRejected on_rejected) const; }; #define PROMISE_ERR_INVALID_STATE do {throw std::runtime_error("invalid promise state");} while (0) #define PROMISE_ERR_MISMATCH_TYPE do {throw std::runtime_error("mismatching promise value types");} while (0) class Promise { std::vector fulfilled_callbacks; std::vector rejected_callbacks; enum class State { Pending, Fulfilled, Rejected, } state; pm_any_t result; pm_any_t reason; void add_on_fulfilled(callback_t cb) { fulfilled_callbacks.push_back(cb); } void add_on_rejected(callback_t cb) { rejected_callbacks.push_back(cb); } template typename std::enable_if< std::is_same::ret_type, promise_t>::value>::type gen_on_fulfilled(Func on_fulfilled, const promise_t &npm, callback_t &ret) { ret = [this, on_fulfilled, npm]() mutable { on_fulfilled(result)->then( [npm] (pm_any_t result_) { npm->resolve(result_); return pm_any_t(none); }, [npm] (pm_any_t reason_) { npm->reject(reason_); return pm_any_t(none); }); }; } template typename std::enable_if< std::is_same::ret_type, pm_any_t>::value>::type gen_on_fulfilled(Func on_fulfilled, const promise_t &npm, callback_t &ret) { ret = [this, on_fulfilled, npm]() mutable { npm->resolve(on_fulfilled(result)); }; } template typename std::enable_if< std::is_same::ret_type, promise_t>::value>::type gen_on_rejected(Func on_rejected, const promise_t &npm, callback_t &ret) { ret = [this, on_rejected, npm]() mutable { on_rejected(reason)->then( [npm] (pm_any_t result_) { npm->resolve(result_); return pm_any_t(none); }, [npm] (pm_any_t reason_) { npm->reject(reason_); return pm_any_t(none); }); }; } template typename std::enable_if< std::is_same::ret_type, pm_any_t>::value>::type gen_on_rejected(Func on_rejected, const promise_t &npm, callback_t &ret) { ret = [this, on_rejected, npm]() mutable { npm->reject(on_rejected(reason)); }; } public: Promise(): state(State::Pending) {} ~Promise() {} template promise_t then(FuncFulfilled on_fulfilled, FuncRejected on_rejected) { switch (state) { case State::Pending: return promise_t([this, on_fulfilled, on_rejected](promise_t npm) { callback_t ret; gen_on_fulfilled(on_fulfilled, npm, ret); add_on_fulfilled(ret); gen_on_rejected(on_rejected, npm, ret); add_on_rejected(ret); }); case State::Fulfilled: return promise_t([this, on_fulfilled](promise_t npm) { callback_t ret; gen_on_fulfilled(on_fulfilled, npm, ret); ret(); }); case State::Rejected: return promise_t([this, on_rejected](promise_t npm) { callback_t ret; gen_on_rejected(on_rejected, npm, ret); ret(); }); default: PROMISE_ERR_INVALID_STATE; } } template promise_t then(FuncFulfilled on_fulfilled) { switch (state) { case State::Pending: return promise_t([this, on_fulfilled](promise_t npm) { callback_t ret; gen_on_fulfilled(on_fulfilled, npm, ret); add_on_fulfilled(ret); add_on_rejected([this, npm]() {npm->reject(reason);}); }); case State::Fulfilled: return promise_t([this, on_fulfilled](promise_t npm) { callback_t ret; gen_on_fulfilled(on_fulfilled, npm, ret); ret(); }); case State::Rejected: return promise_t([this](promise_t npm) {npm->reject(reason);}); default: PROMISE_ERR_INVALID_STATE; } } template promise_t fail(FuncRejected on_rejected) { switch (state) { case State::Pending: return promise_t([this, on_rejected](promise_t npm) { callback_t ret; gen_on_rejected(on_rejected, npm, ret); add_on_rejected(ret); add_on_fulfilled([this, npm]() {npm->resolve(result);}); }); case State::Fulfilled: return promise_t([this](promise_t npm) {npm->resolve(result);}); case State::Rejected: return promise_t([this, on_rejected](promise_t npm) { callback_t ret; gen_on_rejected(on_rejected, npm, ret); ret(); }); default: PROMISE_ERR_INVALID_STATE; } } void resolve(pm_any_t _result) { switch (state) { case State::Pending: result = _result; state = State::Fulfilled; for (const auto &cb: fulfilled_callbacks) cb(); rejected_callbacks.clear(); break; default: break; } } void reject(pm_any_t _reason) { switch (state) { case State::Pending: reason = _reason; state = State::Rejected; for (const auto &cb: rejected_callbacks) cb(); rejected_callbacks.clear(); break; default: break; } } }; template promise_t all(PList promise_list) { return promise_t([promise_list] (promise_t npm) { auto size = std::make_shared(promise_list.size()); auto results = std::make_shared(); if (!size) PROMISE_ERR_MISMATCH_TYPE; results->resize(*size); size_t idx = 0; for (const auto &pm: promise_list) { pm->then( [results, size, idx, npm](pm_any_t result) { (*results)[idx] = result; if (!--(*size)) npm->resolve(*results); return pm_any_t(none); }, [npm, size](pm_any_t reason) { npm->reject(reason); return pm_any_t(none); }); idx++; } }); } template inline void promise_t::resolve(T result) const { (*this)->resolve(result); } template inline void promise_t::reject(T reason) const { (*this)->reject(reason); } template struct callback_types { using arg_type = typename function_traits::arg_type; using ret_type = typename std::conditional< std::is_same::ret_type, promise_t>::value, promise_t, pm_any_t>::type; }; template::ret_type, void>::value>::type * = nullptr> constexpr auto convert_void_func_(Func f) { return [f](typename function_traits::arg_type v) { f(v); return none; }; } template::ret_type, void>::value>::type * = nullptr> constexpr auto convert_void_func_(Func f) { return f; } template::empty_arg * = nullptr> constexpr auto convert_void_func(Func f) { return convert_void_func_([f](None) {f();}); } template::non_empty_arg * = nullptr> constexpr auto convert_void_func(Func f) { return convert_void_func_(f); } template inline promise_t promise_t::then(FuncFulfilled on_fulfilled) const { using fulfill_t = callback_types; return (*this)->then( [on_fulfilled](pm_any_t _result) mutable { try { return typename fulfill_t::ret_type(convert_void_func(on_fulfilled)( any_cast(_result))); } catch (bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } }); } template inline promise_t promise_t::then(FuncFulfilled on_fulfilled, FuncRejected on_rejected) const { using fulfill_t = callback_types; using reject_t = callback_types; return (*this)->then( [on_fulfilled](pm_any_t _result) mutable { try { return typename fulfill_t::ret_type(convert_void_func(on_fulfilled)( any_cast(_result))); } catch (bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } }, [on_rejected](pm_any_t _reason) mutable { try { return typename reject_t::ret_type(convert_void_func(on_rejected)( any_cast(_reason))); } catch (bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } }); } template inline promise_t promise_t::fail(FuncRejected on_rejected) const { using reject_t = callback_types; return (*this)->fail( [on_rejected](pm_any_t _reason) mutable { try { return typename reject_t::ret_type(convert_void_func(on_rejected)( any_cast(_reason))); } catch (bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } }); } } #endif