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.
This commit is contained in:
Simon Brunel 2017-08-23 18:12:57 +02:00
parent 25d2bad54f
commit c55fa03e7b
5 changed files with 183 additions and 0 deletions

View File

@ -300,6 +300,19 @@ 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. 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
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-wait"></a> `QPromise<T>::wait() -> QPromise<T>` ### <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: This method holds the execution of the remaining code **without** blocking the event loop of the current thread:

View File

@ -43,6 +43,9 @@ public:
template <typename THandler> template <typename THandler>
inline QPromise<T> finally(THandler handler) const; inline QPromise<T> finally(THandler handler) const;
template <typename THandler>
inline QPromise<T> tap(THandler handler) const;
inline QPromise<T> wait() const; inline QPromise<T> wait() const;
void swap(QPromiseBase<T>& other) { qSwap(m_d, other.m_d); } void swap(QPromiseBase<T>& other) { qSwap(m_d, other.m_d); }

View File

@ -139,6 +139,16 @@ inline QPromise<T> QPromiseBase<T>::finally(THandler handler) const
}); });
} }
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 T>
inline QPromise<T> QPromiseBase<T>::wait() const inline QPromise<T> QPromiseBase<T>::wait() const
{ {

View File

@ -15,6 +15,7 @@ private Q_SLOTS:
void valueReject(); void valueReject();
void valueThen(); void valueThen();
void valueFinally(); void valueFinally();
void valueTap();
void valueDelayed(); void valueDelayed();
void errorReject(); void errorReject();
void errorThen(); void errorThen();
@ -224,6 +225,36 @@ void tst_benchmark::valueFinally()
} }
} }
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() void tst_benchmark::errorReject()
{ {
{ // should create one copy of the error when rejected by rvalue { // should create one copy of the error when rejected by rvalue

View File

@ -39,6 +39,15 @@ private Q_SLOTS:
void finallyDelayedResolved(); void finallyDelayedResolved();
void finallyDelayedRejected(); void finallyDelayedRejected();
void tapFulfilled();
void tapFulfilled_void();
void tapRejected();
void tapRejected_void();
void tapThrows();
void tapThrows_void();
void tapDelayedResolved();
void tapDelayedRejected();
}; // class tst_qpromise }; // class tst_qpromise
QTEST_MAIN(tst_qpromise) QTEST_MAIN(tst_qpromise)
@ -528,3 +537,120 @@ void tst_qpromise::finallyDelayedRejected()
QCOMPARE(p.isRejected(), true); 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}));
}