Implement QtPromise::attempt(functor, args...)

Add a new helper that calls functor immediately and returns a promise fulfilled with the value returned by functor. Any synchronous exceptions will be turned into rejections on the returned promise. This is a convenient method that can be used instead of handling both synchronous and asynchronous exception flows.

Also simplify PromiseDispatch which now calls the functor with a variable number of arguments (including none).
This commit is contained in:
Simon Brunel 2018-05-21 21:33:05 +02:00
parent 4fa7a37750
commit f610826ef0
8 changed files with 200 additions and 55 deletions

View File

@ -22,5 +22,6 @@
* [::resolve (static)](qtpromise/qpromise/resolve.md) * [::resolve (static)](qtpromise/qpromise/resolve.md)
* [qPromise](qtpromise/helpers/qpromise.md) * [qPromise](qtpromise/helpers/qpromise.md)
* [qPromiseAll](qtpromise/helpers/qpromiseall.md) * [qPromiseAll](qtpromise/helpers/qpromiseall.md)
* [QtPromise::attempt](qtpromise/helpers/attempt.md)
* [QtPromise::filter](qtpromise/helpers/filter.md) * [QtPromise::filter](qtpromise/helpers/filter.md)
* [QtPromise::map](qtpromise/helpers/map.md) * [QtPromise::map](qtpromise/helpers/map.md)

View File

@ -27,5 +27,6 @@
* [`qPromise`](helpers/qpromise.md) * [`qPromise`](helpers/qpromise.md)
* [`qPromiseAll`](helpers/qpromiseall.md) * [`qPromiseAll`](helpers/qpromiseall.md)
* [`QtPromise::attempt`](helpers/attempt.md)
* [`QtPromise::filter`](helpers/filter.md) * [`QtPromise::filter`](helpers/filter.md)
* [`QtPromise::map`](helpers/map.md) * [`QtPromise::map`](helpers/map.md)

View File

@ -0,0 +1,41 @@
## `QtPromise::attempt`
```cpp
QtPromise::attempt(Functor functor, Args...) -> QPromise<R>
// With:
// - Functor: Function(Args...) -> R | QPromise<R>
```
Calls `functor` immediately and returns a promise fulfilled with the value returned by
`functor`. Any synchronous exceptions will be turned into rejections on the returned
promise. This is a convenient method that can be used instead of handling both synchronous
and asynchronous exception flows.
The type `R` of the `output` promise depends on the type returned by the `functor` function.
If `functor` returns a promise (or `QFuture`), the `output` promise is delayed and will be
resolved by the returned promise.
```cpp
QPromise<QByteArray> download(const QUrl& url);
QPromise<QByteArray> process(const QUrl& url)
{
return QtPromise::attempt([&]() {
if (!url.isValid()) {
throw InvalidUrlException();
}
return download(url);
}
}
auto output = process(url);
// 'output' type: QPromise<QByteArray>
output.then([](const QByteArray& res) {
// {...}
}).fail([](const InvalidUrlException& err) {
// {...}
});
```

View File

@ -119,6 +119,13 @@ struct PromiseDeduce<QtPromise::QPromise<T>>
: public PromiseDeduce<T> : public PromiseDeduce<T>
{ }; { };
template <typename Functor, typename... Args>
struct PromiseFunctor
{
using ResultType = typename std::result_of<Functor(Args...)>::type;
using PromiseType = typename PromiseDeduce<ResultType>::Type;
};
template <typename T> template <typename T>
struct PromiseFulfill struct PromiseFulfill
{ {
@ -176,51 +183,17 @@ struct PromiseFulfill<QtPromise::QPromise<void>>
} }
}; };
template <typename T, typename TRes> template <typename Result>
struct PromiseDispatch struct PromiseDispatch
{ {
using Promise = typename PromiseDeduce<TRes>::Type; template <typename Resolve, typename Reject, typename Functor, typename... Args>
using ResType = Unqualified<TRes>; static void call(const Resolve& resolve, const Reject& reject, Functor fn, Args&&... args)
template <typename THandler, typename TResolve, typename TReject>
static void call(const T& value, THandler handler, const TResolve& resolve, const TReject& reject)
{ {
try { try {
PromiseFulfill<ResType>::call(handler(value), resolve, reject); PromiseFulfill<Unqualified<Result>>::call(
} catch (...) { fn(std::forward<Args>(args)...),
reject(std::current_exception()); resolve,
} reject);
}
};
template <typename T>
struct PromiseDispatch<T, void>
{
using Promise = QtPromise::QPromise<void>;
template <typename THandler, typename TResolve, typename TReject>
static void call(const T& value, THandler handler, const TResolve& resolve, const TReject& reject)
{
try {
handler(value);
resolve();
} catch (...) {
reject(std::current_exception());
}
}
};
template <typename TRes>
struct PromiseDispatch<void, TRes>
{
using Promise = typename PromiseDeduce<TRes>::Type;
using ResType = Unqualified<TRes>;
template <typename THandler, typename TResolve, typename TReject>
static void call(THandler handler, const TResolve& resolve, const TReject& reject)
{
try {
PromiseFulfill<ResType>::call(handler(), resolve, reject);
} catch (...) { } catch (...) {
reject(std::current_exception()); reject(std::current_exception());
} }
@ -228,15 +201,13 @@ struct PromiseDispatch<void, TRes>
}; };
template <> template <>
struct PromiseDispatch<void, void> struct PromiseDispatch<void>
{ {
using Promise = QtPromise::QPromise<void>; template <typename Resolve, typename Reject, typename Functor, typename... Args>
static void call(const Resolve& resolve, const Reject& reject, Functor fn, Args&&... args)
template <typename THandler, typename TResolve, typename TReject>
static void call(THandler handler, const TResolve& resolve, const TReject& reject)
{ {
try { try {
handler(); fn(std::forward<Args>(args)...);
resolve(); resolve();
} catch (...) { } catch (...) {
reject(std::current_exception()); reject(std::current_exception());
@ -248,7 +219,7 @@ template <typename T, typename THandler, typename TArg = typename ArgsOf<THandle
struct PromiseHandler struct PromiseHandler
{ {
using ResType = typename std::result_of<THandler(T)>::type; using ResType = typename std::result_of<THandler(T)>::type;
using Promise = typename PromiseDispatch<T, ResType>::Promise; using Promise = typename PromiseDeduce<ResType>::Type;
template <typename TResolve, typename TReject> template <typename TResolve, typename TReject>
static std::function<void(const T&)> create( static std::function<void(const T&)> create(
@ -257,7 +228,7 @@ struct PromiseHandler
const TReject& reject) const TReject& reject)
{ {
return [=](const T& value) { return [=](const T& value) {
PromiseDispatch<T, ResType>::call(value, std::move(handler), resolve, reject); PromiseDispatch<ResType>::call(resolve, reject, handler, value);
}; };
} }
}; };
@ -266,7 +237,7 @@ template <typename T, typename THandler>
struct PromiseHandler<T, THandler, void> struct PromiseHandler<T, THandler, void>
{ {
using ResType = typename std::result_of<THandler()>::type; using ResType = typename std::result_of<THandler()>::type;
using Promise = typename PromiseDispatch<T, ResType>::Promise; using Promise = typename PromiseDeduce<ResType>::Type;
template <typename TResolve, typename TReject> template <typename TResolve, typename TReject>
static std::function<void(const T&)> create( static std::function<void(const T&)> create(
@ -275,7 +246,7 @@ struct PromiseHandler<T, THandler, void>
const TReject& reject) const TReject& reject)
{ {
return [=](const T&) { return [=](const T&) {
PromiseDispatch<void, ResType>::call(handler, resolve, reject); PromiseDispatch<ResType>::call(resolve, reject, handler);
}; };
} }
}; };
@ -284,7 +255,7 @@ template <typename THandler>
struct PromiseHandler<void, THandler, void> struct PromiseHandler<void, THandler, void>
{ {
using ResType = typename std::result_of<THandler()>::type; using ResType = typename std::result_of<THandler()>::type;
using Promise = typename PromiseDispatch<void, ResType>::Promise; using Promise = typename PromiseDeduce<ResType>::Type;
template <typename TResolve, typename TReject> template <typename TResolve, typename TReject>
static std::function<void()> create( static std::function<void()> create(
@ -293,7 +264,7 @@ struct PromiseHandler<void, THandler, void>
const TReject& reject) const TReject& reject)
{ {
return [=]() { return [=]() {
PromiseDispatch<void, ResType>::call(handler, resolve, reject); PromiseDispatch<ResType>::call(resolve, reject, handler);
}; };
} }
}; };
@ -351,7 +322,7 @@ struct PromiseCatcher
try { try {
error.rethrow(); error.rethrow();
} catch (const TArg& error) { } catch (const TArg& error) {
PromiseDispatch<TArg, ResType>::call(error, handler, resolve, reject); PromiseDispatch<ResType>::call(resolve, reject, handler, error);
} catch (...) { } catch (...) {
reject(std::current_exception()); reject(std::current_exception());
} }
@ -374,7 +345,7 @@ struct PromiseCatcher<T, THandler, void>
try { try {
error.rethrow(); error.rethrow();
} catch (...) { } catch (...) {
PromiseDispatch<void, ResType>::call(handler, resolve, reject); PromiseDispatch<ResType>::call(resolve, reject, handler);
} }
}; };
} }

View File

@ -38,6 +38,32 @@ static inline QPromise<void> qPromiseAll(const Sequence<QPromise<void>, Args...>
return QPromise<void>::all(promises); return QPromise<void>::all(promises);
} }
template <typename Functor, typename... Args>
static inline typename QtPromisePrivate::PromiseFunctor<Functor, Args...>::PromiseType
attempt(Functor&& fn, Args&&... args)
{
using namespace QtPromisePrivate;
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 = QPromiseResolve<ValueType>;
using RejectType = QPromiseReject<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 Sequence, typename Functor> template <typename Sequence, typename Functor>
static inline typename QtPromisePrivate::PromiseMapper<Sequence, Functor>::PromiseType static inline typename QtPromisePrivate::PromiseMapper<Sequence, Functor>::PromiseType
map(const Sequence& values, Functor fn) map(const Sequence& values, Functor fn)

View File

@ -0,0 +1,5 @@
QT += concurrent
TARGET = tst_helpers_attempt
SOURCES += $$PWD/tst_attempt.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,99 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtConcurrent>
#include <QtTest>
// STL
#include <memory>
using namespace QtPromise;
class tst_helpers_attempt : public QObject
{
Q_OBJECT
private Q_SLOTS:
void voidResult();
void typedResult();
void futureResult();
void promiseResult();
void functorThrows();
void callWithParams();
};
QTEST_MAIN(tst_helpers_attempt)
#include "tst_attempt.moc"
void tst_helpers_attempt::voidResult()
{
auto p = QtPromise::attempt([]() {});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}
void tst_helpers_attempt::typedResult()
{
auto p = QtPromise::attempt([]() {
return QString("foo");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, QString()), QString("foo"));
}
void tst_helpers_attempt::futureResult()
{
auto p = QtPromise::attempt([]() {
return QtConcurrent::run([]() {
return QString("foo");
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QString()), QString("foo"));
}
void tst_helpers_attempt::promiseResult()
{
auto p = QtPromise::attempt([]() {
return QtPromise::qPromise(42).delay(200);
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), 42);
}
void tst_helpers_attempt::functorThrows()
{
auto p = QtPromise::attempt([]() {
if (true) {
throw QString("bar");
}
return 42;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForError(p, QString()), QString("bar"));
}
void tst_helpers_attempt::callWithParams()
{
auto p = QtPromise::attempt([&](int i, const QString& s) {
return QString("%1:%2").arg(i).arg(s);
}, 42, "foo");
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, QString()), QString("42:foo"));
}

View File

@ -1,6 +1,7 @@
TEMPLATE = subdirs TEMPLATE = subdirs
SUBDIRS += \ SUBDIRS += \
all \ all \
attempt \
filter \ filter \
map \ map \
reject \ reject \