From 50bae380beaea07db8a87900cb3830f93c91930e Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Tue, 13 Mar 2018 22:52:08 +0100 Subject: [PATCH] Implement QPromise::tapFail(handler) --- docs/SUMMARY.md | 1 + docs/qtpromise/api-reference.md | 1 + docs/qtpromise/qpromise/tapfail.md | 21 +++ src/qtpromise/qpromise.h | 3 + src/qtpromise/qpromise.inl | 10 ++ tests/auto/qtpromise/qpromise/qpromise.pro | 1 + .../qtpromise/qpromise/tapfail/tapfail.pro | 4 + .../qpromise/tapfail/tst_tapfail.cpp | 159 ++++++++++++++++++ 8 files changed, 200 insertions(+) create mode 100644 docs/qtpromise/qpromise/tapfail.md create mode 100644 tests/auto/qtpromise/qpromise/tapfail/tapfail.pro create mode 100644 tests/auto/qtpromise/qpromise/tapfail/tst_tapfail.cpp diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index bb9c03c..8e6072e 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -11,6 +11,7 @@ * [.isPending](qtpromise/qpromise/ispending.md) * [.isRejected](qtpromise/qpromise/isrejected.md) * [.tap](qtpromise/qpromise/tap.md) + * [.tapFail](qtpromise/qpromise/tapfail.md) * [.then](qtpromise/qpromise/then.md) * [.timeout](qtpromise/qpromise/timeout.md) * [.wait](qtpromise/qpromise/wait.md) diff --git a/docs/qtpromise/api-reference.md b/docs/qtpromise/api-reference.md index 02bdb82..c464f04 100644 --- a/docs/qtpromise/api-reference.md +++ b/docs/qtpromise/api-reference.md @@ -10,6 +10,7 @@ * [`QPromise::isPending`](qpromise/ispending.md) * [`QPromise::isRejected`](qpromise/isrejected.md) * [`QPromise::tap`](qpromise/tap.md) +* [`QPromise::tapFail`](qpromise/tapfail.md) * [`QPromise::then`](qpromise/then.md) * [`QPromise::timeout`](qpromise/timeout.md) * [`QPromise::wait`](qpromise/wait.md) diff --git a/docs/qtpromise/qpromise/tapfail.md b/docs/qtpromise/qpromise/tapfail.md new file mode 100644 index 0000000..e9b372e --- /dev/null +++ b/docs/qtpromise/qpromise/tapfail.md @@ -0,0 +1,21 @@ +## `QPromise::tapFail` + +``` +QPromise::tapFail(Function handler) -> QPromise +``` + +This `handler` allows to observe errors of the `input` promise without handling them - similar to [`finally`](finally.md) but **only** called on rejections. The `output` promise has the same type as the `input` one but also the same value or error. However, if `handler` throws, `output` is rejected with the new exception. + +```cpp +QPromise input = {...} +auto output = input.tapFail([](Error err) { + log(err); +}).then([](int res) { + return process(res); +}).fail([](Error err) { + handle(err); + return -1; +}); +``` + +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. diff --git a/src/qtpromise/qpromise.h b/src/qtpromise/qpromise.h index 3d3b568..360b219 100644 --- a/src/qtpromise/qpromise.h +++ b/src/qtpromise/qpromise.h @@ -59,6 +59,9 @@ public: template inline QPromise tap(THandler handler) const; + template + inline QPromise tapFail(THandler handler) const; + template inline QPromise timeout(int msec, E&& error = E()) const; diff --git a/src/qtpromise/qpromise.inl b/src/qtpromise/qpromise.inl index 03f6dff..9469751 100644 --- a/src/qtpromise/qpromise.inl +++ b/src/qtpromise/qpromise.inl @@ -158,6 +158,16 @@ inline QPromise QPromiseBase::tap(THandler handler) const }); } +template +template +inline QPromise QPromiseBase::tapFail(THandler handler) const +{ + QPromise p = *this; + return p.then([](){}, handler).then([=]() { + return p; + }); +} + template template inline QPromise QPromiseBase::timeout(int msec, E&& error) const diff --git a/tests/auto/qtpromise/qpromise/qpromise.pro b/tests/auto/qtpromise/qpromise/qpromise.pro index eb3544e..e0c90bd 100644 --- a/tests/auto/qtpromise/qpromise/qpromise.pro +++ b/tests/auto/qtpromise/qpromise/qpromise.pro @@ -8,5 +8,6 @@ SUBDIRS += \ operators \ resolve \ tap \ + tapfail \ then \ timeout diff --git a/tests/auto/qtpromise/qpromise/tapfail/tapfail.pro b/tests/auto/qtpromise/qpromise/tapfail/tapfail.pro new file mode 100644 index 0000000..e5e31d7 --- /dev/null +++ b/tests/auto/qtpromise/qpromise/tapfail/tapfail.pro @@ -0,0 +1,4 @@ +TARGET = tst_qpromise_tapfail +SOURCES += $$PWD/tst_tapfail.cpp + +include(../../qtpromise.pri) diff --git a/tests/auto/qtpromise/qpromise/tapfail/tst_tapfail.cpp b/tests/auto/qtpromise/qpromise/tapfail/tst_tapfail.cpp new file mode 100644 index 0000000..43c812a --- /dev/null +++ b/tests/auto/qtpromise/qpromise/tapfail/tst_tapfail.cpp @@ -0,0 +1,159 @@ +// Tests +#include "../../shared/utils.h" + +// QtPromise +#include + +// Qt +#include + +using namespace QtPromise; + +class tst_qpromise_tapfail : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void fulfilled(); + void fulfilled_void(); + void rejected(); + void rejected_void(); + void throws(); + void throws_void(); + void delayedResolved(); + void delayedRejected(); +}; + +QTEST_MAIN(tst_qpromise_tapfail) +#include "tst_tapfail.moc" + +void tst_qpromise_tapfail::fulfilled() +{ + int value = -1; + auto p = QPromise::resolve(42).tapFail([&]() { + value = 43; + }); + + QCOMPARE(waitForValue(p, 42), 42); + QCOMPARE(p.isFulfilled(), true); + QCOMPARE(value, -1); +} + +void tst_qpromise_tapfail::fulfilled_void() +{ + int value = -1; + auto p = QPromise::resolve().tapFail([&]() { + value = 43; + }); + + QCOMPARE(waitForValue(p, -1, 42), 42); + QCOMPARE(p.isFulfilled(), true); + QCOMPARE(value, -1); +} + +void tst_qpromise_tapfail::rejected() +{ + QStringList errors; + + auto p0 = QPromise::reject(QString("foo")) + .tapFail([&](const QString& err) { + errors << "1:" + err; + }); + + auto p1 = p0 + .fail([&](const QString& err) { + errors << "2:" + err; + return 43; + }); + + QCOMPARE(waitForError(p0, QString()), QString("foo")); + QCOMPARE(waitForValue(p1, -1), 43); + QCOMPARE(p0.isRejected(), true); + QCOMPARE(p1.isFulfilled(), true); + QCOMPARE(errors, QStringList() << "1:foo" << "2:foo"); +} + +void tst_qpromise_tapfail::rejected_void() +{ + QStringList errors; + + auto p0 = QPromise::reject(QString("foo")) + .tapFail([&](const QString& err) { + errors << "1:" + err; + }); + + auto p1 = p0 + .fail([&](const QString& err) { + errors << "2:" + err; + }); + + QCOMPARE(waitForError(p0, QString()), QString("foo")); + QCOMPARE(waitForValue(p1, -1, 43), 43); + QCOMPARE(p0.isRejected(), true); + QCOMPARE(p1.isFulfilled(), true); + QCOMPARE(errors, QStringList() << "1:foo" << "2:foo"); +} + +void tst_qpromise_tapfail::throws() +{ + auto p = QPromise::reject(QString("foo")) + .tapFail([&]() { + throw QString("bar"); + }); + + QCOMPARE(waitForError(p, QString()), QString("bar")); + QCOMPARE(p.isRejected(), true); +} + +void tst_qpromise_tapfail::throws_void() +{ + auto p = QPromise::reject(QString("foo")) + .tapFail([&]() { + throw QString("bar"); + }); + + QCOMPARE(waitForError(p, QString()), QString("bar")); + QCOMPARE(p.isRejected(), true); +} + +void tst_qpromise_tapfail::delayedResolved() +{ + QVector values; + auto p = QPromise::reject(QString("foo")) + .tapFail([&]() { + QPromise p([&](const QPromiseResolve& resolve) { + QtPromisePrivate::qtpromise_defer([=, &values]() { + values << 3; + resolve(); // ignored! + }); + }); + + values << 2; + return p; + }); + + QCOMPARE(waitForError(p, QString()), QString("foo")); + QCOMPARE(values, QVector({2, 3})); +} + +void tst_qpromise_tapfail::delayedRejected() +{ + QVector values; + auto p = QPromise::reject(QString("foo")) + .tapFail([&]() { + QPromise p([&]( + const QPromiseResolve&, + const QPromiseReject& reject){ + QtPromisePrivate::qtpromise_defer([=, &values]() { + values << 3; + reject(QString("bar")); + }); + }); + + values << 2; + return p; + }); + + QCOMPARE(waitForError(p, QString()), QString("bar")); + QCOMPARE(values, QVector({2, 3})); +}