mirror of
https://github.com/simonbrunel/qtpromise.git
synced 2025-07-01 06:41:55 +08:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
0682269d8f | |||
18324d3f44 | |||
b47ca0569e | |||
c55fa03e7b | |||
25d2bad54f | |||
49a1d6a57b | |||
c4aab4ef36 | |||
5d6bcc40ec |
24
README.md
24
README.md
@ -300,6 +300,30 @@ auto output = input.finally([]() {
|
||||
|
||||
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise.
|
||||
|
||||
### <a name="qpromise-tap"></a> `QPromise<T>::tap(handler) -> QPromise<T>`
|
||||
This `handler` allows to observe the value of the `input` promise, without changing the propagated value. The `output` promise will be resolved with the same value as the `input` promise (the `handler` returned value will be ignored). However, if `handler` throws, `output` is rejected with the new exception. Unlike [`finally`](#qpromise-finally), this handler is **not** called for rejections.
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.tap([](int res) {
|
||||
log(res);
|
||||
}).then([](int res) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise.
|
||||
|
||||
### <a name="qpromise-delay"></a> `QPromise<T>::delay(handler) -> QPromise<T>`
|
||||
This method returns a promise that will be fulfilled with the same value as the `input` promise and after at least `msec` milliseconds. If the `input` promise is rejected, the `output` promise is immediately rejected with the same reason.
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.delay(2000).then([](int res) {
|
||||
// called 2 seconds after `input` is fulfilled
|
||||
});
|
||||
```
|
||||
|
||||
### <a name="qpromise-wait"></a> `QPromise<T>::wait() -> QPromise<T>`
|
||||
This method holds the execution of the remaining code **without** blocking the event loop of the current thread:
|
||||
|
||||
|
2
qpm.json
2
qpm.json
@ -10,7 +10,7 @@
|
||||
"url": "https://github.com/simonbrunel/qtpromise.git"
|
||||
},
|
||||
"version": {
|
||||
"label": "0.1.0"
|
||||
"label": "0.2.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"pri_filename": "qtpromise.pri",
|
||||
|
@ -40,6 +40,16 @@ public:
|
||||
inline typename QtPromisePrivate::PromiseHandler<T, std::nullptr_t>::Promise
|
||||
fail(TRejected&& rejected) const;
|
||||
|
||||
template <typename THandler>
|
||||
inline QPromise<T> finally(THandler handler) const;
|
||||
|
||||
template <typename THandler>
|
||||
inline QPromise<T> tap(THandler handler) const;
|
||||
|
||||
template <typename E = QPromiseTimeoutException>
|
||||
inline QPromise<T> timeout(int msec, E&& error = E()) const;
|
||||
|
||||
inline QPromise<T> delay(int msec) const;
|
||||
inline QPromise<T> wait() const;
|
||||
|
||||
void swap(QPromiseBase<T>& other) { qSwap(m_d, other.m_d); }
|
||||
@ -49,6 +59,7 @@ public: // STATIC
|
||||
inline static QPromise<T> reject(E&& error);
|
||||
|
||||
protected:
|
||||
friend struct QtPromisePrivate::PromiseFulfill<QPromise<T> >;
|
||||
friend class QPromiseResolve<T>;
|
||||
friend class QPromiseReject<T>;
|
||||
|
||||
@ -62,9 +73,6 @@ public:
|
||||
template <typename F>
|
||||
QPromise(F&& resolver): QPromiseBase<T>(std::forward<F>(resolver)) { }
|
||||
|
||||
template <typename THandler>
|
||||
inline QPromise<T> finally(THandler handler) const;
|
||||
|
||||
public: // STATIC
|
||||
inline static QPromise<QVector<T> > all(const QVector<QPromise<T> >& promises);
|
||||
inline static QPromise<T> resolve(T&& value);
|
||||
@ -80,9 +88,6 @@ public:
|
||||
template <typename F>
|
||||
QPromise(F&& resolver): QPromiseBase<void>(std::forward<F>(resolver)) { }
|
||||
|
||||
template <typename THandler>
|
||||
inline QPromise<void> finally(THandler handler) const;
|
||||
|
||||
public: // STATIC
|
||||
inline static QPromise<void> all(const QVector<QPromise<void> >& promises);
|
||||
inline static QPromise<void> resolve();
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Qt
|
||||
#include <QCoreApplication>
|
||||
#include <QSharedPointer>
|
||||
#include <QTimer>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
@ -12,28 +13,18 @@ public:
|
||||
: m_promise(new QPromise<T>(std::move(p)))
|
||||
{ }
|
||||
|
||||
void operator()(const T& value) const
|
||||
template <typename V>
|
||||
void operator()(V&& value) const
|
||||
{
|
||||
resolve(value);
|
||||
}
|
||||
|
||||
void operator()(T&& value) const
|
||||
{
|
||||
resolve(std::move(value));
|
||||
Q_ASSERT(!m_promise.isNull());
|
||||
if (m_promise->isPending()) {
|
||||
m_promise->m_d->resolve(std::forward<V>(value));
|
||||
m_promise->m_d->dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QSharedPointer<QPromise<T> > m_promise;
|
||||
|
||||
template <typename U>
|
||||
void resolve(U&& value) const
|
||||
{
|
||||
Q_ASSERT(!m_promise.isNull());
|
||||
if (m_promise->isPending()) {
|
||||
m_promise->m_d->resolve(std::forward<U>(value));
|
||||
m_promise->m_d->dispatch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
@ -139,6 +130,56 @@ QPromiseBase<T>::fail(TRejected&& rejected) const
|
||||
return then(nullptr, std::forward<TRejected>(rejected));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename THandler>
|
||||
inline QPromise<T> QPromiseBase<T>::finally(THandler handler) const
|
||||
{
|
||||
QPromise<T> p = *this;
|
||||
return p.then(handler, handler).then([=]() {
|
||||
return p;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename THandler>
|
||||
inline QPromise<T> QPromiseBase<T>::tap(THandler handler) const
|
||||
{
|
||||
QPromise<T> p = *this;
|
||||
return p.then(handler).then([=]() {
|
||||
return p;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename E>
|
||||
inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const
|
||||
{
|
||||
QPromise<T> p = *this;
|
||||
return QPromise<T>([&](
|
||||
const QPromiseResolve<T>& resolve,
|
||||
const QPromiseReject<T>& reject) {
|
||||
|
||||
QTimer::singleShot(msec, [=]() {
|
||||
// we don't need to verify the current promise state, reject()
|
||||
// takes care of checking if the promise is already resolved,
|
||||
// and thus will ignore this rejection.
|
||||
reject(std::move(error));
|
||||
});
|
||||
|
||||
QtPromisePrivate::PromiseFulfill<QPromise<T> >::call(p, resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline QPromise<T> QPromiseBase<T>::delay(int msec) const
|
||||
{
|
||||
return tap([=]() {
|
||||
return QPromise<void>([&](const QPromiseResolve<void>& resolve) {
|
||||
QTimer::singleShot(msec, resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline QPromise<T> QPromiseBase<T>::wait() const
|
||||
{
|
||||
@ -160,23 +201,6 @@ inline QPromise<T> QPromiseBase<T>::reject(E&& error)
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename THandler>
|
||||
inline QPromise<T> QPromise<T>::finally(THandler handler) const
|
||||
{
|
||||
return this->then([=](const T& res) {
|
||||
return QPromise<void>::resolve().then(handler).then([=](){
|
||||
return res;
|
||||
});
|
||||
}, [=]() {
|
||||
const auto exception = std::current_exception();
|
||||
return QPromise<void>::resolve().then(handler).then([=](){
|
||||
std::rethrow_exception(exception);
|
||||
return T();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises)
|
||||
{
|
||||
@ -216,19 +240,6 @@ inline QPromise<T> QPromise<T>::resolve(T&& value)
|
||||
});
|
||||
}
|
||||
|
||||
template <typename THandler>
|
||||
inline QPromise<void> QPromise<void>::finally(THandler handler) const
|
||||
{
|
||||
return this->then([=]() {
|
||||
return QPromise<void>::resolve().then(handler).then([](){});
|
||||
}, [=]() {
|
||||
const auto exception = std::current_exception();
|
||||
return QPromise<void>::resolve().then(handler).then([=](){
|
||||
std::rethrow_exception(exception);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promises)
|
||||
{
|
||||
const int count = promises.size();
|
||||
|
@ -79,13 +79,17 @@ struct PromiseFulfill<QtPromise::QPromise<T> >
|
||||
const QtPromise::QPromiseResolve<T>& resolve,
|
||||
const QtPromise::QPromiseReject<T>& reject)
|
||||
{
|
||||
promise.then(
|
||||
[=](const T& value) {
|
||||
resolve(value);
|
||||
},
|
||||
[=]() { // catch all
|
||||
reject(std::current_exception());
|
||||
if (promise.isFulfilled()) {
|
||||
resolve(promise.m_d->value());
|
||||
} else if (promise.isRejected()) {
|
||||
reject(promise.m_d->error());
|
||||
} else {
|
||||
promise.then([=]() {
|
||||
resolve(promise.m_d->value());
|
||||
}, [=]() { // catch all
|
||||
reject(promise.m_d->error());
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -98,13 +102,17 @@ struct PromiseFulfill<QtPromise::QPromise<void> >
|
||||
const TResolve& resolve,
|
||||
const TReject& reject)
|
||||
{
|
||||
promise.then(
|
||||
[=]() {
|
||||
if (promise.isFulfilled()) {
|
||||
resolve();
|
||||
} else if (promise.isRejected()) {
|
||||
reject(promise.m_d->error());
|
||||
} else {
|
||||
promise.then([=]() {
|
||||
resolve();
|
||||
},
|
||||
[=]() { // catch all
|
||||
reject(std::current_exception());
|
||||
}, [=]() { // catch all
|
||||
reject(promise.m_d->error());
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -331,11 +339,12 @@ struct PromiseCatcher<T, std::nullptr_t, void>
|
||||
|
||||
template <typename T> class PromiseData;
|
||||
|
||||
template <typename T>
|
||||
template <typename T, typename F>
|
||||
class PromiseDataBase : public QSharedData
|
||||
{
|
||||
public:
|
||||
using Error = QtPromise::QPromiseError;
|
||||
using Handler = std::pair<QPointer<QThread>, std::function<F> >;
|
||||
using Catcher = std::pair<QPointer<QThread>, std::function<void(const Error&)> >;
|
||||
|
||||
virtual ~PromiseDataBase() {}
|
||||
@ -356,6 +365,12 @@ public:
|
||||
return !m_settled;
|
||||
}
|
||||
|
||||
void addHandler(std::function<F> handler)
|
||||
{
|
||||
QWriteLocker lock(&m_lock);
|
||||
m_handlers.append({QThread::currentThread(), std::move(handler)});
|
||||
}
|
||||
|
||||
void addCatcher(std::function<void(const Error&)> catcher)
|
||||
{
|
||||
QWriteLocker lock(&m_lock);
|
||||
@ -370,21 +385,43 @@ public:
|
||||
setSettled();
|
||||
}
|
||||
|
||||
void reject(const QSharedPointer<Error>& error)
|
||||
{
|
||||
Q_ASSERT(isPending());
|
||||
Q_ASSERT(m_error.isNull());
|
||||
m_error = error;
|
||||
this->setSettled();
|
||||
}
|
||||
|
||||
const QSharedPointer<Error>& error() const
|
||||
{
|
||||
Q_ASSERT(isRejected());
|
||||
return m_error;
|
||||
}
|
||||
|
||||
void dispatch()
|
||||
{
|
||||
if (isPending()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_error.isNull()) {
|
||||
notify();
|
||||
return;
|
||||
}
|
||||
// A promise can't be resolved multiple times so once settled, its state can't
|
||||
// change. When fulfilled, handlers must be called (a single time) and catchers
|
||||
// ignored indefinitely (or vice-versa when the promise is rejected), so make
|
||||
// sure to clear both handlers AND catchers when dispatching. This also prevents
|
||||
// shared pointer circular reference memory leaks when the owning promise is
|
||||
// captured in the handler and/or catcher lambdas.
|
||||
|
||||
m_lock.lockForWrite();
|
||||
QVector<Handler> handlers(std::move(m_handlers));
|
||||
QVector<Catcher> catchers(std::move(m_catchers));
|
||||
m_lock.unlock();
|
||||
|
||||
if (m_error.isNull()) {
|
||||
notify(handlers);
|
||||
return;
|
||||
}
|
||||
|
||||
QSharedPointer<Error> error = m_error;
|
||||
Q_ASSERT(!error.isNull());
|
||||
|
||||
@ -406,28 +443,24 @@ protected:
|
||||
m_settled = true;
|
||||
}
|
||||
|
||||
virtual void notify() = 0;
|
||||
virtual void notify(const QVector<Handler>&) = 0;
|
||||
|
||||
private:
|
||||
bool m_settled = false;
|
||||
QVector<Handler> m_handlers;
|
||||
QVector<Catcher> m_catchers;
|
||||
QSharedPointer<Error> m_error;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class PromiseData : public PromiseDataBase<T>
|
||||
class PromiseData : public PromiseDataBase<T, void(const T&)>
|
||||
{
|
||||
using Handler = std::pair<QPointer<QThread>, std::function<void(const T&)> >;
|
||||
using Handler = typename PromiseDataBase<T, void(const T&)>::Handler;
|
||||
|
||||
public:
|
||||
void addHandler(std::function<void(const T&)> handler)
|
||||
{
|
||||
QWriteLocker lock(&this->m_lock);
|
||||
m_handlers.append({QThread::currentThread(), std::move(handler)});
|
||||
}
|
||||
|
||||
void resolve(T&& value)
|
||||
{
|
||||
Q_ASSERT(this->isPending());
|
||||
Q_ASSERT(m_value.isNull());
|
||||
m_value.reset(new T(std::move(value)));
|
||||
this->setSettled();
|
||||
@ -435,17 +468,28 @@ public:
|
||||
|
||||
void resolve(const T& value)
|
||||
{
|
||||
Q_ASSERT(this->isPending());
|
||||
Q_ASSERT(m_value.isNull());
|
||||
m_value.reset(new T(value));
|
||||
this->setSettled();
|
||||
}
|
||||
|
||||
void notify() Q_DECL_OVERRIDE
|
||||
void resolve(const QSharedPointer<T>& value)
|
||||
{
|
||||
this->m_lock.lockForWrite();
|
||||
QVector<Handler> handlers(std::move(m_handlers));
|
||||
this->m_lock.unlock();
|
||||
Q_ASSERT(this->isPending());
|
||||
Q_ASSERT(m_value.isNull());
|
||||
m_value = value;
|
||||
this->setSettled();
|
||||
}
|
||||
|
||||
const QSharedPointer<T>& value() const
|
||||
{
|
||||
Q_ASSERT(this->isFulfilled());
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
|
||||
{
|
||||
QSharedPointer<T> value(m_value);
|
||||
Q_ASSERT(!value.isNull());
|
||||
|
||||
@ -458,38 +502,27 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<Handler> m_handlers;
|
||||
QSharedPointer<T> m_value;
|
||||
};
|
||||
|
||||
template <>
|
||||
class PromiseData<void> : public PromiseDataBase<void>
|
||||
class PromiseData<void> : public PromiseDataBase<void, void()>
|
||||
{
|
||||
using Handler = std::pair<QPointer<QThread>, std::function<void()> >;
|
||||
using Handler = typename PromiseDataBase<void, void()>::Handler;
|
||||
|
||||
public:
|
||||
void addHandler(std::function<void()> handler)
|
||||
void resolve()
|
||||
{
|
||||
QWriteLocker lock(&m_lock);
|
||||
m_handlers.append({QThread::currentThread(), std::move(handler)});
|
||||
setSettled();
|
||||
}
|
||||
|
||||
void resolve() { setSettled(); }
|
||||
|
||||
protected:
|
||||
void notify() Q_DECL_OVERRIDE
|
||||
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
|
||||
{
|
||||
this->m_lock.lockForWrite();
|
||||
QVector<Handler> handlers(std::move(m_handlers));
|
||||
this->m_lock.unlock();
|
||||
|
||||
for (const auto& handler: handlers) {
|
||||
qtpromise_defer(handler.second, handler.first);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<Handler> m_handlers;
|
||||
};
|
||||
|
||||
} // namespace QtPromise
|
||||
|
@ -4,6 +4,9 @@
|
||||
// QtPromise
|
||||
#include "qpromiseglobal.h"
|
||||
|
||||
// Qt
|
||||
#include <QException>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
class QPromiseError
|
||||
@ -53,6 +56,16 @@ private:
|
||||
std::exception_ptr m_exception;
|
||||
};
|
||||
|
||||
class QPromiseTimeoutException : public QException
|
||||
{
|
||||
public:
|
||||
void raise() const Q_DECL_OVERRIDE { throw *this; }
|
||||
QPromiseTimeoutException* clone() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return new QPromiseTimeoutException(*this);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QtPromise
|
||||
|
||||
#endif // QTPROMISE_QPROMISEERROR_H
|
||||
|
@ -7,7 +7,7 @@
|
||||
namespace QtPromise {
|
||||
|
||||
template <typename T>
|
||||
typename QtPromisePrivate::PromiseDeduce<T>::Type qPromise(T&& value)
|
||||
static inline typename QtPromisePrivate::PromiseDeduce<T>::Type qPromise(T&& value)
|
||||
{
|
||||
using namespace QtPromisePrivate;
|
||||
using Promise = typename PromiseDeduce<T>::Type;
|
||||
@ -18,7 +18,7 @@ typename QtPromisePrivate::PromiseDeduce<T>::Type qPromise(T&& value)
|
||||
});
|
||||
}
|
||||
|
||||
QPromise<void> qPromise()
|
||||
static inline QPromise<void> qPromise()
|
||||
{
|
||||
return QPromise<void>([](
|
||||
const QPromiseResolve<void>& resolve) {
|
||||
@ -27,12 +27,12 @@ QPromise<void> qPromise()
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QPromise<QVector<T> > qPromiseAll(const QVector<QPromise<T> >& promises)
|
||||
static inline QPromise<QVector<T> > qPromiseAll(const QVector<QPromise<T> >& promises)
|
||||
{
|
||||
return QPromise<T>::all(promises);
|
||||
}
|
||||
|
||||
QPromise<void> qPromiseAll(const QVector<QPromise<void> >& promises)
|
||||
static inline QPromise<void> qPromiseAll(const QVector<QPromise<void> >& promises)
|
||||
{
|
||||
return QPromise<void>::all(promises);
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ private Q_SLOTS:
|
||||
void valueResolve();
|
||||
void valueReject();
|
||||
void valueThen();
|
||||
void valueFinally();
|
||||
void valueTap();
|
||||
void valueDelayed();
|
||||
void errorReject();
|
||||
void errorThen();
|
||||
|
||||
@ -128,7 +131,7 @@ void tst_benchmark::valueThen()
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 0);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0); // move value to the promise data
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(error, QString("foo"));
|
||||
QCOMPARE(value, -1);
|
||||
@ -162,6 +165,96 @@ void tst_benchmark::valueThen()
|
||||
}
|
||||
}
|
||||
|
||||
void tst_benchmark::valueDelayed()
|
||||
{
|
||||
{ // should not copy the value on continutation if fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QPromise<int>::resolve(42).then([&](int res) {
|
||||
return QPromise<Data>::resolve(Data(res + 1));
|
||||
}).then([&](const Data& res) {
|
||||
value = res.value();
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 1); // move value to the input promise data
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 43);
|
||||
}
|
||||
{ // should not create value on continutation if rejected
|
||||
Data::logs().reset();
|
||||
QPromise<int>::resolve(42).then([&]() {
|
||||
return QPromise<Data>::reject(QString("foo"));
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 0);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_benchmark::valueFinally()
|
||||
{
|
||||
{ // should not copy the value on continutation if fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QPromise<Data>::resolve(Data(42)).finally([&]() {
|
||||
value = 42;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 1); // move value to the input and output promise data
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
{ // should not create value on continutation if rejected
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QPromise<Data>::reject(QString("foo")).finally([&]() {
|
||||
value = 42;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 0);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_benchmark::valueTap()
|
||||
{
|
||||
{ // should not copy the value on continutation if fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QPromise<Data>::resolve(Data(42)).tap([&](const Data& res) {
|
||||
value = res.value();
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 1); // move value to the input and output promise data
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
{ // should not create value on continutation if rejected
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QPromise<Data>::reject(QString("foo")).tap([&](const Data& res) {
|
||||
value = res.value();
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 0);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_benchmark::errorReject()
|
||||
{
|
||||
{ // should create one copy of the error when rejected by rvalue
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
// Qt
|
||||
#include <QtTest>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
using namespace QtPromise;
|
||||
using namespace QtPromisePrivate;
|
||||
@ -30,13 +31,31 @@ private Q_SLOTS:
|
||||
void failBaseClass();
|
||||
void failCatchAll();
|
||||
|
||||
void finallyReturns();
|
||||
void finallyReturns_void();
|
||||
void finallyFulfilled();
|
||||
void finallyFulfilled_void();
|
||||
void finallyRejected();
|
||||
void finallyRejected_void();
|
||||
void finallyThrows();
|
||||
void finallyThrows_void();
|
||||
void finallyDelayedResolved();
|
||||
void finallyDelayedRejected();
|
||||
|
||||
void tapFulfilled();
|
||||
void tapFulfilled_void();
|
||||
void tapRejected();
|
||||
void tapRejected_void();
|
||||
void tapThrows();
|
||||
void tapThrows_void();
|
||||
void tapDelayedResolved();
|
||||
void tapDelayedRejected();
|
||||
|
||||
void timeoutFulfilled();
|
||||
void timeoutRejected();
|
||||
void timeoutReject();
|
||||
|
||||
void delayFulfilled();
|
||||
void delayRejected();
|
||||
|
||||
}; // class tst_qpromise
|
||||
|
||||
QTEST_MAIN(tst_qpromise)
|
||||
@ -355,60 +374,60 @@ void tst_qpromise::failCatchAll()
|
||||
QCOMPARE(error, QString("bar"));
|
||||
}
|
||||
|
||||
void tst_qpromise::finallyReturns()
|
||||
void tst_qpromise::finallyFulfilled()
|
||||
{
|
||||
{ // fulfilled
|
||||
int value = -1;
|
||||
auto p = QPromise<int>::resolve(42).finally([&]() {
|
||||
value = 8;
|
||||
return 16; // ignored!
|
||||
});
|
||||
int value = -1;
|
||||
auto p = QPromise<int>::resolve(42).finally([&]() {
|
||||
value = 8;
|
||||
return 16; // ignored!
|
||||
});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||
QCOMPARE(waitForValue(p, -1), 42);
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(value, 8);
|
||||
}
|
||||
{ // rejected
|
||||
int value = -1;
|
||||
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
|
||||
value = 8;
|
||||
return 16; // ignored!
|
||||
});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(value, 8);
|
||||
}
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||
QCOMPARE(waitForValue(p, -1), 42);
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(value, 8);
|
||||
}
|
||||
|
||||
void tst_qpromise::finallyReturns_void()
|
||||
void tst_qpromise::finallyFulfilled_void()
|
||||
{
|
||||
{ // fulfilled
|
||||
int value = -1;
|
||||
auto p = QPromise<void>::resolve().finally([&]() {
|
||||
value = 8;
|
||||
return 16; // ignored!
|
||||
});
|
||||
int value = -1;
|
||||
auto p = QPromise<void>::resolve().finally([&]() {
|
||||
value = 8;
|
||||
return 16; // ignored!
|
||||
});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(value, 8);
|
||||
}
|
||||
{ // rejected
|
||||
int value = -1;
|
||||
auto p = QPromise<void>::reject(QString("foo")).finally([&]() {
|
||||
value = 8;
|
||||
return 16; // ignored!
|
||||
});
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(value, 8);
|
||||
}
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(value, 8);
|
||||
}
|
||||
void tst_qpromise::finallyRejected()
|
||||
{
|
||||
int value = -1;
|
||||
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
|
||||
value = 8;
|
||||
return 16; // ignored!
|
||||
});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(value, 8);
|
||||
}
|
||||
|
||||
void tst_qpromise::finallyRejected_void()
|
||||
{
|
||||
int value = -1;
|
||||
auto p = QPromise<void>::reject(QString("foo")).finally([&]() {
|
||||
value = 8;
|
||||
return 16; // ignored!
|
||||
});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(value, 8);
|
||||
}
|
||||
|
||||
void tst_qpromise::finallyThrows()
|
||||
@ -526,3 +545,222 @@ void tst_qpromise::finallyDelayedRejected()
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_qpromise::tapFulfilled()
|
||||
{
|
||||
int value = -1;
|
||||
auto p = QPromise<int>::resolve(42).tap([&](int res) {
|
||||
value = res + 1;
|
||||
return 8;
|
||||
});
|
||||
|
||||
QCOMPARE(waitForValue(p, 42), 42);
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(value, 43);
|
||||
}
|
||||
|
||||
void tst_qpromise::tapFulfilled_void()
|
||||
{
|
||||
int value = -1;
|
||||
auto p = QPromise<void>::resolve().tap([&]() {
|
||||
value = 43;
|
||||
return 8;
|
||||
});
|
||||
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(value, 43);
|
||||
}
|
||||
|
||||
void tst_qpromise::tapRejected()
|
||||
{
|
||||
int value = -1;
|
||||
auto p = QPromise<int>::reject(QString("foo")).tap([&](int res) {
|
||||
value = res + 1;
|
||||
});
|
||||
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(value, -1);
|
||||
}
|
||||
|
||||
void tst_qpromise::tapRejected_void()
|
||||
{
|
||||
int value = -1;
|
||||
auto p = QPromise<void>::reject(QString("foo")).tap([&]() {
|
||||
value = 43;
|
||||
});
|
||||
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(value, -1);
|
||||
}
|
||||
|
||||
void tst_qpromise::tapThrows()
|
||||
{
|
||||
auto p = QPromise<int>::resolve(42).tap([&](int) {
|
||||
throw QString("foo");
|
||||
});
|
||||
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
}
|
||||
|
||||
void tst_qpromise::tapThrows_void()
|
||||
{
|
||||
auto p = QPromise<void>::resolve().tap([&]() {
|
||||
throw QString("foo");
|
||||
});
|
||||
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
}
|
||||
|
||||
void tst_qpromise::tapDelayedResolved()
|
||||
{
|
||||
QVector<int> values;
|
||||
auto p = QPromise<int>::resolve(1).tap([&](int) {
|
||||
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
|
||||
qtpromise_defer([=, &values]() {
|
||||
values << 3;
|
||||
resolve(4); // ignored!
|
||||
});
|
||||
});
|
||||
|
||||
values << 2;
|
||||
return p;
|
||||
});
|
||||
|
||||
p.then([&](int r) {
|
||||
values << r;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(values, QVector<int>({2, 3, 1}));
|
||||
}
|
||||
|
||||
void tst_qpromise::tapDelayedRejected()
|
||||
{
|
||||
QVector<int> values;
|
||||
auto p = QPromise<int>::resolve(1).tap([&](int) {
|
||||
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
||||
qtpromise_defer([=, &values]() {
|
||||
values << 3;
|
||||
reject(QString("foo"));
|
||||
});
|
||||
});
|
||||
|
||||
values << 2;
|
||||
return p;
|
||||
});
|
||||
|
||||
p.then([&](int r) {
|
||||
values << r;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(values, QVector<int>({2, 3}));
|
||||
}
|
||||
|
||||
void tst_qpromise::timeoutFulfilled()
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
qint64 elapsed = -1;
|
||||
|
||||
timer.start();
|
||||
|
||||
auto p = QPromise<int>([](const QPromiseResolve<int>& resolve) {
|
||||
QTimer::singleShot(1000, [=]() {
|
||||
resolve(42);
|
||||
});
|
||||
}).timeout(2000).finally([&]() {
|
||||
elapsed = timer.elapsed();
|
||||
});
|
||||
|
||||
QCOMPARE(waitForValue(p, -1), 42);
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QVERIFY(elapsed < 2000);
|
||||
}
|
||||
|
||||
void tst_qpromise::timeoutRejected()
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
qint64 elapsed = -1;
|
||||
|
||||
timer.start();
|
||||
|
||||
auto p = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
||||
QTimer::singleShot(1000, [=]() {
|
||||
reject(QString("foo"));
|
||||
});
|
||||
}).timeout(2000).finally([&]() {
|
||||
elapsed = timer.elapsed();
|
||||
});
|
||||
|
||||
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QVERIFY(elapsed < 2000);
|
||||
}
|
||||
|
||||
void tst_qpromise::timeoutReject()
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
qint64 elapsed = -1;
|
||||
bool failed = false;
|
||||
|
||||
timer.start();
|
||||
|
||||
auto p = QPromise<int>([](const QPromiseResolve<int>& resolve) {
|
||||
QTimer::singleShot(4000, [=]() {
|
||||
resolve(42);
|
||||
});
|
||||
}).timeout(2000).finally([&]() {
|
||||
elapsed = timer.elapsed();
|
||||
});
|
||||
|
||||
p.fail([&](const QPromiseTimeoutException&) {
|
||||
failed = true;
|
||||
return -1;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(waitForValue(p, -1), -1);
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(failed, true);
|
||||
QVERIFY(elapsed >= 2000 * 0.95); // Qt::CoarseTimer (default) Coarse timers try to
|
||||
QVERIFY(elapsed <= 2000 * 1.05); // keep accuracy within 5% of the desired interval.
|
||||
}
|
||||
|
||||
void tst_qpromise::delayFulfilled()
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
qint64 elapsed = -1;
|
||||
|
||||
timer.start();
|
||||
|
||||
auto p = QPromise<int>::resolve(42).delay(1000).finally([&]() {
|
||||
elapsed = timer.elapsed();
|
||||
});
|
||||
|
||||
QCOMPARE(waitForValue(p, -1), 42);
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QVERIFY(elapsed >= 1000 * 0.95); // Qt::CoarseTimer (default) Coarse timers try to
|
||||
QVERIFY(elapsed <= 1000 * 1.05); // keep accuracy within 5% of the desired interval.
|
||||
}
|
||||
|
||||
void tst_qpromise::delayRejected()
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
qint64 elapsed = -1;
|
||||
|
||||
timer.start();
|
||||
|
||||
auto p = QPromise<int>::reject(QString("foo")).delay(1000).finally([&]() {
|
||||
elapsed = timer.elapsed();
|
||||
});
|
||||
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QVERIFY(elapsed < 5);
|
||||
}
|
||||
|
Reference in New Issue
Block a user