aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeterminant <ted.sybil@gmail.com>2018-02-03 19:35:43 -0500
committerDeterminant <ted.sybil@gmail.com>2018-02-03 20:16:07 -0500
commitbc940e3365faad180a536d602f7ae0110515ee68 (patch)
treee17065bbf615c83531fad8b01dc849b5b1cc59b5
parent86b1bab87e0a4049f6fc9d2fedec323556f5ef27 (diff)
...
-rw-r--r--.travis.yml34
-rw-r--r--README.rst65
-rw-r--r--promise.hpp20
-rw-r--r--test.cpp92
-rw-r--r--test_ref.txt24
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>);
diff --git a/test.cpp b/test.cpp
index f169828..afa0f3f 100644
--- a/test.cpp
+++ b/test.cpp
@@ -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