#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 { using std::function; #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 using None = std::nullptr_t; using values_t = std::vector; const auto none = nullptr; const auto do_nothing = [](){}; /* match lambdas */ template struct function_traits: public function_traits {}; /* match plain functions */ template struct function_traits { using ret_type = ReturnType; using arg_type = ArgType; }; /* 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 ptr; public: template promise_t(Func callback) { 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_any(FuncFulfilled fulfilled_callback, FuncRejected rejected_callback) const; template inline promise_t then_any(FuncFulfilled fulfilled_callback) const; template inline promise_t fail_any(FuncRejected rejected_callback) 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(function cb) { fulfilled_callbacks.push_back(cb); } void add_on_rejected(function 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, function &ret) { ret = [this, on_fulfilled, npm]() mutable { on_fulfilled(result).then_any( [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, function &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, function &ret) { ret = [this, on_rejected, npm]() mutable { on_rejected(reason).then_any( [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, function &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) { function 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) { function ret; gen_on_fulfilled(on_fulfilled, npm, ret); ret(); }); case State::Rejected: return promise_t([this, on_rejected](promise_t npm) { function 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) { function 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) { function 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) { function 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) { function 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_any( [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 { ptr->resolve(result); } template inline void promise_t::reject(T reason) const { ptr->reject(reason); } template inline promise_t promise_t::then_any(FuncFulfilled fulfilled_callback, FuncRejected rejected_callback) const { return ptr->then(fulfilled_callback, rejected_callback); } template inline promise_t promise_t::then_any(FuncFulfilled fulfilled_callback) const { return ptr->then(fulfilled_callback); } template inline promise_t promise_t::fail_any(FuncRejected rejected_callback) const { return ptr->fail(rejected_callback); } template struct callback_type_conversion { using type = pm_any_t; }; template<> struct callback_type_conversion { using type = promise_t; }; template struct callback_types { using arg_type = typename function_traits::arg_type; using ret_type = typename callback_type_conversion::ret_type>::type; }; template inline promise_t promise_t::then(FuncFulfilled on_fulfilled) const { using arg_type = typename callback_types::arg_type; using ret_type = typename callback_types::ret_type; return ptr->then( [on_fulfilled](pm_any_t _result) mutable { try { return ret_type(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_arg_type = typename callback_types::arg_type; using fulfill_ret_type = typename callback_types::ret_type; using reject_arg_type = typename callback_types::arg_type; using reject_ret_type = typename callback_types::ret_type; return ptr->then( [on_fulfilled](pm_any_t _result) mutable { try { return fulfill_ret_type(on_fulfilled(any_cast(_result))); } catch (bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } }, [on_rejected](pm_any_t _reason) mutable { try { return reject_ret_type(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 arg_type = typename callback_types::arg_type; using ret_type = typename callback_types::ret_type; return ptr->fail( [on_rejected](pm_any_t _reason) mutable { try { return ret_type(on_rejected(any_cast(_reason))); } catch (bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } }); } } #endif