#ifndef _CPPROMISE_HPP #define _CPPROMISE_HPP #include #include #include #include /** Implement type-safe Promise primitives similar to the ones specified by * Javascript A+. */ namespace promise { using std::function; using pm_any_t = std::any; 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 { //function fulfilled_callback; //function rejected_callback; std::vector> fulfilled_callbacks; std::vector> rejected_callbacks; enum class State { Pending, Fulfilled, Rejected, } state; pm_any_t result; pm_any_t reason; /* this implementation causes stack overflow because of the nested lambdas */ /* void add_on_fulfilled(function cb) { auto old_cbs = fulfilled_callback; fulfilled_callback = function( [cb, old_cbs]() { old_cbs(); cb(); }); } void add_on_rejected(function cb) { auto old_cbs = rejected_callback; rejected_callback = function( [cb, old_cbs]() { old_cbs(); cb(); }); } */ 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(): /* fulfilled_callback(do_nothing), rejected_callback(do_nothing), */ state(State::Pending) { //printf("%lx constructed\n", (uintptr_t)this); } ~Promise() { //printf("%lx freed\n", (uintptr_t)this); } 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; //fulfilled_callback(); 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; //rejected_callback(); 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(std::any_cast(_result))); } catch (std::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(std::any_cast(_result))); } catch (std::bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } }, [on_rejected](pm_any_t _reason) mutable { try { return reject_ret_type(on_rejected(std::any_cast(_reason))); } catch (std::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(std::any_cast(_reason))); } catch (std::bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } }); } } #endif