From bc940e3365faad180a536d602f7ae0110515ee68 Mon Sep 17 00:00:00 2001 From: Determinant Date: Sat, 3 Feb 2018 19:35:43 -0500 Subject: ... --- .travis.yml | 34 ++++++++++++++++++++++ README.rst | 65 ++++++++++++++++++++++++++++++++++++++++++ promise.hpp | 20 ++++++++----- test.cpp | 92 ++++++++++++++++++++++++++++++++---------------------------- test_ref.txt | 24 ++++++++++++++++ 5 files changed, 185 insertions(+), 50 deletions(-) create mode 100644 .travis.yml create mode 100644 README.rst create mode 100644 test_ref.txt 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 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 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 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 + 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 + 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 + 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 #include #include - -#if __has_include("any") -#include +#include + +#ifdef __has_include +# if __has_include() +# include +# ifdef __cpp_lib_any +# define _CPPROMISE_STD_ANY +# endif +# endif #endif -#if !defined(__cpp_lib_any) +#ifndef _CPPROMISE_STD_ANY #include #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 constexpr auto any_cast = static_cast(std::any_cast); 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 constexpr auto any_cast = static_cast(boost::any_cast); diff --git a/test.cpp b/test.cpp index f169828..afa0f3f 100644 --- a/test.cpp +++ b/test.cpp @@ -1,61 +1,66 @@ #include +#include #include "promise.hpp" + +using callback_t = std::function; 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 t1; - std::function t2; - std::function t3; - std::function t4; - std::function 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{p1, p2, p3}) + auto pm6 = promise::all(std::vector{pm3, pm4, pm5}) .then([](const promise::values_t values) { - printf("%d %s %s\n", any_cast(values[0]), - any_cast(values[1]).c_str(), - any_cast(values[2]).c_str()); + printf("promise 3, 4, 5 resolved with %d, %.2f, \"%s\"\n", + any_cast(values[0]), + any_cast(values[1]), + any_cast(values[2]).c_str()); return 100; }); - auto p5 = promise::all(std::vector{pm, p4}) + auto pm7 = promise::all(std::vector{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(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 -- cgit v1.2.3-70-g09d2