From a979e6d6d182850925c8dd65bd5f34c72063d895 Mon Sep 17 00:00:00 2001 From: Determinant Date: Sat, 27 Jan 2018 18:36:09 -0500 Subject: init --- .gitignore | 2 + Makefile | 2 + promise.hpp | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.cpp | 79 +++++++++++++++ 4 files changed, 410 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 promise.hpp create mode 100644 test.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9db44c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.hpp.gch +test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9b09161 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: test.cpp promise.hpp + g++ -o test test.cpp -std=c++17 diff --git a/promise.hpp b/promise.hpp new file mode 100644 index 0000000..e197803 --- /dev/null +++ b/promise.hpp @@ -0,0 +1,327 @@ +#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; + enum class State { + Pending, + Fulfilled, + Rejected, + } state; + pm_any_t result; + pm_any_t reason; + + 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(); + }); + } + + 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([](promise_t npm) {}); + 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([](promise_t npm) {}); + 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(); + break; + default: + break; + } + } + + void reject(pm_any_t _reason) { + switch (state) + { + case State::Pending: + reason = _reason; + state = State::Rejected; + rejected_callback(); + 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 diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..327f5a6 --- /dev/null +++ b/test.cpp @@ -0,0 +1,79 @@ +#include +#include "promise.hpp" +using promise::promise_t; + +int main() { + std::function t1; + std::function t2; + std::function t3; + std::function t4; + std::function t5; + auto pm = promise_t([&t1](promise_t pm) { + puts("pm1"); + //t1 = [pm]() {pm.reject(5);}; + t1 = [pm]() {pm.resolve(5);}; + }).then(std::function([](int x) { + printf("%d\n", x); + return 6; + })).then(std::function([](int y) { + printf("%d\n", y); + return 0; + })).then(std::function([&t2](int x) { + auto pm2 = promise_t([x, &t2](promise_t pm2) { + printf("pm2 %d\n", x); + t2 = [pm2]() {pm2.resolve(std::string("hello"));}; + }); + return pm2; + })).then(std::function([](std::string s) { + printf("%s\n", s.c_str()); + return 10; + })).then(std::function([](int x) { + printf("%d\n", x); + return promise::none; + })); + auto p1 = promise_t([&t4](promise_t pm) { + puts("p1"); + t4 = [pm]() {pm.resolve(1);}; + }); + + auto p2 = promise_t([&t5](promise_t pm) { + puts("p2"); + t5 = [pm]() {pm.resolve(std::string("hello"));}; + }); + + auto p3 = promise_t([&t3](promise_t pm) { + puts("p3"); + t3 = [pm]() {pm.resolve(std::string("world"));}; + }); + + auto p4 = promise::all(std::vector{p1, p2, p3}) + .then(std::function( + [](const promise::values_t values) { + printf("%d %s %s\n", std::any_cast(values[0]), + std::any_cast(values[1]).c_str(), + std::any_cast(values[2]).c_str()); + return 100; + })); + + auto p5 = promise::all(std::vector{pm, p4}) + .fail(std::function( + [](int reason) { + printf("reason: %d\n", reason); + return reason; + })) + .then(std::function( + [](const promise::values_t values) { + printf("finally %d\n", std::any_cast(values[1])); + return promise::none; + })); + puts("calling t"); + t4(); + puts("calling t2"); + t5(); + puts("calling t3"); + t3(); + + t1(); + printf("=== after ===\n"); + t2(); +} -- cgit v1.2.3-70-g09d2