mirror of
https://github.com/simonbrunel/qtpromise.git
synced 2024-11-23 19:20:57 +08:00
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:
parent
4fa7a37750
commit
f610826ef0
@ -22,5 +22,6 @@
|
||||
* [::resolve (static)](qtpromise/qpromise/resolve.md)
|
||||
* [qPromise](qtpromise/helpers/qpromise.md)
|
||||
* [qPromiseAll](qtpromise/helpers/qpromiseall.md)
|
||||
* [QtPromise::attempt](qtpromise/helpers/attempt.md)
|
||||
* [QtPromise::filter](qtpromise/helpers/filter.md)
|
||||
* [QtPromise::map](qtpromise/helpers/map.md)
|
||||
|
@ -27,5 +27,6 @@
|
||||
|
||||
* [`qPromise`](helpers/qpromise.md)
|
||||
* [`qPromiseAll`](helpers/qpromiseall.md)
|
||||
* [`QtPromise::attempt`](helpers/attempt.md)
|
||||
* [`QtPromise::filter`](helpers/filter.md)
|
||||
* [`QtPromise::map`](helpers/map.md)
|
||||
|
41
docs/qtpromise/helpers/attempt.md
Normal file
41
docs/qtpromise/helpers/attempt.md
Normal 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) {
|
||||
// {...}
|
||||
});
|
||||
```
|
@ -119,6 +119,13 @@ struct PromiseDeduce<QtPromise::QPromise<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>
|
||||
struct PromiseFulfill
|
||||
{
|
||||
@ -176,51 +183,17 @@ struct PromiseFulfill<QtPromise::QPromise<void>>
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename TRes>
|
||||
template <typename Result>
|
||||
struct PromiseDispatch
|
||||
{
|
||||
using Promise = typename PromiseDeduce<TRes>::Type;
|
||||
using ResType = Unqualified<TRes>;
|
||||
|
||||
template <typename THandler, typename TResolve, typename TReject>
|
||||
static void call(const T& value, THandler handler, const TResolve& resolve, const TReject& reject)
|
||||
template <typename Resolve, typename Reject, typename Functor, typename... Args>
|
||||
static void call(const Resolve& resolve, const Reject& reject, Functor fn, Args&&... args)
|
||||
{
|
||||
try {
|
||||
PromiseFulfill<ResType>::call(handler(value), resolve, reject);
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
PromiseFulfill<Unqualified<Result>>::call(
|
||||
fn(std::forward<Args>(args)...),
|
||||
resolve,
|
||||
reject);
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
}
|
||||
@ -228,15 +201,13 @@ struct PromiseDispatch<void, TRes>
|
||||
};
|
||||
|
||||
template <>
|
||||
struct PromiseDispatch<void, void>
|
||||
struct PromiseDispatch<void>
|
||||
{
|
||||
using Promise = QtPromise::QPromise<void>;
|
||||
|
||||
template <typename THandler, typename TResolve, typename TReject>
|
||||
static void call(THandler handler, const TResolve& resolve, const TReject& reject)
|
||||
template <typename Resolve, typename Reject, typename Functor, typename... Args>
|
||||
static void call(const Resolve& resolve, const Reject& reject, Functor fn, Args&&... args)
|
||||
{
|
||||
try {
|
||||
handler();
|
||||
fn(std::forward<Args>(args)...);
|
||||
resolve();
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
@ -248,7 +219,7 @@ template <typename T, typename THandler, typename TArg = typename ArgsOf<THandle
|
||||
struct PromiseHandler
|
||||
{
|
||||
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>
|
||||
static std::function<void(const T&)> create(
|
||||
@ -257,7 +228,7 @@ struct PromiseHandler
|
||||
const TReject& reject)
|
||||
{
|
||||
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>
|
||||
{
|
||||
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>
|
||||
static std::function<void(const T&)> create(
|
||||
@ -275,7 +246,7 @@ struct PromiseHandler<T, THandler, void>
|
||||
const TReject& reject)
|
||||
{
|
||||
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>
|
||||
{
|
||||
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>
|
||||
static std::function<void()> create(
|
||||
@ -293,7 +264,7 @@ struct PromiseHandler<void, THandler, void>
|
||||
const TReject& reject)
|
||||
{
|
||||
return [=]() {
|
||||
PromiseDispatch<void, ResType>::call(handler, resolve, reject);
|
||||
PromiseDispatch<ResType>::call(resolve, reject, handler);
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -351,7 +322,7 @@ struct PromiseCatcher
|
||||
try {
|
||||
error.rethrow();
|
||||
} catch (const TArg& error) {
|
||||
PromiseDispatch<TArg, ResType>::call(error, handler, resolve, reject);
|
||||
PromiseDispatch<ResType>::call(resolve, reject, handler, error);
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
}
|
||||
@ -374,7 +345,7 @@ struct PromiseCatcher<T, THandler, void>
|
||||
try {
|
||||
error.rethrow();
|
||||
} catch (...) {
|
||||
PromiseDispatch<void, ResType>::call(handler, resolve, reject);
|
||||
PromiseDispatch<ResType>::call(resolve, reject, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -38,6 +38,32 @@ static inline QPromise<void> qPromiseAll(const Sequence<QPromise<void>, Args...>
|
||||
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>
|
||||
static inline typename QtPromisePrivate::PromiseMapper<Sequence, Functor>::PromiseType
|
||||
map(const Sequence& values, Functor fn)
|
||||
|
5
tests/auto/qtpromise/helpers/attempt/attempt.pro
Normal file
5
tests/auto/qtpromise/helpers/attempt/attempt.pro
Normal file
@ -0,0 +1,5 @@
|
||||
QT += concurrent
|
||||
TARGET = tst_helpers_attempt
|
||||
SOURCES += $$PWD/tst_attempt.cpp
|
||||
|
||||
include(../../qtpromise.pri)
|
99
tests/auto/qtpromise/helpers/attempt/tst_attempt.cpp
Normal file
99
tests/auto/qtpromise/helpers/attempt/tst_attempt.cpp
Normal 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"));
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS += \
|
||||
all \
|
||||
attempt \
|
||||
filter \
|
||||
map \
|
||||
reject \
|
||||
|
Loading…
Reference in New Issue
Block a user