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)
|
* [::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)
|
||||||
|
@ -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)
|
||||||
|
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>
|
: 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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
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
|
TEMPLATE = subdirs
|
||||||
SUBDIRS += \
|
SUBDIRS += \
|
||||||
all \
|
all \
|
||||||
|
attempt \
|
||||||
filter \
|
filter \
|
||||||
map \
|
map \
|
||||||
reject \
|
reject \
|
||||||
|
Loading…
Reference in New Issue
Block a user