diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | promise.hpp | 327 | ||||
-rw-r--r-- | test.cpp | 79 |
4 files changed, 410 insertions, 0 deletions
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 <vector> +#include <memory> +#include <any> +#include <functional> + +/** 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<pm_any_t>; + const auto none = nullptr; + const auto do_nothing = [](){}; + + class Promise; + class promise_t { + std::shared_ptr<Promise> ptr; + public: + promise_t(function<void(promise_t)> callback) { + ptr = std::make_shared<Promise>(); + callback(*this); + } + + template<typename T> inline void resolve(T result) const; + template<typename T> inline void reject(T reason) const; + + template<typename F_, typename F = pm_any_t, typename R_, typename R = pm_any_t> + inline promise_t then(function<F_(pm_any_t)> fulfilled_callback, + function<R_(pm_any_t)> rejected_callback) const; + + template<typename F_, typename F = pm_any_t> + inline promise_t then(function<F_(pm_any_t)> fulfilled_callback) const; + + template<typename R_, typename R = pm_any_t> + inline promise_t fail(function<R_(pm_any_t)> rejected_callback) const; + + template<typename F_, typename F> + inline promise_t then(function<F_(F)> on_fulfilled) const; + + template<typename F_, typename F, typename R_, typename R> + inline promise_t then(function<F_(F)> on_fulfilled, function<R_(R)> on_rejected) const; + + template<typename F_, typename F> + inline promise_t fail(function<F_(F)> 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<void()> fulfilled_callback; + function<void()> rejected_callback; + enum class State { + Pending, + Fulfilled, + Rejected, + } state; + pm_any_t result; + pm_any_t reason; + + void add_on_fulfilled(function<void()> cb) { + auto old_cbs = fulfilled_callback; + fulfilled_callback = function<void()>( + [cb, old_cbs]() { + old_cbs(); + cb(); + }); + } + + void add_on_rejected(function<void()> cb) { + auto old_cbs = rejected_callback; + rejected_callback = function<void()>( + [cb, old_cbs]() { + old_cbs(); + cb(); + }); + } + + function<void()> gen_on_fulfilled( + function<promise_t(pm_any_t)> on_fulfilled, + const promise_t &npm) { + return [this, on_fulfilled, npm]() { + on_fulfilled(result).then( + function<None(pm_any_t)>([npm] (pm_any_t result_) { + npm.resolve(result_); + return none; + }), + function<None(pm_any_t)>([npm] (pm_any_t reason_) { + npm.reject(reason_); + return none; + })); + }; + } + + function<void()> gen_on_fulfilled( + function<pm_any_t(pm_any_t)> on_fulfilled, + const promise_t &npm) { + return [this, on_fulfilled, npm]() { + npm.resolve(on_fulfilled(result)); + }; + } + + function<void()> gen_on_rejected( + function<promise_t(pm_any_t)> on_rejected, + const promise_t &npm) { + return [this, on_rejected, npm]() { + on_rejected(reason).then( + function<None(pm_any_t)>([npm] (pm_any_t result_) { + npm.resolve(result_); + return none; + }), + function<None(pm_any_t)>([npm] (pm_any_t reason_) { + npm.reject(reason_); + return none; + })); + }; + } + + function<void()> gen_on_rejected( + function<pm_any_t(pm_any_t)> 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<typename OnFulfilled, typename OnRejected> + promise_t then(function<OnFulfilled(pm_any_t)> on_fulfilled, + function<OnRejected(pm_any_t)> 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<typename OnFulfilled> + promise_t then(function<OnFulfilled(pm_any_t)> 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<typename OnRejected> + promise_t fail(function<OnRejected(pm_any_t)> 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<typename PList> promise_t all(PList promise_list) { + auto size = std::make_shared<size_t>(promise_list.size()); + auto results = std::make_shared<values_t>(); + 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<pm_any_t(pm_any_t)>( + [results, size, idx, npm](pm_any_t result) { + (*results)[idx] = result; + if (!--(*size)) + npm.resolve(*results); + return none; + }), function<pm_any_t(pm_any_t)>( + [npm, size](pm_any_t reason) { + npm.reject(reason); + return none; + })); + idx++; + } + }); + } + + template<typename T> + inline void promise_t::resolve(T result) const { ptr->resolve(result); } + + template<typename T> + inline void promise_t::reject(T reason) const { ptr->reject(reason); } + + template<typename F_, typename F, typename R_, typename R> + inline promise_t promise_t::then(function<F_(pm_any_t)> fulfilled_callback, + function<R_(pm_any_t)> rejected_callback) const { + return ptr->then(fulfilled_callback, rejected_callback); + } + + template<typename F_, typename F> + inline promise_t promise_t::then(function<F_(pm_any_t)> fulfilled_callback) const { + return ptr->then(fulfilled_callback); + } + + template<typename R_, typename R> + inline promise_t promise_t::fail(function<R_(pm_any_t)> rejected_callback) const { + return ptr->fail(rejected_callback); + } + + template<typename T> + struct callback_type_conversion { + using type = pm_any_t; + }; + + template<> + struct callback_type_conversion<promise_t> { + using type = promise_t; + }; + + template<typename F_, typename F> + inline promise_t promise_t::then(function<F_(F)> on_fulfilled) const { + using ret_type = typename callback_type_conversion<F_>::type; + return ptr->then(function<ret_type(pm_any_t)>( + [on_fulfilled](pm_any_t _result) { + try { + return ret_type(on_fulfilled(std::any_cast<F>(_result))); + } catch (std::bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } + })); + } + + template<typename F_, typename F, typename R_, typename R> + inline promise_t promise_t::then(function<F_(F)> on_fulfilled, + function<R_(R)> on_rejected) const { + using fulfill_ret_type = typename callback_type_conversion<F_>::type; + using reject_ret_type = typename callback_type_conversion<R_>::type; + return ptr->then(function<fulfill_ret_type(pm_any_t)>( + [on_fulfilled](pm_any_t _result) { + try { + return fulfill_ret_type(on_fulfilled(std::any_cast<F>(_result))); + } catch (std::bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } + }), function<fulfill_ret_type(pm_any_t)>( + [on_rejected](pm_any_t _reason) { + try { + return reject_ret_type(on_rejected(std::any_cast<R>(_reason))); + } catch (std::bad_any_cast e) { PROMISE_ERR_MISMATCH_TYPE; } + })); + } + + template<typename R_, typename R> + inline promise_t promise_t::fail(function<R_(R)> on_rejected) const { + using ret_type = typename callback_type_conversion<R_>::type; + return ptr->fail(function<ret_type(pm_any_t)>( + [on_rejected](pm_any_t _reason) { + try { + return ret_type(on_rejected(std::any_cast<R>(_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 <string> +#include "promise.hpp" +using promise::promise_t; + +int main() { + std::function<void()> t1; + std::function<void()> t2; + std::function<void()> t3; + std::function<void()> t4; + std::function<void()> 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(int)>([](int x) { + printf("%d\n", x); + return 6; + })).then(std::function<int(int)>([](int y) { + printf("%d\n", y); + return 0; + })).then(std::function<promise_t(int)>([&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<int(std::string)>([](std::string s) { + printf("%s\n", s.c_str()); + return 10; + })).then(std::function<promise::None(int)>([](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<promise_t>{p1, p2, p3}) + .then(std::function<int(promise::values_t)>( + [](const promise::values_t values) { + printf("%d %s %s\n", std::any_cast<int>(values[0]), + std::any_cast<std::string>(values[1]).c_str(), + std::any_cast<std::string>(values[2]).c_str()); + return 100; + })); + + auto p5 = promise::all(std::vector<promise_t>{pm, p4}) + .fail(std::function<int(int)>( + [](int reason) { + printf("reason: %d\n", reason); + return reason; + })) + .then(std::function<promise::None(promise::values_t)>( + [](const promise::values_t values) { + printf("finally %d\n", std::any_cast<int>(values[1])); + return promise::none; + })); + puts("calling t"); + t4(); + puts("calling t2"); + t5(); + puts("calling t3"); + t3(); + + t1(); + printf("=== after ===\n"); + t2(); +} |