#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 = [](){}; class Promise; class promise_t { std::shared_ptr ptr; public: promise_t(function 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(function fulfilled_callback, function rejected_callback) const; template inline promise_t then(function fulfilled_callback) const; template inline promise_t fail(function rejected_callback) const; template inline promise_t then(function on_fulfilled) const; template inline promise_t then(function on_fulfilled, function on_rejected) const; template inline promise_t fail(function on_fulfilled) 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); } function gen_on_fulfilled( function on_fulfilled, const promise_t &npm) { return [this, on_fulfilled, npm]() { on_fulfilled(result).then( function([npm] (pm_any_t result_) { npm.resolve(result_); return none; }), function([npm] (pm_any_t reason_) { npm.reject(reason_); return none; })); }; } function gen_on_fulfilled( function on_fulfilled, const promise_t &npm) { return [this, on_fulfilled, npm]() { npm.resolve(on_fulfilled(result)); }; } function gen_on_rejected( function on_rejected, const promise_t &npm) { return [this, on_rejected, npm]() { on_rejected(reason).then( function([npm] (pm_any_t result_) { npm.resolve(result_); return none; }), function([npm] (pm_any_t reason_) { npm.reject(reason_); return none; })); }; } function gen_on_rejected( function on_rejected, const promise_t &npm) { return [this, on_rejected, npm]() { 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(function on_fulfilled, function on_rejected) { switch (state) { case State::Pending: return promise_t([this, on_fulfilled, on_rejected](promise_t npm) { add_on_fulfilled(gen_on_fulfilled(on_fulfilled, npm)); add_on_rejected(gen_on_rejected(on_rejected, npm)); }); case State::Fulfilled: return promise_t([this, on_fulfilled](promise_t npm) { gen_on_fulfilled(on_fulfilled, npm)(); }); case State::Rejected: return promise_t([this, on_rejected](promise_t npm) { gen_on_rejected(on_rejected, npm)(); }); default: PROMISE_ERR_INVALID_STATE; } } template promise_t then(function on_fulfilled) { switch (state) { case State::Pending: return promise_t([this, on_fulfilled](promise_t npm) { add_on_fulfilled(gen_on_fulfilled(on_fulfilled, npm)); add_on_rejected([this, npm]() {npm.reject(reason);}); }); case State::Fulfilled: return promise_t([this, on_fulfilled](promise_t npm) { gen_on_fulfilled(on_fulfilled, npm)(); }); case State::Rejected: return promise_t([this](promise_t npm) {npm.reject(reason);}); default: PROMISE_ERR_INVALID_STATE; } } template promise_t fail(function on_rejected) { switch (state) { case State::Pending: return promise_t([this, on_rejected](promise_t npm) { add_on_rejected(gen_on_rejected(on_rejected, npm)); 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) { add_on_rejected(gen_on_rejected(on_rejected, npm)); }); 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(); 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(); break; default: break; } } }; template promise_t all(PList promise_list) { auto size = std::make_shared(promise_list.size()); auto results = std::make_shared(); if (!size) PROMISE_ERR_MISMATCH_TYPE; results->resize(*size); return promise_t([=] (promise_t npm) { size_t idx = 0; for (const auto &pm: promise_list) { pm.then(function( [results, size, idx, npm](pm_any_t result) { (*results)[idx] = result; if (!--(*size)) npm.resolve(*results); return none; }), function( [npm, size](pm_any_t reason) { npm.reject(reason); return 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(function fulfilled_callback, function rejected_callback) const { return ptr->then(fulfilled_callback, rejected_callback); } template inline promise_t promise_t::then(function fulfilled_callback) const { return ptr->then(fulfilled_callback); } template inline promise_t promise_t::fail(function 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 inline promise_t promise_t::then(function on_fulfilled) const { using ret_type = typename callback_type_conversion::type; return ptr->then(function( [on_fulfilled](pm_any_t _result) { 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(function on_fulfilled, function on_rejected) const { using fulfill_ret_type = typename callback_type_conversion::type; using reject_ret_type = typename callback_type_conversion::type; return ptr->then(function( [on_fulfilled](pm_any_t _result) { try { return fulfill_ret_type(on_fulfilled(std::any_cast(_result))); } catch (std::bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } }), function( [on_rejected](pm_any_t _reason) { 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(function on_rejected) const { using ret_type = typename callback_type_conversion::type; return ptr->fail(function( [on_rejected](pm_any_t _reason) { try { return ret_type(on_rejected(std::any_cast(_reason))); } catch (std::bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } })); } } #endif