mirror of
https://github.com/simonbrunel/qtpromise.git
synced 2024-10-30 15:57:39 +08:00
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:
parent
cbf4cc7867
commit
e3f0f054af
@ -33,6 +33,7 @@ module.exports = {
|
||||
'qtpromise/qpromise/ispending',
|
||||
'qtpromise/qpromise/isrejected',
|
||||
'qtpromise/qpromise/map',
|
||||
'qtpromise/qpromise/reduce',
|
||||
'qtpromise/qpromise/tap',
|
||||
'qtpromise/qpromise/tapfail',
|
||||
'qtpromise/qpromise/then',
|
||||
@ -51,6 +52,7 @@ module.exports = {
|
||||
'qtpromise/helpers/each',
|
||||
'qtpromise/helpers/filter',
|
||||
'qtpromise/helpers/map',
|
||||
'qtpromise/helpers/reduce',
|
||||
'qtpromise/helpers/resolve'
|
||||
]
|
||||
},
|
||||
|
@ -12,6 +12,7 @@
|
||||
* [`QPromise<T>::isPending`](qpromise/ispending.md)
|
||||
* [`QPromise<T>::isRejected`](qpromise/isrejected.md)
|
||||
* [`QPromise<T>::map`](qpromise/map.md)
|
||||
* [`QPromise<T>::reduce`](qpromise/reduce.md)
|
||||
* [`QPromise<T>::tap`](qpromise/tap.md)
|
||||
* [`QPromise<T>::tapFail`](qpromise/tapfail.md)
|
||||
* [`QPromise<T>::then`](qpromise/then.md)
|
||||
@ -31,6 +32,7 @@
|
||||
* [`QtPromise::each`](helpers/each.md)
|
||||
* [`QtPromise::filter`](helpers/filter.md)
|
||||
* [`QtPromise::map`](helpers/map.md)
|
||||
* [`QtPromise::reduce`](helpers/reduce.md)
|
||||
* [`QtPromise::resolve`](helpers/resolve.md)
|
||||
|
||||
## Exceptions
|
||||
|
48
docs/qtpromise/helpers/reduce.md
Normal file
48
docs/qtpromise/helpers/reduce.md
Normal 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)
|
58
docs/qtpromise/qpromise/reduce.md
Normal file
58
docs/qtpromise/qpromise/reduce.md
Normal 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)
|
@ -46,7 +46,7 @@ The `output` promise is resolved when the `QFuture` is [finished](https://doc.qt
|
||||
|
||||
## 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
|
||||
QPromise<int> promise = ...
|
||||
|
@ -76,6 +76,7 @@ public: // STATIC
|
||||
protected:
|
||||
friend struct QtPromisePrivate::PromiseFulfill<QPromise<T>>;
|
||||
friend class QtPromisePrivate::PromiseResolver<T>;
|
||||
friend struct QtPromisePrivate::PromiseInspect;
|
||||
|
||||
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T>> m_d;
|
||||
};
|
||||
@ -88,15 +89,25 @@ public:
|
||||
QPromise(F&& resolver): QPromiseBase<T>(std::forward<F>(resolver)) { }
|
||||
|
||||
template <typename Functor>
|
||||
inline QPromise<T> each(Functor fn);
|
||||
inline QPromise<T>
|
||||
each(Functor fn);
|
||||
|
||||
template <typename Functor>
|
||||
inline QPromise<T> filter(Functor fn);
|
||||
inline QPromise<T>
|
||||
filter(Functor fn);
|
||||
|
||||
template <typename Functor>
|
||||
inline typename QtPromisePrivate::PromiseMapper<T, Functor>::PromiseType
|
||||
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
|
||||
|
||||
// DEPRECATED (remove at version 1)
|
||||
|
@ -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 <template <typename, typename...> class Sequence, typename ...Args>
|
||||
inline QPromise<QVector<T>> QPromise<T>::all(const Sequence<QPromise<T>, Args...>& promises)
|
||||
|
@ -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
|
||||
|
@ -169,7 +169,8 @@ connect(const Sender* sender, FSignal fsignal, RSignal rsignal)
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
@ -200,7 +201,8 @@ map(const Sequence& values, Functor fn)
|
||||
}
|
||||
|
||||
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)
|
||||
.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)
|
||||
|
||||
template <typename... Args>
|
||||
|
@ -6,5 +6,6 @@ SUBDIRS += \
|
||||
each \
|
||||
filter \
|
||||
map \
|
||||
reduce \
|
||||
reject \
|
||||
resolve
|
||||
|
4
tests/auto/qtpromise/helpers/reduce/reduce.pro
Normal file
4
tests/auto/qtpromise/helpers/reduce/reduce.pro
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = tst_helpers_reduce
|
||||
SOURCES += $$PWD/tst_reduce.cpp
|
||||
|
||||
include(../../qtpromise.pri)
|
271
tests/auto/qtpromise/helpers/reduce/tst_reduce.cpp
Normal file
271
tests/auto/qtpromise/helpers/reduce/tst_reduce.cpp
Normal 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();
|
||||
}
|
@ -8,6 +8,7 @@ SUBDIRS += \
|
||||
finally \
|
||||
map \
|
||||
operators \
|
||||
reduce \
|
||||
resolve \
|
||||
tap \
|
||||
tapfail \
|
||||
|
4
tests/auto/qtpromise/qpromise/reduce/reduce.pro
Normal file
4
tests/auto/qtpromise/qpromise/reduce/reduce.pro
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = tst_qpromise_reduce
|
||||
SOURCES += $$PWD/tst_reduce.cpp
|
||||
|
||||
include(../../qtpromise.pri)
|
271
tests/auto/qtpromise/qpromise/reduce/tst_reduce.cpp
Normal file
271
tests/auto/qtpromise/qpromise/reduce/tst_reduce.cpp
Normal 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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user