327 lines
10 KiB
C++
327 lines
10 KiB
C++
#ifndef __PROMISE_H__
|
|
#define __PROMISE_H__
|
|
|
|
#include <exception>
|
|
|
|
class PromiseUndefinedException : public std::exception {
|
|
public:
|
|
};
|
|
|
|
class PromiseConversionException : public std::exception {
|
|
public:
|
|
};
|
|
|
|
#include "Private/PromiseHandler.h"
|
|
#include "Private/PromiseResolver.h"
|
|
#include <thread>
|
|
|
|
template <typename T>
|
|
class Promise;
|
|
|
|
template <typename T>
|
|
class PromiseBase {
|
|
friend struct PromiseFulfill<Promise<T>>;
|
|
|
|
public:
|
|
using Type = T;
|
|
template <typename F, typename std::enable_if<ArgsOf<F>::count == 1, int>::type = 0>
|
|
PromiseBase(F callback) : m_d{std::make_shared<PromiseData<T>>()} {
|
|
PromiseResolver<T> resolver{*this};
|
|
try {
|
|
callback(PromiseResolve<T>(resolver));
|
|
} catch (...) {
|
|
resolver.reject(std::current_exception());
|
|
}
|
|
}
|
|
|
|
template <typename F, typename std::enable_if<ArgsOf<F>::count != 1, int>::type = 0>
|
|
PromiseBase(F callback) : m_d{std::make_shared<PromiseData<T>>()} {
|
|
PromiseResolver<T> resolver{*this};
|
|
try {
|
|
callback(PromiseResolve<T>(resolver), PromiseReject<T>(resolver));
|
|
} catch (...) {
|
|
resolver.reject(std::current_exception());
|
|
}
|
|
}
|
|
|
|
PromiseBase(const PromiseBase<T> &other) : m_d{other.m_d} {
|
|
}
|
|
PromiseBase(const Promise<T> &other) : m_d{other.m_d} {
|
|
}
|
|
PromiseBase(PromiseBase<T> &&other) {
|
|
swap(other);
|
|
}
|
|
|
|
virtual ~PromiseBase() {
|
|
}
|
|
|
|
PromiseBase<T> &operator=(const PromiseBase<T> &other) {
|
|
m_d = other.m_d;
|
|
return *this;
|
|
}
|
|
PromiseBase<T> &operator=(PromiseBase<T> &&other) {
|
|
PromiseBase<T>(std::move(other)).swap(*this);
|
|
return *this;
|
|
}
|
|
|
|
bool operator==(const PromiseBase<T> &other) const {
|
|
return (m_d == other.m_d);
|
|
}
|
|
bool operator!=(const PromiseBase<T> &other) const {
|
|
return (m_d != other.m_d);
|
|
}
|
|
|
|
void swap(PromiseBase<T> &other) {
|
|
std::swap(m_d, other.m_d);
|
|
}
|
|
|
|
bool isFulfilled() const {
|
|
return m_d->isFulfilled();
|
|
}
|
|
bool isRejected() const {
|
|
return m_d->isRejected();
|
|
}
|
|
bool isPending() const {
|
|
return m_d->isPending();
|
|
}
|
|
|
|
template <typename TFulfilled, typename TRejected>
|
|
typename PromiseHandler<T, TFulfilled>::PromiseType then(const TFulfilled &fulfilled, const TRejected &rejected) const {
|
|
using PromiseType = typename PromiseHandler<T, TFulfilled>::PromiseType;
|
|
|
|
PromiseType next(
|
|
[&](const PromiseResolve<typename PromiseType::Type> &resolve, const PromiseReject<typename PromiseType::Type> &reject) {
|
|
m_d->addHandler(PromiseHandler<T, TFulfilled>::create(fulfilled, resolve, reject));
|
|
m_d->addCatcher(PromiseCatcher<T, TRejected>::create(rejected, resolve, reject));
|
|
});
|
|
|
|
if (!m_d->isPending()) {
|
|
m_d->dispatch();
|
|
}
|
|
|
|
return next;
|
|
}
|
|
|
|
template <typename TFulfilled>
|
|
typename PromiseHandler<T, TFulfilled>::PromiseType then(TFulfilled &&fulfilled) const {
|
|
return then(std::forward<TFulfilled>(fulfilled), nullptr);
|
|
}
|
|
|
|
template <typename THandler>
|
|
Promise<T> finally(THandler handler) const {
|
|
Promise<T> p = *this;
|
|
return p.then(handler, handler).then([=]() { return p; });
|
|
}
|
|
|
|
template <typename TRejected>
|
|
typename PromiseHandler<T, std::nullptr_t>::PromiseType fail(TRejected &&rejected) const {
|
|
return then(nullptr, std::forward<TRejected>(rejected));
|
|
}
|
|
|
|
Promise<T> wait() const {
|
|
while (m_d->isPending()) {
|
|
std::this_thread::yield();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
template <typename E>
|
|
static Promise<T> reject(E &&error) {
|
|
return Promise<T>{[&](const PromiseResolve<T> &, const PromiseReject<T> &reject) { reject(std::forward<E>(error)); }};
|
|
}
|
|
|
|
template <typename THandler>
|
|
Promise<T> tapFail(THandler handler) const {
|
|
Promise<T> p = *this;
|
|
return p.then([]() {}, handler).then([=]() { return p; });
|
|
}
|
|
|
|
template <typename THandler>
|
|
Promise<T> tap(THandler handler) const {
|
|
Promise<T> p = *this;
|
|
return p.then(handler).then([=]() { return p; });
|
|
}
|
|
|
|
#ifdef BOOST_ASIO_EXECUTION_EXECUTOR
|
|
template <class Executor, class Rep, class Period>
|
|
Promise<T> delay(Executor &ioContext, const std::chrono::duration<Rep, Period> &duration) const {
|
|
return tap([&]() {
|
|
return Promise<void>{[&](const PromiseResolve<void> &resolve) {
|
|
auto timer = std::make_shared<boost::asio::steady_timer>(ioContext, duration);
|
|
timer->async_wait([timer, resolve](const boost::system::error_code & /*e*/) mutable {
|
|
boost::asio::post(timer->get_executor(), resolve);
|
|
});
|
|
}};
|
|
});
|
|
}
|
|
|
|
template <class Executor>
|
|
Promise<T> delay(Executor &ioContext, int milliseconds) const {
|
|
return delay(ioContext, std::chrono::milliseconds(milliseconds));
|
|
}
|
|
#endif
|
|
|
|
protected:
|
|
std::shared_ptr<PromiseData<T>> m_d;
|
|
};
|
|
|
|
namespace PromiseHelper {
|
|
template <typename T>
|
|
typename PromiseDeduce<T>::Type resolve(T &&value) {
|
|
using PromiseType = typename PromiseDeduce<T>::Type;
|
|
using ValueType = typename PromiseType::Type;
|
|
using ResolveType = PromiseResolve<ValueType>;
|
|
using RejectType = PromiseReject<ValueType>;
|
|
|
|
return PromiseType{[&](ResolveType &&resolve, RejectType &&reject) {
|
|
PromiseFulfill<std::decay_t<T>>::call(std::forward<T>(value), std::forward<ResolveType>(resolve), std::forward<RejectType>(reject));
|
|
}};
|
|
}
|
|
|
|
Promise<void> resolve();
|
|
|
|
template <typename Functor, typename... Args>
|
|
typename PromiseFunctor<Functor, Args...>::PromiseType attempt(Functor &&fn, Args &&...args) {
|
|
using FunctorType = PromiseFunctor<Functor, Args...>;
|
|
using PromiseType = typename FunctorType::PromiseType;
|
|
using ValueType = typename PromiseType::Type;
|
|
|
|
// NOTE: std::forward<T<U>>: MSVC 2013 fails when forwarding
|
|
// template type (error: "expects 4 arguments - 0 provided").
|
|
// However it succeeds with type alias.
|
|
// TODO: should we expose QPromise::ResolveType & RejectType?
|
|
using ResolveType = PromiseResolve<ValueType>;
|
|
using RejectType = PromiseReject<ValueType>;
|
|
|
|
return PromiseType{[&](ResolveType &&resolve, RejectType &&reject) {
|
|
PromiseDispatch<typename FunctorType::ResultType>::call(std::forward<ResolveType>(resolve), std::forward<RejectType>(reject),
|
|
std::forward<Functor>(fn), std::forward<Args>(args)...);
|
|
}};
|
|
}
|
|
|
|
template <typename T, template <typename, typename...> class Sequence = std::vector, typename... Args>
|
|
Promise<std::vector<T>> all(const Sequence<Promise<T>, Args...> &promises) {
|
|
const int count = static_cast<int>(promises.size());
|
|
if (count == 0) {
|
|
return PromiseHelper::resolve(std::vector<T>{});
|
|
}
|
|
|
|
return Promise<std::vector<T>>{[=](const PromiseResolve<std::vector<T>> &resolve, const PromiseReject<std::vector<T>> &reject) {
|
|
auto remaining = std::make_shared<int>(count);
|
|
auto results = std::make_shared<std::vector<T>>(count);
|
|
|
|
int i = 0;
|
|
for (const auto &promise : promises) {
|
|
promise.then(
|
|
[=](const T &res) mutable {
|
|
(*results)[i] = res;
|
|
if (--(*remaining) == 0) {
|
|
resolve(*results);
|
|
}
|
|
},
|
|
[=]() mutable {
|
|
if (*remaining != -1) {
|
|
*remaining = -1;
|
|
reject(std::current_exception());
|
|
}
|
|
});
|
|
|
|
i++;
|
|
}
|
|
}};
|
|
}
|
|
|
|
template <template <typename, typename...> class Sequence = std::vector, typename... Args>
|
|
Promise<void> all(const Sequence<Promise<void>, Args...> &promises);
|
|
|
|
}; // namespace PromiseHelper
|
|
|
|
template <typename T>
|
|
class Promise : public PromiseBase<T> {
|
|
friend class PromiseResolver<T>;
|
|
|
|
public:
|
|
template <typename F>
|
|
Promise(F &&resolver) : PromiseBase<T>(std::forward<F>(resolver)) {
|
|
}
|
|
|
|
template <typename U>
|
|
Promise<U> convert() const {
|
|
return PromiseBase<T>::then(PromiseConverter<T, U>::create());
|
|
}
|
|
|
|
template <typename Functor>
|
|
Promise<T> each(Functor fn) {
|
|
return this->tap([=](const T &values) {
|
|
int i = 0;
|
|
|
|
std::vector<Promise<void>> promises;
|
|
for (const auto &v : values) {
|
|
promises.push_back(PromiseHelper::attempt(fn, v, i).then([]() {
|
|
// Cast to void in case fn returns a non promise value.
|
|
// TODO remove when implicit cast is implemented.
|
|
}));
|
|
|
|
i++;
|
|
}
|
|
|
|
return PromiseHelper::all(promises);
|
|
});
|
|
}
|
|
|
|
static Promise<T> resolve(const T &value) {
|
|
return Promise<T>{[&](const PromiseResolve<T> &resolve) { resolve(value); }};
|
|
}
|
|
};
|
|
|
|
template <>
|
|
class Promise<void> : public PromiseBase<void> {
|
|
friend class PromiseResolver<void>;
|
|
|
|
public:
|
|
template <typename F>
|
|
Promise(F &&resolver) : PromiseBase<void>(std::forward<F>(resolver)) {
|
|
}
|
|
|
|
public:
|
|
static Promise<void> resolve() {
|
|
return Promise<void>{[](const PromiseResolve<void> &resolve) { resolve(); }};
|
|
}
|
|
|
|
private:
|
|
friend class PromiseBase<void>;
|
|
};
|
|
|
|
namespace PromiseHelper {
|
|
Promise<void> resolve() {
|
|
return Promise<void>{[](const PromiseResolve<void> &resolve) { resolve(); }};
|
|
}
|
|
|
|
template <template <typename, typename...> class Sequence = std::vector, typename... Args>
|
|
Promise<void> all(const Sequence<Promise<void>, Args...> &promises) {
|
|
const int count = static_cast<int>(promises.size());
|
|
if (count == 0) {
|
|
return PromiseHelper::resolve();
|
|
}
|
|
|
|
return Promise<void>{[=](const PromiseResolve<void> &resolve, const PromiseReject<void> &reject) {
|
|
auto remaining = std::make_shared<int>(count);
|
|
for (const auto &promise : promises) {
|
|
promise.then(
|
|
[=]() {
|
|
if (--(*remaining) == 0) {
|
|
resolve();
|
|
}
|
|
},
|
|
[=]() {
|
|
if (*remaining != -1) {
|
|
*remaining = -1;
|
|
reject(std::current_exception());
|
|
}
|
|
});
|
|
}
|
|
}};
|
|
}
|
|
} // namespace PromiseHelper
|
|
|
|
#endif // __PROMISE_H__
|