From c55fa03e7bb08eef3aab799a1592941b5e5cf624 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Wed, 23 Aug 2017 18:12:57 +0200 Subject: [PATCH] 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. --- README.md | 13 +++ src/qtpromise/qpromise.h | 3 + src/qtpromise/qpromise.inl | 10 ++ tests/auto/benchmark/tst_benchmark.cpp | 31 ++++++ tests/auto/qpromise/tst_qpromise.cpp | 126 +++++++++++++++++++++++++ 5 files changed, 183 insertions(+) diff --git a/README.md b/README.md index 9d08b3a..174fcbf 100644 --- a/README.md +++ b/README.md @@ -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. +### `QPromise::tap(handler) -> QPromise` +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. + ### `QPromise::wait() -> QPromise` This method holds the execution of the remaining code **without** blocking the event loop of the current thread: diff --git a/src/qtpromise/qpromise.h b/src/qtpromise/qpromise.h index ab6e28b..dd9ca39 100644 --- a/src/qtpromise/qpromise.h +++ b/src/qtpromise/qpromise.h @@ -43,6 +43,9 @@ public: template inline QPromise finally(THandler handler) const; + template + inline QPromise tap(THandler handler) const; + inline QPromise wait() const; void swap(QPromiseBase& other) { qSwap(m_d, other.m_d); } diff --git a/src/qtpromise/qpromise.inl b/src/qtpromise/qpromise.inl index 2f99be0..ee28700 100644 --- a/src/qtpromise/qpromise.inl +++ b/src/qtpromise/qpromise.inl @@ -139,6 +139,16 @@ inline QPromise QPromiseBase::finally(THandler handler) const }); } +template +template +inline QPromise QPromiseBase::tap(THandler handler) const +{ + QPromise p = *this; + return p.then(handler).then([=]() { + return p; + }); +} + template inline QPromise QPromiseBase::wait() const { diff --git a/tests/auto/benchmark/tst_benchmark.cpp b/tests/auto/benchmark/tst_benchmark.cpp index 885d1f9..0653a23 100644 --- a/tests/auto/benchmark/tst_benchmark.cpp +++ b/tests/auto/benchmark/tst_benchmark.cpp @@ -15,6 +15,7 @@ private Q_SLOTS: void valueReject(); void valueThen(); void valueFinally(); + void valueTap(); void valueDelayed(); void errorReject(); 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::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::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 diff --git a/tests/auto/qpromise/tst_qpromise.cpp b/tests/auto/qpromise/tst_qpromise.cpp index 14874d9..2b41528 100644 --- a/tests/auto/qpromise/tst_qpromise.cpp +++ b/tests/auto/qpromise/tst_qpromise.cpp @@ -39,6 +39,15 @@ private Q_SLOTS: void finallyDelayedResolved(); void finallyDelayedRejected(); + void tapFulfilled(); + void tapFulfilled_void(); + void tapRejected(); + void tapRejected_void(); + void tapThrows(); + void tapThrows_void(); + void tapDelayedResolved(); + void tapDelayedRejected(); + }; // class tst_qpromise QTEST_MAIN(tst_qpromise) @@ -528,3 +537,120 @@ void tst_qpromise::finallyDelayedRejected() QCOMPARE(p.isRejected(), true); } } + +void tst_qpromise::tapFulfilled() +{ + int value = -1; + auto p = QPromise::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::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::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::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::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::resolve().tap([&]() { + throw QString("foo"); + }); + + QCOMPARE(waitForError(p, QString()), QString("foo")); + QCOMPARE(p.isRejected(), true); +} + +void tst_qpromise::tapDelayedResolved() +{ + QVector values; + auto p = QPromise::resolve(1).tap([&](int) { + QPromise p([&](const QPromiseResolve& 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({2, 3, 1})); +} + +void tst_qpromise::tapDelayedRejected() +{ + QVector values; + auto p = QPromise::resolve(1).tap([&](int) { + QPromise p([&](const QPromiseResolve&, const QPromiseReject& 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({2, 3})); +}