8 Commits

Author SHA1 Message Date
0682269d8f Bump version to 0.2.0 2017-09-02 12:36:44 +02:00
18324d3f44 Implement QPromise::timeout(msec, error) 2017-09-02 12:23:42 +02:00
b47ca0569e Implement QPromise::delay(msec)
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.
2017-08-24 18:28:44 +02:00
c55fa03e7b Implement QPromise::tap(handler)
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`, this handler is not called for rejections.
2017-08-23 21:06:59 +02:00
25d2bad54f Enhance QPromise::finally implementation
Make sure that the chained value is not copied when `finally` is called for a fulfilled input promise. The value was copied 7 times in the previous version because it was captured in a lambda, which one copied multiple times.
2017-08-23 10:59:44 +02:00
49a1d6a57b Avoid value copy when fulfilled from promise 2017-08-22 21:52:28 +02:00
c4aab4ef36 Fix circular reference memory leaks
When dispatching the promise result, we need to clear both handlers and catchers to prevent retaining circular references when the promise is captured into the handler and/or catcher. Also refactor part of the `notify` logic.
2017-08-22 12:38:41 +02:00
5d6bcc40ec Fix helpers multiple defined symbols 2017-08-12 15:57:05 +02:00
9 changed files with 570 additions and 153 deletions

View File

@ -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:

View File

@ -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",

View File

@ -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();

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}