diff options
author | Determinant <[email protected]> | 2018-02-03 19:35:43 -0500 |
---|---|---|
committer | Determinant <[email protected]> | 2018-02-03 20:16:07 -0500 |
commit | bc940e3365faad180a536d602f7ae0110515ee68 (patch) | |
tree | e17065bbf615c83531fad8b01dc849b5b1cc59b5 | |
parent | 86b1bab87e0a4049f6fc9d2fedec323556f5ef27 (diff) |
...
-rw-r--r-- | .travis.yml | 34 | ||||
-rw-r--r-- | README.rst | 65 | ||||
-rw-r--r-- | promise.hpp | 20 | ||||
-rw-r--r-- | test.cpp | 92 | ||||
-rw-r--r-- | test_ref.txt | 24 |
5 files changed, 185 insertions, 50 deletions
diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..31ebff7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,34 @@ +language: cpp +compiler: gcc +sudo: required +dist: trusty + +matrix: + fast_finish: true + include: + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.9 + env: + - MATRIX_EVAL="CC=gcc-4.9 && CXX=g++-4.9" + + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-5 + env: + - MATRIX_EVAL="CC=gcc-5 && CXX=g++-5" + +before_install: + - sudo apt-get install libboost-all-dev + - eval "${MATRIX_EVAL}" + +script: + - make diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..8f4bc46 --- /dev/null +++ b/README.rst @@ -0,0 +1,65 @@ +CPPromise +========= + +This is a lightweight C++14/17 compatiable implementation of promises (similar +to Javascript Promise/A+). It allows type-safe polymorphic promises and incurs +little runtime overhead. The runtime type-checking is enforced and supported by +the underlying `any` type, an exception will be thrown when the resolved value +types do not match the types expected in the subsequent computation. See +`test.cpp` for detailed examples. + +API +=== + +:: + + typename<typename Func> promise_t(Func callback); + +Create a new promise object, the ``callback(promise_t pm)`` is invoked +immediately after the object is constructed, so usually the user registers +``pm`` to some external logic which triggers ``pm.resolve()`` or +``pm.reject()`` when the time comes. + +:: + + template<typename T> resolve(T result) const; + +Resolve the promise with value ``result``. This may trigger the other promises +waiting for the current promise recursively. When a promise is triggered, the +registered ``on_fulfilled()`` function will be invoked using ``result`` as the argument. + +:: + + tempalte<typename T> reject(T reason) const; + +Reject the promise with value ``result``. This may reject the other promises +waiting for the current promise recursively. When a promise is rejected, the +registered ``on_rejected()`` function will be invoked using ``reason`` as the argument. + +:: + + template<typename FuncFulfilled> + promise_t then(FuncFulfilled on_fulfilled) const; + +Create a new promise that waits for the resolution of the current promise. +``on_fulfilled`` will be called with result from the current promise when +resolved. The rejection will skip the callback and pass on to the promises that +follow the created promise. + +:: + + template<typename FuncRejected> + promise_t fail(FuncRejected on_rejected) const; + +Create a new promise that waits for the rejection of the current promise. +``on_rejected`` will be called with reason from the current promise when +rejected. The resolution will skip the callback and pass on to the promises +that follow the created promise. + +:: + + template<typename FuncFulfilled, typename FuncRejected> + inline promise_t then(FuncFulfilled on_fulfilled, + FuncRejected on_rejected) const; + +Create a promise that handles both resolution and rejection of the current promise. diff --git a/promise.hpp b/promise.hpp index 8fdf14e..1173e87 100644 --- a/promise.hpp +++ b/promise.hpp @@ -27,12 +27,18 @@ #include <vector> #include <memory> #include <functional> - -#if __has_include("any") -#include <any> +#include <type_traits> + +#ifdef __has_include +# if __has_include(<any>) +# include <any> +# ifdef __cpp_lib_any +# define _CPPROMISE_STD_ANY +# endif +# endif #endif -#if !defined(__cpp_lib_any) +#ifndef _CPPROMISE_STD_ANY #include <boost/any.hpp> #endif @@ -41,14 +47,14 @@ * Javascript Promise/A+. */ namespace promise { -#if defined(__cpp_lib_any) +#ifdef _CPPROMISE_STD_ANY using pm_any_t = std::any; template<typename T> constexpr auto any_cast = static_cast<T(*)(const std::any&)>(std::any_cast<T>); using bad_any_cast = std::bad_any_cast; #else -#warning "using boost::any" -#pragma message "using boost::any" +# warning "using boost::any" +# pragma message "using boost::any" using pm_any_t = boost::any; template<typename T> constexpr auto any_cast = static_cast<T(*)(const boost::any&)>(boost::any_cast<T>); @@ -1,61 +1,66 @@ #include <string> +#include <functional> #include "promise.hpp" + +using callback_t = std::function<void()>; using promise::promise_t; using promise::any_cast; struct A { int operator()(int x) { - printf("%d\n", x); - return 1; + printf("operator A got %d\n", x); + return x + 1; } }; struct B { promise_t operator()(int x) { - printf("%d\n", x); - return promise_t([](promise_t pm) {pm.resolve(1);}); + printf("operator B got %d\n", x); + return promise_t([x](promise_t pm) {pm.resolve(x + 1);}); } }; int f(int x) { - printf("%d\n", x); + printf("plain function f resolved with %d\n", x); return x + 1; } promise_t g(int x) { - printf("%d\n", x); + printf("plain function g resolved with %d\n", x); return promise_t([](promise_t pm) {pm.resolve(1);}); } int main() { - std::function<void()> t1; - std::function<void()> t2; - std::function<void()> t3; - std::function<void()> t4; - std::function<void()> t5; + callback_t t1; + callback_t t2; + callback_t t3; + callback_t t4; + callback_t t5; A a1, a2, a3; B b1, b2, b3; - auto pm = promise_t([&t1](promise_t pm) { - puts("pm1"); - //t1 = [pm]() {pm.reject(5);}; - t1 = [pm]() {pm.resolve(5);}; + auto pm1 = promise_t([&t1](promise_t pm) { + puts("promise 1 constructed, but won't be resolved immediately"); + t1 = [pm]() {pm.resolve(10);}; }).then([](int x) { - printf("%d\n", x); - return 6; - }).then([](int y) { - printf("%d\n", y); - return 0; + printf("got resolved x = %d, output x + 42\n", x); + return x + 42; + }).then([](int x) { + printf("got resolved x = %d, output x * 2\n", x); + return x * 2; }).then([&t2](int x) { auto pm2 = promise_t([x, &t2](promise_t pm2) { - printf("pm2 %d\n", x); - t2 = [pm2]() {pm2.resolve(std::string("hello"));}; + printf("get resolved x = %d, " + "promise 2 constructed, not resolved, " + "will be resolved with a string instead\n", x); + t2 = [pm2]() {pm2.resolve(std::string("promise 2 resolved"));}; }); return pm2; }).then([](std::string s) { - printf("%s\n", s.c_str()); - return 10; + printf("got string from promise 2: \"%s\", " + "output 11\n", s.c_str()); + return 11; }).then([](int x) { - printf("%d\n", x); + printf("got resolved x = %d, output 12\n", x); return 12; }).then(f).then(a1).fail(a2).then(b1).fail(b2).then(g).then(a3, b3) .then([](int x) { @@ -67,30 +72,31 @@ int main() { puts("void parameter will ignore the returned value"); }); - auto p1 = promise_t([&t4](promise_t pm) { - puts("p1"); + auto pm3 = promise_t([&t4](promise_t pm) { + puts("promise 3 constructed"); t4 = [pm]() {pm.resolve(1);}; }); - auto p2 = promise_t([&t5](promise_t pm) { - puts("p2"); - t5 = [pm]() {pm.resolve(std::string("hello"));}; + auto pm4 = promise_t([&t5](promise_t pm) { + puts("promise 4 constructed"); + t5 = [pm]() {pm.resolve(1.5);}; }); - auto p3 = promise_t([&t3](promise_t pm) { - puts("p3"); - t3 = [pm]() {pm.resolve(std::string("world"));}; + auto pm5 = promise_t([&t3](promise_t pm) { + puts("promise 5 constructed"); + t3 = [pm]() {pm.resolve(std::string("hello world"));}; }); - auto p4 = promise::all(std::vector<promise_t>{p1, p2, p3}) + auto pm6 = promise::all(std::vector<promise_t>{pm3, pm4, pm5}) .then([](const promise::values_t values) { - printf("%d %s %s\n", any_cast<int>(values[0]), - any_cast<std::string>(values[1]).c_str(), - any_cast<std::string>(values[2]).c_str()); + printf("promise 3, 4, 5 resolved with %d, %.2f, \"%s\"\n", + any_cast<int>(values[0]), + any_cast<double>(values[1]), + any_cast<std::string>(values[2]).c_str()); return 100; }); - auto p5 = promise::all(std::vector<promise_t>{pm, p4}) + auto pm7 = promise::all(std::vector<promise_t>{pm1, pm6}) .fail([](int reason) { printf("reason: %d\n", reason); return reason; @@ -98,14 +104,14 @@ int main() { .then([](const promise::values_t values) { printf("finally %d\n", any_cast<int>(values[1])); }); - puts("calling t"); + puts("calling t4: resolve promise 3"); t4(); - puts("calling t2"); + puts("calling t5: resolve promise 4"); t5(); - puts("calling t3"); + puts("calling t3: resolve promise 5"); t3(); - + puts("calling t1: resolve first half of promise 1"); t1(); - printf("=== after ===\n"); + puts("calling t2: resolve the second half of promise 1 (promise 2)"); t2(); } diff --git a/test_ref.txt b/test_ref.txt new file mode 100644 index 0000000..54d6079 --- /dev/null +++ b/test_ref.txt @@ -0,0 +1,24 @@ +promise 1 constructed, but won't be resolved immediately +promise 3 constructed +promise 4 constructed +promise 5 constructed +calling t4: resolve promise 3 +calling t5: resolve promise 4 +calling t3: resolve promise 5 +promise 3, 4, 5 resolved with 1, 1.50, "hello world" +calling t1: resolve first half of promise 1 +got resolved x = 10, output x + 42 +got resolved x = 52, output x * 2 +get resolved x = 104, promise 2 constructed, not resolved, will be resolved with a string instead +calling t2: resolve the second half of promise 1 (promise 2) +got string from promise 2: "promise 2 resolved", output 11 +got resolved x = 11, output 12 +plain function f resolved with 12 +operator A got 13 +operator B got 14 +plain function g resolved with 15 +operator A got 1 +void return is ok +void parameter is ok +void parameter will ignore the returned value +finally 100 |