Implement QPromise<Sequence<T>>::reduce(reducer, initialValue)

Iterates over all the promise values (i.e. `Sequence<T>`) and reduces the sequence to a single value using the given `reducer` function and an optional `initialValue`. Also provide a static helper to directly reduce values (`QtPromise::reduce(values, reducer, initialValue)`).
This commit is contained in:
Simon Brunel 2019-02-25 10:07:06 +01:00
parent cbf4cc7867
commit e3f0f054af
15 changed files with 763 additions and 6 deletions

View File

@ -33,6 +33,7 @@ module.exports = {
'qtpromise/qpromise/ispending', 'qtpromise/qpromise/ispending',
'qtpromise/qpromise/isrejected', 'qtpromise/qpromise/isrejected',
'qtpromise/qpromise/map', 'qtpromise/qpromise/map',
'qtpromise/qpromise/reduce',
'qtpromise/qpromise/tap', 'qtpromise/qpromise/tap',
'qtpromise/qpromise/tapfail', 'qtpromise/qpromise/tapfail',
'qtpromise/qpromise/then', 'qtpromise/qpromise/then',
@ -51,6 +52,7 @@ module.exports = {
'qtpromise/helpers/each', 'qtpromise/helpers/each',
'qtpromise/helpers/filter', 'qtpromise/helpers/filter',
'qtpromise/helpers/map', 'qtpromise/helpers/map',
'qtpromise/helpers/reduce',
'qtpromise/helpers/resolve' 'qtpromise/helpers/resolve'
] ]
}, },

View File

@ -12,6 +12,7 @@
* [`QPromise<T>::isPending`](qpromise/ispending.md) * [`QPromise<T>::isPending`](qpromise/ispending.md)
* [`QPromise<T>::isRejected`](qpromise/isrejected.md) * [`QPromise<T>::isRejected`](qpromise/isrejected.md)
* [`QPromise<T>::map`](qpromise/map.md) * [`QPromise<T>::map`](qpromise/map.md)
* [`QPromise<T>::reduce`](qpromise/reduce.md)
* [`QPromise<T>::tap`](qpromise/tap.md) * [`QPromise<T>::tap`](qpromise/tap.md)
* [`QPromise<T>::tapFail`](qpromise/tapfail.md) * [`QPromise<T>::tapFail`](qpromise/tapfail.md)
* [`QPromise<T>::then`](qpromise/then.md) * [`QPromise<T>::then`](qpromise/then.md)
@ -31,6 +32,7 @@
* [`QtPromise::each`](helpers/each.md) * [`QtPromise::each`](helpers/each.md)
* [`QtPromise::filter`](helpers/filter.md) * [`QtPromise::filter`](helpers/filter.md)
* [`QtPromise::map`](helpers/map.md) * [`QtPromise::map`](helpers/map.md)
* [`QtPromise::reduce`](helpers/reduce.md)
* [`QtPromise::resolve`](helpers/resolve.md) * [`QtPromise::resolve`](helpers/resolve.md)
## Exceptions ## Exceptions

View File

@ -0,0 +1,48 @@
---
title: reduce
---
# QtPromise::reduce
*Since: 0.5.0*
```cpp
QPromise::reduce(Sequence<T|QPromise<T>> values, Reducer reducer) -> QPromise<T>
QPromise::reduce(Sequence<T|QPromise<T>> values, Reducer reducer, R|QPromise<R> initialValue) -> QPromise<R>
// With:
// - Sequence: STL compatible container (e.g. QVector, etc.)
// - Reducer: Function(T|R accumulator, T item, int index) -> R|QPromise<R>
```
Iterates over `values` and [reduces the sequence to a single value](https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29) using the given `reducer` function and an optional `initialValue`. The type returned by `reducer` determines the type of the `output` promise. If `reducer` throws, `output` is rejected with the new exception.
If `reducer` returns a promise (or `QFuture`), then the result of the promise is awaited, before continuing with next iteration. If any promise in the `values` sequence is rejected or a promise returned by the `reducer` function is rejected, `output` immediately rejects with the error of the promise that rejected.
```cpp
// Concatenate the content of the given files, read asynchronously
auto output = QtPromise::reduce(QList<QUrl>{
"file:f0.txt", // contains "foo"
"file:f1.txt", // contains "bar"
"file:f2.txt" // contains "42"
}, [](const QString& acc, const QString& cur, int idx) {
return readAsync(cur).then([=](const QString& res) {
return QString("%1;%2:%3").arg(acc).arg(idx).arg(res);
});
}, QString("index:text"));
// 'output' resolves as soon as all promises returned by
// 'reducer' are fulfilled or at least one is rejected.
// 'output' type: QPromise<QString>
output.then([](const QString& res) {
// res == "index:text;0:foo;1:bar;2:42"
// {...}
});
```
::: warning IMPORTANT
The first time `reducer` is called, if no `initialValue` is provided, `accumulator` will be equal to the first value in the sequence, and `currentValue` to the second one (thus index will be `1`).
:::
See also: [`QPromise<T>::reduce`](../qpromise/reduce.md)

View File

@ -0,0 +1,58 @@
---
title: .reduce
---
# QPromise::reduce
*Since: 0.5.0*
```cpp
QPromise<Sequence<T>>::reduce(Reducer reducer) -> QPromise<T>
QPromise<Sequence<T>>::reduce(Reducer reducer, R|QPromise<R> initialValue) -> QPromise<R>
// With:
// - Sequence: STL compatible container (e.g. QVector, etc.)
// - Reducer: Function(T|R accumulator, T item, int index) -> R|QPromise<R>
```
::: warning IMPORTANT
This method only applies to promise with sequence value.
:::
Iterates over all the promise values (i.e. `Sequence<T>`) and [reduces the sequence to a single value](https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29) using the given `reducer` function and an optional `initialValue`. The type returned by `reducer` determines the type of the `output` promise.
See [`QtPromise::reduce`](../helpers/reduce.md) for details, this method is provided for convenience and is similar to:
```cpp
promise.then([](const T& values) {
return QtPromise::reduce(values, reducer, initialValue);
})
```
For example:
```cpp
auto input = QtPromise::resolve(QList<QUrl>{
"file:f0.txt", // contains "foo"
"file:f1.txt", // contains "bar"
"file:f2.txt" // contains "42"
});
// Concatenate the content of the given files, read asynchronously
auto output = input.reduce([](const QString& acc, const QString& cur, int idx) {
return readAsync(cur).then([=](const QString& res) {
return QString("%1;%2:%3").arg(acc).arg(idx).arg(res);
});
}, QString("index:text"));
// 'output' resolves as soon as all promises returned by
// 'reducer' are fulfilled or at least one is rejected.
// 'output' type: QPromise<QString>
output.then([](const QString& res) {
// res == "index:text;0:foo;1:bar;2:42"
// {...}
});
```
See also: [`QtPromise::reduce`](../helpers/reduce.md)

View File

@ -46,7 +46,7 @@ The `output` promise is resolved when the `QFuture` is [finished](https://doc.qt
## Error ## Error
Exceptions thrown from a QtConcurrent thread reject the associated promise with the exception as the reason. Note that if you throw an exception that is not a subclass of `QException`, the promise with be rejected with [`QUnhandledException`](https://doc.qt.io/qt-5/qunhandledexception.html#details) (this restriction only applies to exceptions thrown from a QtConcurrent thread, [read more](https://doc.qt.io/qt-5/qexception.html#details)). Exceptions thrown from a QtConcurrent thread reject the associated promise with the exception as the reason. Note that if you throw an exception that is not a subclass of `QException`, the promise will be rejected with [`QUnhandledException`](https://doc.qt.io/qt-5/qunhandledexception.html#details) (this restriction only applies to exceptions thrown from a QtConcurrent thread, [read more](https://doc.qt.io/qt-5/qexception.html#details)).
```cpp ```cpp
QPromise<int> promise = ... QPromise<int> promise = ...

View File

@ -76,6 +76,7 @@ public: // STATIC
protected: protected:
friend struct QtPromisePrivate::PromiseFulfill<QPromise<T>>; friend struct QtPromisePrivate::PromiseFulfill<QPromise<T>>;
friend class QtPromisePrivate::PromiseResolver<T>; friend class QtPromisePrivate::PromiseResolver<T>;
friend struct QtPromisePrivate::PromiseInspect;
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T>> m_d; QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T>> m_d;
}; };
@ -88,15 +89,25 @@ public:
QPromise(F&& resolver): QPromiseBase<T>(std::forward<F>(resolver)) { } QPromise(F&& resolver): QPromiseBase<T>(std::forward<F>(resolver)) { }
template <typename Functor> template <typename Functor>
inline QPromise<T> each(Functor fn); inline QPromise<T>
each(Functor fn);
template <typename Functor> template <typename Functor>
inline QPromise<T> filter(Functor fn); inline QPromise<T>
filter(Functor fn);
template <typename Functor> template <typename Functor>
inline typename QtPromisePrivate::PromiseMapper<T, Functor>::PromiseType inline typename QtPromisePrivate::PromiseMapper<T, Functor>::PromiseType
map(Functor fn); map(Functor fn);
template <typename Functor, typename Input>
inline typename QtPromisePrivate::PromiseDeduce<Input>::Type
reduce(Functor fn, Input initial);
template <typename Functor, typename U = T>
inline typename QtPromisePrivate::PromiseDeduce<typename U::value_type>::Type
reduce(Functor fn);
public: // STATIC public: // STATIC
// DEPRECATED (remove at version 1) // DEPRECATED (remove at version 1)

View File

@ -197,6 +197,26 @@ QPromise<T>::map(Functor fn)
}); });
} }
template <typename T>
template <typename Functor, typename Input>
inline typename QtPromisePrivate::PromiseDeduce<Input>::Type
QPromise<T>::reduce(Functor fn, Input initial)
{
return this->then([=](const T& values) {
return QtPromise::reduce(values, fn, initial);
});
}
template <typename T>
template <typename Functor, typename U>
inline typename QtPromisePrivate::PromiseDeduce<typename U::value_type>::Type
QPromise<T>::reduce(Functor fn)
{
return this->then([=](const T& values) {
return QtPromise::reduce(values, fn);
});
}
template <typename T> template <typename T>
template <template <typename, typename...> class Sequence, typename ...Args> template <template <typename, typename...> class Sequence, typename ...Args>
inline QPromise<QVector<T>> QPromise<T>::all(const Sequence<QPromise<T>, Args...>& promises) inline QPromise<QVector<T>> QPromise<T>::all(const Sequence<QPromise<T>, Args...>& promises)

View File

@ -558,6 +558,15 @@ protected:
} }
}; };
} // namespace QtPromise struct PromiseInspect
{
template <typename T>
static inline PromiseData<T>* get(const QtPromise::QPromise<T>& p)
{
return p.m_d.data();
}
};
} // namespace QtPromisePrivate
#endif // QTPROMISE_QPROMISE_H #endif // QTPROMISE_QPROMISE_H

View File

@ -169,7 +169,8 @@ connect(const Sender* sender, FSignal fsignal, RSignal rsignal)
} }
template <typename Sequence, typename Functor> template <typename Sequence, typename Functor>
static inline QPromise<Sequence> each(const Sequence& values, Functor&& fn) static inline QPromise<Sequence>
each(const Sequence& values, Functor&& fn)
{ {
return QPromise<Sequence>::resolve(values).each(std::forward<Functor>(fn)); return QPromise<Sequence>::resolve(values).each(std::forward<Functor>(fn));
} }
@ -200,7 +201,8 @@ map(const Sequence& values, Functor fn)
} }
template <typename Sequence, typename Functor> template <typename Sequence, typename Functor>
static inline QPromise<Sequence> filter(const Sequence& values, Functor fn) static inline QPromise<Sequence>
filter(const Sequence& values, Functor fn)
{ {
return QtPromise::map(values, fn) return QtPromise::map(values, fn)
.then([=](const QVector<bool>& filters) { .then([=](const QVector<bool>& filters) {
@ -219,6 +221,59 @@ static inline QPromise<Sequence> filter(const Sequence& values, Functor fn)
}); });
} }
template <typename T, template <typename...> class Sequence = QVector, typename Reducer, typename Input, typename ...Args>
static inline typename QtPromisePrivate::PromiseDeduce<Input>::Type
reduce(const Sequence<T, Args...>& values, Reducer fn, Input initial)
{
using namespace QtPromisePrivate;
using PromiseType = typename PromiseDeduce<T>::Type;
using ValueType = typename PromiseType::Type;
int idx = 0;
auto promise = QtPromise::resolve(std::move(initial));
for (const auto& value : values) {
auto input = QtPromise::resolve(value);
promise = promise.then([=]() {
return input.then([=](const ValueType& cur) {
return fn(PromiseInspect::get(promise)->value().data(), cur, idx);
});
});
idx++;
}
return promise;
}
template <typename T, template <typename...> class Sequence = QVector, typename Reducer, typename ...Args>
static inline typename QtPromisePrivate::PromiseDeduce<T>::Type
reduce(const Sequence<T, Args...>& values, Reducer fn)
{
using namespace QtPromisePrivate;
using PromiseType = typename PromiseDeduce<T>::Type;
using ValueType = typename PromiseType::Type;
Q_ASSERT(values.size());
int idx = 1;
auto it = values.begin();
auto promise = QtPromise::resolve(*it);
for (++it; it != values.end(); ++it) {
auto input = QtPromise::resolve(*it);
promise = promise.then([=]() {
return input.then([=](const ValueType& cur) {
return fn(PromiseInspect::get(promise)->value().data(), cur, idx);
});
});
idx++;
}
return promise;
}
// DEPRECATIONS (remove at version 1) // DEPRECATIONS (remove at version 1)
template <typename... Args> template <typename... Args>

View File

@ -6,5 +6,6 @@ SUBDIRS += \
each \ each \
filter \ filter \
map \ map \
reduce \
reject \ reject \
resolve resolve

View File

@ -0,0 +1,4 @@
TARGET = tst_helpers_reduce
SOURCES += $$PWD/tst_reduce.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,271 @@
#include "../../shared/data.h"
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_helpers_reduce : public QObject
{
Q_OBJECT
private Q_SLOTS:
void emptySequence();
void regularValues();
void promiseValues();
void convertResultType();
void delayedInitialValue();
void delayedFulfilled();
void delayedRejected();
void functorThrows();
void sequenceTypes();
};
QTEST_MAIN(tst_helpers_reduce)
#include "tst_reduce.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
static void exec()
{
Sequence inputs{
QtPromise::resolve(4).delay(400),
QtPromise::resolve(6).delay(300),
QtPromise::resolve(8).delay(200)
};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
return acc + cur + idx;
});
auto p1 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
return acc + cur + idx;
}, QtPromise::resolve(2).delay(100));
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 21);
QCOMPARE(waitForValue(p1, -1), 23);
QCOMPARE(v0, QVector<int>({4, 6, 1, 11, 8, 2}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
};
} // anonymous namespace
void tst_helpers_reduce::emptySequence()
{
bool called = false;
auto p = QtPromise::reduce(QVector<int>{}, [&](...) {
called = true;
return 43;
}, 42);
// NOTE(SB): reduce() on an empty sequence without an initial value is an error!
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(called, false);
}
void tst_helpers_reduce::regularValues()
{
QVector<int> inputs{4, 6, 8};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
return acc + cur + idx;
});
auto p1 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
return acc + cur + idx;
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 21);
QCOMPARE(waitForValue(p1, -1), 23);
QCOMPARE(v0, QVector<int>({4, 6, 1, 11, 8, 2}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
void tst_helpers_reduce::promiseValues()
{
QVector<QPromise<int>> inputs{
QtPromise::resolve(4).delay(400),
QtPromise::resolve(6).delay(300),
QtPromise::resolve(8).delay(200)
};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
return acc + cur + idx;
});
auto p1 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
return acc + cur + idx;
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 21);
QCOMPARE(waitForValue(p1, -1), 23);
QCOMPARE(v0, QVector<int>({4, 6, 1, 11, 8, 2}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
void tst_helpers_reduce::convertResultType()
{
QVector<int> inputs{4, 6, 8};
auto p = QtPromise::reduce(inputs, [&](const QString& acc, int cur, int idx) {
return QString("%1:%2:%3").arg(acc).arg(cur).arg(idx);
}, QString("foo"));
// NOTE(SB): when no initial value is given, the result type is the sequence type.
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QString()), QString("foo:4:0:6:1:8:2"));
}
void tst_helpers_reduce::delayedInitialValue()
{
QVector<int> values;
auto p = QtPromise::reduce(QVector<int>{4, 6, 8}, [&](int acc, int cur, int idx) {
values << acc << cur << idx;
return acc + cur + idx;
}, QtPromise::resolve(2).delay(100));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), 23);
QCOMPARE(values, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
void tst_helpers_reduce::delayedFulfilled()
{
QVector<int> inputs{4, 6, 8};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
return QtPromise::resolve(acc + cur + idx).delay(100);
});
auto p1 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
return QtPromise::resolve(acc + cur + idx).delay(100);
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 21);
QCOMPARE(waitForValue(p1, -1), 23);
QCOMPARE(v0, QVector<int>({4, 6, 1, 11, 8, 2}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
void tst_helpers_reduce::delayedRejected()
{
QVector<int> inputs{4, 6, 8};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
if (cur == 6) {
return QPromise<int>::reject(QString("foo"));
}
return QtPromise::resolve(acc + cur + idx);
});
auto p1 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
if (cur == 6) {
return QPromise<int>::reject(QString("bar"));
}
return QtPromise::resolve(acc + cur + idx);
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForError(p0, QString()), QString("foo"));
QCOMPARE(waitForError(p1, QString()), QString("bar"));
QCOMPARE(v0, QVector<int>({4, 6, 1}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1}));
}
void tst_helpers_reduce::functorThrows()
{
QVector<int> inputs{4, 6, 8};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
if (cur == 6) {
throw QString("foo");
}
return acc + cur + idx;
});
auto p1 = QtPromise::reduce(inputs, [&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
if (cur == 6) {
throw QString("bar");
}
return acc + cur + idx;
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForError(p0, QString()), QString("foo"));
QCOMPARE(waitForError(p1, QString()), QString("bar"));
QCOMPARE(v0, QVector<int>({4, 6, 1}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1}));
}
void tst_helpers_reduce::sequenceTypes()
{
SequenceTester<QLinkedList<QPromise<int>>>::exec();
SequenceTester<QList<QPromise<int>>>::exec();
SequenceTester<QVector<QPromise<int>>>::exec();
SequenceTester<std::list<QPromise<int>>>::exec();
SequenceTester<std::vector<QPromise<int>>>::exec();
}

View File

@ -8,6 +8,7 @@ SUBDIRS += \
finally \ finally \
map \ map \
operators \ operators \
reduce \
resolve \ resolve \
tap \ tap \
tapfail \ tapfail \

View File

@ -0,0 +1,4 @@
TARGET = tst_qpromise_reduce
SOURCES += $$PWD/tst_reduce.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,271 @@
#include "../../shared/data.h"
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_reduce : public QObject
{
Q_OBJECT
private Q_SLOTS:
void emptySequence();
void regularValues();
void promiseValues();
void convertResultType();
void delayedInitialValue();
void delayedFulfilled();
void delayedRejected();
void functorThrows();
void sequenceTypes();
};
QTEST_MAIN(tst_qpromise_reduce)
#include "tst_reduce.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
static void exec()
{
Sequence inputs{
QtPromise::resolve(4).delay(400),
QtPromise::resolve(6).delay(300),
QtPromise::resolve(8).delay(200)
};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
return acc + cur + idx;
});
auto p1 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
return acc + cur + idx;
}, QtPromise::resolve(2).delay(100));
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 21);
QCOMPARE(waitForValue(p1, -1), 23);
QCOMPARE(v0, QVector<int>({4, 6, 1, 11, 8, 2}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
};
} // anonymous namespace
void tst_qpromise_reduce::emptySequence()
{
bool called = false;
auto p = QtPromise::resolve(QVector<int>{}).reduce([&](...) {
called = true;
return 43;
}, 42);
// NOTE(SB): reduce() on an empty sequence without an initial value is an error!
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(called, false);
}
void tst_qpromise_reduce::regularValues()
{
QVector<int> inputs{4, 6, 8};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
return acc + cur + idx;
});
auto p1 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
return acc + cur + idx;
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 21);
QCOMPARE(waitForValue(p1, -1), 23);
QCOMPARE(v0, QVector<int>({4, 6, 1, 11, 8, 2}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
void tst_qpromise_reduce::promiseValues()
{
QVector<QPromise<int>> inputs{
QtPromise::resolve(4).delay(400),
QtPromise::resolve(6).delay(300),
QtPromise::resolve(8).delay(200)
};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
return acc + cur + idx;
});
auto p1 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
return acc + cur + idx;
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 21);
QCOMPARE(waitForValue(p1, -1), 23);
QCOMPARE(v0, QVector<int>({4, 6, 1, 11, 8, 2}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
void tst_qpromise_reduce::convertResultType()
{
QVector<int> inputs{4, 6, 8};
auto p = QtPromise::resolve(inputs).reduce([&](const QString& acc, int cur, int idx) {
return QString("%1:%2:%3").arg(acc).arg(cur).arg(idx);
}, QString("foo"));
// NOTE(SB): when no initial value is given, the result type is the sequence type.
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QString()), QString("foo:4:0:6:1:8:2"));
}
void tst_qpromise_reduce::delayedInitialValue()
{
QVector<int> values;
auto p = QtPromise::resolve(QVector<int>{4, 6, 8}).reduce([&](int acc, int cur, int idx) {
values << acc << cur << idx;
return acc + cur + idx;
}, QtPromise::resolve(2).delay(100));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), 23);
QCOMPARE(values, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
void tst_qpromise_reduce::delayedFulfilled()
{
QVector<int> inputs{4, 6, 8};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
return QtPromise::resolve(acc + cur + idx).delay(100);
});
auto p1 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
return QtPromise::resolve(acc + cur + idx).delay(100);
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 21);
QCOMPARE(waitForValue(p1, -1), 23);
QCOMPARE(v0, QVector<int>({4, 6, 1, 11, 8, 2}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1, 13, 8, 2}));
}
void tst_qpromise_reduce::delayedRejected()
{
QVector<int> inputs{4, 6, 8};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
if (cur == 6) {
return QPromise<int>::reject(QString("foo"));
}
return QtPromise::resolve(acc + cur + idx);
});
auto p1 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
if (cur == 6) {
return QPromise<int>::reject(QString("bar"));
}
return QtPromise::resolve(acc + cur + idx);
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForError(p0, QString()), QString("foo"));
QCOMPARE(waitForError(p1, QString()), QString("bar"));
QCOMPARE(v0, QVector<int>({4, 6, 1}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1}));
}
void tst_qpromise_reduce::functorThrows()
{
QVector<int> inputs{4, 6, 8};
QVector<int> v0;
QVector<int> v1;
auto p0 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v0 << acc << cur << idx;
if (cur == 6) {
throw QString("foo");
}
return acc + cur + idx;
});
auto p1 = QtPromise::resolve(inputs).reduce([&](int acc, int cur, int idx) {
v1 << acc << cur << idx;
if (cur == 6) {
throw QString("bar");
}
return acc + cur + idx;
}, 2);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForError(p0, QString()), QString("foo"));
QCOMPARE(waitForError(p1, QString()), QString("bar"));
QCOMPARE(v0, QVector<int>({4, 6, 1}));
QCOMPARE(v1, QVector<int>({2, 4, 0, 6, 6, 1}));
}
void tst_qpromise_reduce::sequenceTypes()
{
SequenceTester<QLinkedList<QPromise<int>>>::exec();
SequenceTester<QList<QPromise<int>>>::exec();
SequenceTester<QVector<QPromise<int>>>::exec();
SequenceTester<std::list<QPromise<int>>>::exec();
SequenceTester<std::vector<QPromise<int>>>::exec();
}