50 Commits

Author SHA1 Message Date
67837827b1 Bump version to 0.5.0 2019-03-31 22:14:45 +02:00
e3f0f054af 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)`).
2019-03-31 15:16:08 +02:00
cbf4cc7867 Rename qPromiseAll() helper to QtPromise::all()
For consistency with other helpers, deprecate `qPromiseAll()` in favor of `QtPromise::all()`.
2019-03-25 14:45:51 +01:00
963ec621e1 Rename qPromise() helper to QtPromise::resolve()
For consistency with other helpers, deprecate `qPromise()` in favor of `QtPromise::resolve()` but also add support for calling this helper with lvalue. Add extra unit tests to make sure that rvalue is not copied.
2019-03-25 14:45:41 +01:00
1f30224578 Add support for creating promises from Qt signals (#25)
Introduce a new `QtPromise::connect()` helper that allows to create a promise resolved from a single signal and optionally, rejected by another one (from a different object or not). The promise type is determined by the type of the first signal argument (other arguments are currently ignored). A `QPromise<void>` is returned if the resolve signal doesn't provide any argument.

If the rejection is emitted before the promise is resolved, the promise will be rejected with the value of the first argument (other arguments being ignored). If the rejection signal doesn't provide any argument, the promise will be rejected with `QPromiseUndefinedException` if the signal is emitted. Additionally, the promise will be automatically rejected with `QPromiseContextException` if the source object is destroyed before the promise is resolved.
2019-03-04 14:02:32 +01:00
47b90fb532 Fix Gcov warning 'version 409*, prefer version 408*' 2019-03-01 17:47:32 +01:00
63acdfaab9 Make docs formatting consistent (no explicit word wrap) 2019-03-01 17:41:32 +01:00
700098ef7b Reorganize exceptions and add unit tests 2019-03-01 17:41:32 +01:00
6110cd40d3 Test LTS and latest Qt versions in AppVeyor 2019-02-21 12:07:21 +01:00
a6d883acbd Update LICENSE year and cleanup a few comments 2019-02-18 20:30:41 +01:00
9119cc72f6 Add documentation for built-in exceptions 2019-02-01 16:06:29 +01:00
fa5a4192ff Allow undefined rejection reason
While not recommended because it makes tracking errors more difficult, it's now possible to reject a promise without explicit reason, in which case, a built-in `QPromiseUndefinedException` is thrown. This is done in anticipation of handling rejection signals without argument.
2019-01-31 19:03:57 +01:00
16229fc2c9 Integrate DocSearch (Algolia)
https://github.com/algolia/docsearch-configs/blob/master/configs/qtpromise.json
https://community.algolia.com/docsearch/
2018-09-10 12:36:31 +02:00
22faef4ac3 Bump version to 0.4.0 2018-09-09 21:27:33 +02:00
eebcb4f364 Migrate documentation to VuePress
GitBook development seems a bit stuck right now so let's switch to VuePress which, IMO, is more user friendly. Update the documentation to include the version number in which features were added and use custom container to display notes and warnings.
2018-09-09 10:55:07 +02:00
051fed5fbc Implement QPromise<Sequence<T>>::each(functor)
Call the given `functor` on each element in the promise value (i.e. `Sequence<T>`), then resolve to the original sequence unmodified. Also provide a static helper to directly filter values (`QtPromise::each(values, functor)`).
2018-09-07 16:50:29 +02:00
f610826ef0 Implement QtPromise::attempt(functor, args...)
Add a new helper that calls functor immediately and returns a promise fulfilled with the value returned by functor. Any synchronous exceptions will be turned into rejections on the returned promise. This is a convenient method that can be used instead of handling both synchronous and asynchronous exception flows.

Also simplify PromiseDispatch which now calls the functor with a variable number of arguments (including none).
2018-05-31 09:02:51 +02:00
4fa7a37750 Implement QPromise<Sequence<T>>::filter(filterer)
Add a new method that iterates over all the promise values (i.e. `Sequence<T>`) and filters the sequence to another using the given `filterer` function. If `filterer` returns `true`, a copy of the item is put in the `output` sequence, otherwise, the item will not appear in `output`. Also provide a static helper to directly filter values (`QtPromise::filter(values, filterer)`).
2018-05-30 18:52:53 +02:00
69c07855f4 Implement QPromise<Sequence<T>>::map(mapper) (#15)
Iterate over all the promise value (i.e. `Sequence<T>`) and map the sequence to another using the given `mapper` function. Also provide a static helper to directly map values (`QtPromise::map(values, mapper)`).
2018-05-26 11:40:51 +02:00
4cfe2e54f4 Move resolver related classes in a separate file. 2018-05-23 21:24:14 +02:00
54d88f16a3 Reorganize helpers unit tests 2018-05-23 21:24:00 +02:00
bdf3619469 Add AppVeyor configuration file for running MSVC tests (#19)
Qt 5.6/msvc2013 and Qt 5.9/msvc2013_64
2018-05-23 18:31:35 +02:00
8da467e9da Relax timing constraints when checking timeout in tst_timeout 2018-05-21 10:12:35 +02:00
efb6001b9d Upgrade TravisCI environment to Qt 5.6 and GCC 4.9 2018-05-18 11:32:01 +02:00
26a2110a14 Fix GCC -Wold-style-cast warnings
Explicitly enable -Wold-style-cast and make all warnings into errors in tests (-Werror on GCC, -WX on MSVC).
2018-05-10 11:51:17 +02:00
fa987a5044 Cleanup promise captured in resolve/reject
Make sure that QPromiseResolve and QPromiseReject release their associated promise as soon as one of them is resolved or rejected, ensuring that the promise data is cleaned up once resolved (that fixes cases where one or both of them are captured in a signal/slot connection lambda)
2018-05-10 11:51:17 +02:00
7b0cba5b9d Allow QSharedPointer as rejection reason
Embed the promise fulfillment value and rejection reason in respectively PromiseValue and PromiseError private wrappers, both storing the data in a shared pointer (QPromiseError is now deprecated).
2018-05-10 11:51:14 +02:00
2c8ed6e676 Remove extra space between closing angle brackets
The extra space between template closing angle brackets is not anymore required in C++11 (https://en.wikipedia.org/wiki/C%2B%2B11#Right_angle_bracket)
2018-05-10 09:21:21 +02:00
d128a5fa8d Clarify QPromise::all fulfillment values order 2018-04-30 19:05:21 +02:00
dcbb2ef860 Fix clang "unused type alias 'FType'" warning 2018-04-26 16:06:10 +02:00
50bae380be Implement QPromise::tapFail(handler) 2018-03-29 09:08:19 +02:00
d279fb4391 Bump version to 0.3.0 2018-03-01 22:32:46 +01:00
313d3882d7 Fix dispatching when app (or thread) terminated
Make sure to **not** notify handlers if the captured thread doesn't exist anymore, which would potentially result in dispatching to the wrong thread (ie. nullptr == current thread). This also applies when the app is shutting down and the even loop is not anymore available. In both cases, we should not trigger any error and skip notifications.
2018-02-24 12:43:52 +01:00
f794916be6 Enhance QPromise<T>::wait documentation 2018-02-17 15:41:46 +01:00
9d2a4ca00f Add QPromise<T>::timeout documentation 2018-02-17 12:59:05 +01:00
4af2740d80 Enable QPromise<T>::resolve() by reference 2018-02-14 22:36:22 +01:00
18739bd8e0 New documentation based on GitBook CLI
Split the root README.md in multiple Markdown files (in the `docs/` folder) to make easier reading, editing and extending the documentation. An online version is also available on netlify (https://qtpromise.netlify.com). Building it requires Node.js installed, then:

- npm install -g gitbook-cli
- gitbook install ./
- gitbook build . dist/docs
2018-02-11 19:02:14 +01:00
c34316243e Make QPromise::all accept more container types
Currently, QPromise can't be added dynamically to QVector (::push_* | ::append) because it doesn't expose a default constructor. Until deciding if a default constructor should be added (private/public?), let's make the `QPromise::all` method work with any container types that are STL compatible.
2018-02-11 16:26:27 +01:00
d306423159 Add QPromise assignment and equality operators 2018-02-10 18:26:08 +01:00
d3b69f1248 Split QPromise tests per feature in separate .pro 2017-10-26 23:03:16 +02:00
36a0eed12a Move tests under their own qtpromise sub folder 2017-09-19 09:39:13 +02:00
931d5d5b13 Fix MSVC 2013 compilation issues 2017-09-04 11:17:26 +02:00
0682269d8f Bump version to 0.2.0 2017-09-02 12:36:44 +02:00
18324d3f44 Implement QPromise::timeout(msec, error) 2017-09-02 12:23:42 +02:00
b47ca0569e Implement QPromise::delay(msec)
This method returns a promise that will be fulfilled with the same value as the `input` promise and after at least `msec` milliseconds. If the `input` promise is rejected, the `output` promise is immediately rejected with the same reason.
2017-08-24 18:28:44 +02:00
c55fa03e7b 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.
2017-08-23 21:06:59 +02:00
25d2bad54f Enhance QPromise::finally implementation
Make sure that the chained value is not copied when `finally` is called for a fulfilled input promise. The value was copied 7 times in the previous version because it was captured in a lambda, which one copied multiple times.
2017-08-23 10:59:44 +02:00
49a1d6a57b Avoid value copy when fulfilled from promise 2017-08-22 21:52:28 +02:00
c4aab4ef36 Fix circular reference memory leaks
When dispatching the promise result, we need to clear both handlers and catchers to prevent retaining circular references when the promise is captured into the handler and/or catcher. Also refactor part of the `notify` logic.
2017-08-22 12:38:41 +02:00
5d6bcc40ec Fix helpers multiple defined symbols 2017-08-12 15:57:05 +02:00
142 changed files with 7958 additions and 1776 deletions

27
.appveyor.yml Normal file
View File

@ -0,0 +1,27 @@
image: Visual Studio 2015
init:
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio "%VSVER%".0\VC\vcvarsall.bat" %ARCH%
- cmd: qmake --version
# https://www.appveyor.com/docs/windows-images-software/#qt
environment:
PATH: '%PATH%;%QTDIR%\bin'
matrix:
- QTDIR: C:\Qt\5.6\msvc2013
VSVER: 12
ARCH: x86
- QTDIR: C:\Qt\5.9\msvc2013_64
VSVER: 12
ARCH: x64
- QTDIR: C:\Qt\latest\msvc2015_64
VSVER: 14
ARCH: x64
build_script:
- cmd: qmake qtpromise.pro
- cmd: nmake
test_script:
- cmd: nmake check

11
.gitignore vendored
View File

@ -1,7 +1,16 @@
_book
dist
node_modules
*.gcno
*.gcda
*.moc
*.o
*.obj
*.exe
*.user
Makefile
Makefile*
moc_*.cpp
moc_*.h
coverage.info
package-lock.json
target_wrapper.bat

View File

@ -4,12 +4,17 @@ language: cpp
compiler: gcc
before_install:
- sudo add-apt-repository -y ppa:beineri/opt-qt542-trusty
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- sudo add-apt-repository -y ppa:beineri/opt-qt563-trusty
- sudo apt-get update -qq
install:
- sudo apt-get install -qq qt54base
- source /opt/qt54/bin/qt54-env.sh
- sudo apt-get install -qq gcc-4.9 g++-4.9
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 90
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.9 90
- sudo update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-4.9 90
- sudo apt-get install -qq qt56base
- source /opt/qt56/bin/qt56-env.sh
- wget http://archive.ubuntu.com/ubuntu/pool/universe/l/lcov/lcov_1.13.orig.tar.gz
- tar xf lcov_1.13.orig.tar.gz
- cd lcov-1.13/
@ -18,8 +23,8 @@ install:
before_script:
- qmake --version
- lcov --version
- gcc --version
- lcov --version && gcov --version
- gcc --version && g++ --version
script:
- qmake qtpromise.pro CONFIG+=coverage

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017 Simon Brunel
Copyright (c) 2019 Simon Brunel
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

408
README.md
View File

@ -1,407 +1,21 @@
<a href="https://promisesaplus.com/" title="Promises/A+ 1.1"><img src="http://promisesaplus.com/assets/logo-small.png" alt="Promises/A+" align="right"/></a>
<a href="https://promisesaplus.com/" title="Promises/A+ 1.1"><img src="https://promisesaplus.com/assets/logo-small.png" alt="Promises/A+" align="right"/></a>
# QtPromise
[![qpm](https://img.shields.io/github/release/simonbrunel/qtpromise.svg?style=flat-square&label=qpm&colorB=4CAF50)](http://www.qpm.io/packages/com.github.simonbrunel.qtpromise/index.html) [![Travis](https://img.shields.io/travis/simonbrunel/qtpromise.svg?style=flat-square)](https://travis-ci.org/simonbrunel/qtpromise) [![coverage](https://img.shields.io/codecov/c/github/simonbrunel/qtpromise.svg?style=flat-square)](https://codecov.io/gh/simonbrunel/qtpromise)
[![qpm](https://img.shields.io/github/release/simonbrunel/qtpromise.svg?style=flat-square&label=qpm&colorB=4CAF50)](https://www.qpm.io/packages/com.github.simonbrunel.qtpromise/index.html) [![Travis](https://img.shields.io/travis/simonbrunel/qtpromise/master.svg?style=flat-square)](https://travis-ci.org/simonbrunel/qtpromise) [![coverage](https://img.shields.io/codecov/c/github/simonbrunel/qtpromise.svg?style=flat-square)](https://codecov.io/gh/simonbrunel/qtpromise)
[Promises/A+](https://promisesaplus.com/) implementation for [Qt/C++](https://www.qt.io/).
Requires [Qt 5.4](https://www.qt.io/download/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects).
Requires [Qt 5.6](https://www.qt.io/download/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects).
## Getting Started
### Installation
QtPromise is a [header-only](https://en.wikipedia.org/wiki/Header-only) library, simply download the [latest release](https://github.com/simonbrunel/qtpromise/releases/latest) (or [`git submodule`](https://git-scm.com/docs/git-submodule])) and include `qtpromise.pri` from your project `.pro`.
## Documentation
### qpm
Alternatively and **only** if your project relies on [qpm](http://www.qpm.io/), you can install QtPromise as follow:
```bash
qpm install com.github.simonbrunel.qtpromise
```
### Usage
The recommended way to use QtPromise is to include the single module header:
```cpp
#include <QtPromise>
```
### Example
Let's first make the code more readable by using the library namespace:
```cpp
using namespace QtPromise;
```
This `download` function creates a [promise from callbacks](#qpromise-qpromise) which will be resolved when the network request is finished:
```cpp
QPromise<QByteArray> download(const QUrl& url)
{
return QPromise<QByteArray>([&](
const QPromiseResolve<QByteArray>& resolve,
const QPromiseReject<QByteArray>& reject) {
QNetworkReply* reply = manager->get(QNetworkRequest(url));
QObject::connect(reply, &QNetworkReply::finished, [=]() {
if (reply->error() == QNetworkReply::NoError) {
resolve(reply->readAll());
} else {
reject(reply->error());
}
reply->deleteLater();
});
});
}
```
The following method `uncompress` data in a separate thread and returns a [promise from QFuture](#qtconcurrent):
```cpp
QPromise<Entries> uncompress(const QByteArray& data)
{
return qPromise(QtConcurrent::run([](const QByteArray& data) {
Entries entries;
// {...} uncompress data and parse content.
if (error) {
throw MalformedException();
}
return entries;
}, data));
}
```
It's then easy to chain the whole asynchronous process using promises:
- initiate the promise chain by downloading a specific URL,
- [`then`](#qpromise-then) *and only if download succeeded*, uncompress received data,
- [`then`](#qpromise-then) validate and process the uncompressed entries,
- [`finally`](#qpromise-finally) perform operations whatever the process succeeded or failed,
- and handle specific errors using [`fail`](#qpromise-fail).
```cpp
download(url).then(&uncompress).then([](const Entries& entries) {
if (entries.isEmpty()) {
throw UpdateException("No entries");
}
// {...} process entries
}).finally([]() {
// {...} cleanup
}).fail([](QNetworkReply::NetworkError err) {
// {...} handle network error
}).fail([](const UpdateException& err) {
// {...} handle update error
}).fail([]() {
// {...} catch all
});
```
## QtConcurrent
QtPromise integrates with [QtConcurrent](http://doc.qt.io/qt-5/qtconcurrent-index.html) to make easy chaining QFuture with QPromise.
### <a name="qtconcurrent-convert"></a> Convert
Converting `QFuture<T>` to `QPromise<T>` is done using the [`qPromise`](#helpers-qpromise) helper:
```cpp
QFuture<int> future = QtConcurrent::run([]() {
// {...}
return 42;
});
QPromise<int> promise = qPromise(future);
```
or simply:
```cpp
auto promise = qPromise(QtConcurrent::run([]() {
// {...}
}));
```
### Chain
Returning a `QFuture<T>` in [`then`](#qpromise-then) or [`fail`](#qpromise-fail) automatically translate to `QPromise<T>`:
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
return QtConcurrent::run([]() {
// {...}
return QString("42");
});
});
// output type: QPromise<QString>
output.then([](const QString& res) {
// {...}
});
```
The `output` promise is resolved when the `QFuture` is [finished](http://doc.qt.io/qt-5/qfuture.html#isFinished).
### 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`](http://doc.qt.io/qt-5/qunhandledexception.html#details) (this restriction only applies to exceptions thrown from a QtConcurrent thread, [read more](http://doc.qt.io/qt-5/qexception.html#details)).
```cpp
QPromise<int> promise = ...
promise.then([](int res) {
return QtConcurrent::run([]() {
// {...}
if (!success) {
throw CustomException();
}
return QString("42");
});
}).fail(const CustomException& err) {
// {...}
});
```
## Thread-Safety
QPromise is thread-safe and can be copied and accessed across different threads. QPromise relies on [explicitly data sharing](http://doc.qt.io/qt-5/qexplicitlyshareddatapointer.html#details) and thus `auto p2 = p1` represents the same promise: when `p1` resolves, handlers registered on `p1` and `p2` are called, the fulfilled value being shared between both instances.
> **Note:** while it's safe to access the resolved value from different threads using [`then`](#qpromise-then), QPromise provides no guarantee about the object being pointed to. Thread-safety and reentrancy rules for that object still apply.
## QPromise
### <a name="qpromise-qpromise"></a> `QPromise<T>::QPromise(resolver)`
Creates a new promise that will be fulfilled or rejected by the given `resolver` lambda:
```cpp
QPromise<int> promise([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>& reject) {
async_method([=](bool success, int result) {
if (success) {
resolve(result);
} else {
reject(customException());
}
});
});
```
> **Note:** `QPromise<void>` is specialized to not contain any value, meaning that the `resolve` callback takes no argument.
**C++14**
```cpp
QPromise<int> promise([](const auto& resolve, const auto& reject) {
// {...}
});
```
### <a name="qpromise-then"></a> `QPromise<T>::then(onFulfilled, onRejected) -> QPromise<R>`
See [Promises/A+ `.then`](https://promisesaplus.com/#the-then-method) for details.
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
// called with the 'input' result if fulfilled
}, [](const ReasonType& reason) {
// called with the 'input' reason if rejected
// see QPromise<T>::fail for details
});
```
> **Note**: `onRejected` handler is optional, `output` will be rejected with the same reason as `input`.
> **Note**: it's recommended to use the [`fail`](#qpromise-fail) shorthand to handle errors.
The type `<R>` of the `output` promise depends on the return type of the `onFulfilled` handler:
```cpp
QPromise<int> input = {...}
auto output = input.then([](int res) {
return QString::number(res); // -> QPromise<QString>
});
// output type: QPromise<QString>
output.then([](const QString& res) {
// {...}
});
```
> **Note**: only `onFulfilled` can change the promise type, `onRejected` **must** return the same type as `onFulfilled`. That also means if `onFulfilled` is `nullptr`, `onRejected` must return the same type as the `input` promise.
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
return res + 4;
}, [](const ReasonType& reason) {
return -1;
});
```
If `onFulfilled` doesn't return any value, the `output` type is `QPromise<void>`:
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
// {...}
});
// output type: QPromise<void>
output.then([]() {
// `QPromise<void>` `onFulfilled` handler has no argument
});
```
You can also decide to skip the promise result by omitting the handler argument:
```cpp
QPromise<int> input = {...}
auto output = input.then([]( /* skip int result */ ) {
// {...}
});
```
The `output` promise can be *rejected* by throwing an exception in either `onFulfilled` or `onRejected`:
```cpp
QPromise<int> input = {...}
auto output = input.then([](int res) {
if (res == -1) {
throw ReasonType();
} else {
return res;
}
});
// output.isRejected() is true
```
If an handler returns a promise (or QFuture), the `output` promise is delayed and will be resolved by the returned promise.
### <a name="qpromise-fail"></a> `QPromise<T>::fail(onRejected) -> QPromise<T>`
Shorthand to `promise.then(nullptr, onRejected)`, similar to the [`catch` statement](http://en.cppreference.com/w/cpp/language/try_catch):
```cpp
promise.fail([](const MyException&) {
// {...}
}).fail(const QException&) {
// {...}
}).fail(const std::exception&) {
// {...}
}).fail() {
// {...} catch-all
});
```
### <a name="qpromise-finally"></a> `QPromise<T>::finally(handler) -> QPromise<T>`
This `handler` is **always** called, without any argument and whatever the `input` promise state (fulfilled or rejected). The `output` promise has the same type as the `input` one but also the same value or error. The finally `handler` **can not modify the fulfilled value** (the returned value is ignored), however, if `handler` throws, `output` is rejected with the new exception.
```cpp
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.
### <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:
```cpp
int result = -1;
QPromise<int> input = qPromise(QtConcurrent::run([]() { return 42; }));
auto output = input.then([&](int res) {
result = res;
});
// output.isPending() is true && result is -1
output.wait();
// output.isPending() is false && result is 42
```
### `QPromise<T>::isPending() -> bool`
Returns `true` if the promise is pending (not fulfilled or rejected), otherwise returns `false`.
### `QPromise<T>::isFulfilled() -> bool`
Returns `true` if the promise is fulfilled, otherwise returns `false`.
### `QPromise<T>::isRejected() -> bool`
Returns `true` if the promise is rejected, otherwise returns `false`.
## QPromise (statics)
### <a name="qpromise-resolve"></a> `[static] QPromise<T>::resolve(value) -> QPromise<T>`
Creates a `QPromise<T>` that is fulfilled with the given `value` of type `T`:
```cpp
QPromise<int> compute(const QString& type)
{
if (type == "magic") {
return QPromise<int>::resolve(42);
}
return QPromise<int>([](const QPromiseResolve<int>& resolve) {
// {...}
});
}
```
See also: [`qPromise`](#helpers-qpromise)
### <a name="qpromise-reject"></a> `[static] QPromise<T>::reject(reason) -> QPromise<T>`
Creates a `QPromise<T>` that is rejected with the given `reason` of *whatever type*:
```cpp
QPromise<int> compute(const QString& type)
{
if (type == "foobar") {
return QPromise<int>::reject(QString("Unknown type: %1").arg(type));
}
return QPromise<int>([](const QPromiseResolve<int>& resolve) {
// {...}
});
}
```
### <a name="qpromise-all"></a> `[static] QPromise<T>::all(QVector<QPromise<T>>) -> QPromise<QVector<T>>`
Returns a `QPromise<QVector<T>>` that fulfills when **all** `promises` of (the same) type `T` have been fulfilled. The `output` value is a vector containing **all** the values of `promises`, in the same order. If any of the given `promises` fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
```cpp
QVector<QPromise<QByteArray> > promises{
download(QUrl("http://a...")),
download(QUrl("http://b...")),
download(QUrl("http://c..."))
};
auto output = QPromise<QByteArray>::all(promises);
// output type: QPromise<QVector<QByteArray>>
output.then([](const QVector<QByteArray>& res) {
// {...}
});
```
See also: [`qPromiseAll`](#helpers-qpromiseall)
## Helpers
### <a name="helpers-qpromise"></a> `qPromise(T value) -> QPromise<R>`
Similar to the `QPromise<T>::resolve` static method, creates a promise resolved from a given `value` without the extra typing:
```cpp
auto promise = qPromise(); // QPromise<void>
auto promise = qPromise(42); // QPromise<int>
auto promise = qPromise(QString("foo")); // QPromise<QString>
```
This method also allows to convert `QFuture<T>` to `QPromise<T>` delayed until the `QFuture` is finished ([read more](#qtconcurrent-convert)).
### <a name="helpers-qpromiseall"></a> `qPromiseAll(QVector<QPromise<T> promises) -> QPromise<QVector<T>>`
This method simply calls the appropriated [`QPromise<T>::all`](#qpromise-all) static method based on the given `QVector` type. In some cases, this method is more convenient than the static one since it avoid some extra typing:
```cpp
QVector<QPromise<QByteArray> > promises{...}
auto output = qPromiseAll(promises);
// eq. QPromise<QByteArray>::all(promises)
```
* [Getting Started](https://qtpromise.netlify.com/qtpromise/getting-started.html)
* [Qt Concurrent](https://qtpromise.netlify.com/qtpromise/qtconcurrent.html)
* [Qt Signals](https://qtpromise.netlify.com/qtpromise/qtsignals.html)
* [Thread-Safety](https://qtpromise.netlify.com/qtpromise/thread-safety.html)
* [API Reference](https://qtpromise.netlify.com/qtpromise/api-reference.html)
## License
QtPromise is available under the [MIT license](LICENSE).

70
docs/.vuepress/config.js Normal file
View File

@ -0,0 +1,70 @@
module.exports = {
title: 'QtPromise',
description: 'Promises/A+ implementation for Qt/C++',
ga: 'UA-113899811-1',
head: [
['link', { rel: 'icon', href: `/favicon.png` }],
],
themeConfig: {
repo: 'simonbrunel/qtpromise',
lastUpdated: 'Last Updated',
editLinks: true,
docsDir: 'docs',
algolia: {
apiKey: '0e6e9cccb8c2c360a5543e28c4e31cb8',
indexName: 'qtpromise'
},
sidebar: [
'qtpromise/getting-started',
'qtpromise/qtconcurrent',
'qtpromise/qtsignals',
'qtpromise/thread-safety',
'qtpromise/api-reference',
{
title: 'QPromise',
children: [
'qtpromise/qpromise/constructor',
'qtpromise/qpromise/delay',
'qtpromise/qpromise/each',
'qtpromise/qpromise/fail',
'qtpromise/qpromise/filter',
'qtpromise/qpromise/finally',
'qtpromise/qpromise/isfulfilled',
'qtpromise/qpromise/ispending',
'qtpromise/qpromise/isrejected',
'qtpromise/qpromise/map',
'qtpromise/qpromise/reduce',
'qtpromise/qpromise/tap',
'qtpromise/qpromise/tapfail',
'qtpromise/qpromise/then',
'qtpromise/qpromise/timeout',
'qtpromise/qpromise/wait',
'qtpromise/qpromise/reject.md',
'qtpromise/qpromise/resolve.md'
]
},
{
title: 'Helpers',
children: [
'qtpromise/helpers/all',
'qtpromise/helpers/attempt',
'qtpromise/helpers/connect',
'qtpromise/helpers/each',
'qtpromise/helpers/filter',
'qtpromise/helpers/map',
'qtpromise/helpers/reduce',
'qtpromise/helpers/resolve'
]
},
{
title: 'Exceptions',
children: [
'qtpromise/exceptions/canceled',
'qtpromise/exceptions/context',
'qtpromise/exceptions/timeout',
'qtpromise/exceptions/undefined'
]
}
]
}
}

View File

@ -0,0 +1,2 @@
$accentColor = #23b223
$textColor = #404244

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,4 @@
@import 'override.styl'
.content a code
color: $accentColor

9
docs/README.md Normal file
View File

@ -0,0 +1,9 @@
<a href="https://promisesaplus.com/" title="Promises/A+ 1.1"><img src="https://promisesaplus.com/assets/logo-small.png" alt="Promises/A+" align="right"/></a>
# QtPromise
[Promises/A+](https://promisesaplus.com/) implementation for [Qt/C++](https://www.qt.io/).
Requires [Qt 5.6](https://www.qt.io/download/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects).
## License
QtPromise is available under the [MIT license](https://github.com/simonbrunel/qtpromise/blob/master/LICENSE).

View File

@ -0,0 +1,49 @@
# API Reference
## Functions
* [`QPromise<T>::QPromise`](qpromise/constructor.md)
* [`QPromise<T>::delay`](qpromise/delay.md)
* [`QPromise<T>::each`](qpromise/each.md)
* [`QPromise<T>::fail`](qpromise/fail.md)
* [`QPromise<T>::filter`](qpromise/filter.md)
* [`QPromise<T>::finally`](qpromise/finally.md)
* [`QPromise<T>::isFulfilled`](qpromise/isfulfilled.md)
* [`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)
* [`QPromise<T>::timeout`](qpromise/timeout.md)
* [`QPromise<T>::wait`](qpromise/wait.md)
## Static Functions
* [`[static] QPromise<T>::reject`](qpromise/reject.md)
* [`[static] QPromise<T>::resolve`](qpromise/resolve.md)
## Helpers
* [`QtPromise::all`](helpers/all.md)
* [`QtPromise::attempt`](helpers/attempt.md)
* [`QtPromise::connect`](helpers/connect.md)
* [`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
* [`QPromiseCanceledException`](exceptions/canceled.md)
* [`QPromiseContextException`](exceptions/context.md)
* [`QPromiseTimeoutException`](exceptions/timeout.md)
* [`QPromiseUndefinedException`](exceptions/undefined.md)
## Deprecations
* `[static] QPromise<T>::all`: use [`QtPromise::all`](helpers/all.md) instead (since 0.5.0)
* `QtPromise::qPromise`: use [`QtPromise::resolve`](helpers/resolve.md) instead (since 0.5.0)
* `QtPromise::qPromiseAll`: use [`QtPromise::all`](helpers/all.md) instead (since 0.5.0)

View File

@ -0,0 +1,20 @@
---
title: QPromiseCanceledException
---
# QPromiseCanceledException
*Since: 0.1.0*
This exception is thrown for promise created from a [`QFuture`](../qtconcurrent.md) which has been canceled (e.g. using [`QFuture::cancel()`](http://doc.qt.io/qt-5/qfuture.html#cancel)), for example:
```cpp
auto output = QtPromise::resolve(future)
.fail([](const QPromiseCanceledException&) {
// `future` has been canceled!
});
```
::: tip NOTE
QtPromise doesn't support promise cancelation (yet?)
:::

View File

@ -0,0 +1,17 @@
---
title: QPromiseContextException
---
# QPromiseContextException
*Since: 0.5.0*
When a promise is created using [`QtPromise::connect()`](../helpers/connect.md), this exception is thrown when the `sender` object is destroyed, for example:
```cpp
auto promise = QtPromise::connect(sender, &Object::finished, &Object::error);
promise.fail([](const QPromiseContextException&) {
// 'sender' has been destroyed.
})
```

View File

@ -0,0 +1,17 @@
---
title: QPromiseTimeoutException
---
# QPromiseTimeoutException
*Since: 0.2.0*
This is the default exception thrown when reaching the time limit when using the [`QPromise::timeout()`](../qpromise/timeout.md) method, for example:
```cpp
QPromise<int> input = {...}
auto output = input.timeout(2000)
.fail([](const QPromiseTimeoutException& e) {
// operation timed out after 2s!
});
```

View File

@ -0,0 +1,25 @@
---
title: QPromiseUndefinedException
---
# QPromiseUndefinedException
*Since: 0.5.0*
This exception is thrown when rejecting a promise with no explicit reason, for example:
```cpp
QPromise<int> promise([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>& reject) {
async_method([=](bool success, int result) {
if (success) {
resolve(result);
} else {
reject();
}
});
});
promise.fail([](const QPromiseUndefinedException&) {
// promise rejected without reason!
})
```

View File

@ -0,0 +1,95 @@
# Getting Started
## Installation
QtPromise is a [header-only](https://en.wikipedia.org/wiki/Header-only) library, simply download the [latest release](https://github.com/simonbrunel/qtpromise/releases/latest) (or [`git submodule`](https://git-scm.com/docs/git-submodule)) and include `qtpromise.pri` from your project `.pro`.
### qpm
Alternatively and **only** if your project relies on [qpm](https://www.qpm.io/), you can install QtPromise as follow:
```bash
qpm install com.github.simonbrunel.qtpromise
```
## Usage
The recommended way to use QtPromise is to include the single module header:
```cpp
#include <QtPromise>
```
## Example
Let's first make the code more readable by using the library namespace:
```cpp
using namespace QtPromise;
```
This `download` function creates a [promise from callbacks](qpromise/constructor.md) which will be resolved when the network request is finished:
```cpp
QPromise<QByteArray> download(const QUrl& url)
{
return QPromise<QByteArray>([&](
const QPromiseResolve<QByteArray>& resolve,
const QPromiseReject<QByteArray>& reject) {
QNetworkReply* reply = manager->get(QNetworkRequest(url));
QObject::connect(reply, &QNetworkReply::finished, [=]() {
if (reply->error() == QNetworkReply::NoError) {
resolve(reply->readAll());
} else {
reject(reply->error());
}
reply->deleteLater();
});
});
}
```
The following method `uncompress` data in a separate thread and returns a [promise from QFuture](qtconcurrent.md):
```cpp
QPromise<Entries> uncompress(const QByteArray& data)
{
return QtPromise::resolve(QtConcurrent::run([](const QByteArray& data) {
Entries entries;
// {...} uncompress data and parse content.
if (error) {
throw MalformedException();
}
return entries;
}, data));
}
```
It's then easy to chain the whole asynchronous process using promises:
- initiate the promise chain by downloading a specific URL,
- [`then`](qpromise/then.md) *and only if download succeeded*, uncompress received data,
- [`then`](qpromise/then.md) validate and process the uncompressed entries,
- [`finally`](qpromise/finally.md) perform operations whatever the process succeeded or failed,
- and handle specific errors using [`fail`](qpromise/fail.md).
```cpp
download(url).then(&uncompress).then([](const Entries& entries) {
if (entries.isEmpty()) {
throw UpdateException("No entries");
}
// {...} process entries
}).finally([]() {
// {...} cleanup
}).fail([](QNetworkReply::NetworkError err) {
// {...} handle network error
}).fail([](const UpdateException& err) {
// {...} handle update error
}).fail([]() {
// {...} catch all
});
```

View File

@ -0,0 +1,33 @@
---
title: all
---
# QtPromise::all
*Since: 0.5.0*
```
QtPromise::all(Sequence<QPromise<T>> promises) -> QPromise<QVector<T>>
QtPromise::all(Sequence<QPromise<void>> promises) -> QPromise<void>
```
Returns a `QPromise<QVector<T>>` (or `QPromise<void>`) that fulfills when **all** `promises` of (the same) type `T` have been fulfilled. The `output` value is a vector containing all the values of `promises`, in the same order, i.e., at the respective positions to the original sequence, regardless of completion order.
If any of the given `promises` fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
`Sequence` is any STL compatible container (eg. `QVector`, `QList`, `std::vector`, etc.)
```cpp
QVector<QPromise<QByteArray> > promises{
download(QUrl("http://a...")),
download(QUrl("http://b...")),
download(QUrl("http://c..."))
};
auto output = QtPromise::all(promises);
// output type: QPromise<QVector<QByteArray>>
output.then([](const QVector<QByteArray>& res) {
// {...}
});
```

View File

@ -0,0 +1,42 @@
---
title: attempt
---
# QtPromise::attempt
*Since: 0.4.0*
```cpp
QtPromise::attempt(Functor functor, Args...) -> QPromise<R>
// With:
// - Functor: Function(Args...) -> R | QPromise<R>
```
Calls `functor` immediately and returns a promise fulfilled with the value returned by `functor`. Any synchronous exceptions will be turned into rejections on the returned promise. This is a convenient method that can be used instead of handling both synchronous and asynchronous exception flows.
The type `R` of the `output` promise depends on the type returned by the `functor` function. If `functor` returns a promise (or `QFuture`), the `output` promise is delayed and will be resolved by the returned promise.
```cpp
QPromise<QByteArray> download(const QUrl& url);
QPromise<QByteArray> process(const QUrl& url)
{
return QtPromise::attempt([&]() {
if (!url.isValid()) {
throw InvalidUrlException();
}
return download(url);
}
}
auto output = process(url);
// 'output' type: QPromise<QByteArray>
output.then([](const QByteArray& res) {
// {...}
}).fail([](const InvalidUrlException& err) {
// {...}
});
```

View File

@ -0,0 +1,48 @@
---
title: connect
---
# QtPromise::connect
*Since: 0.5.0*
```cpp
(1) QtPromise::connect(QObject* sender, Signal(T) resolver) -> QPromise<T>
(2) QtPromise::connect(QObject* sender, Signal(T) resolver, Signal(R) rejecter) -> QPromise<T>
(3) QtPromise::connect(QObject* sender, Signal(T) resolver, QObject* sender2, Signal(R) rejecter) -> QPromise<T>
```
Creates a `QPromise<T>` that will be fulfilled with the `resolver` signal's first argument, or a `QPromise<void>` if `resolver` doesn't provide any argument.
The second `(2)` and third `(3)` variants of this method will reject the `output` promise when the `rejecter` signal is emitted. The rejection reason is the value of the `rejecter` signal's first argument or [`QPromiseUndefinedException`](../exceptions/undefined) if `rejected` doesn't provide any argument.
Additionally, the `output` promise will be automatically rejected with [`QPromiseContextException`](../exceptions/context.md) if `sender` is destroyed before the promise is resolved (that doesn't apply to `sender2`).
```cpp
class Sender : public QObject
{
Q_SIGNALS:
void finished(const QByteArray&);
void error(ErrorCode);
};
auto sender = new Sender();
auto output = QtPromise::connect(sender, &Sender::finished, &Sender::error);
// 'output' resolves as soon as one of the following events happens:
// - the 'sender' object is destroyed, the promise is rejected
// - the 'finished' signal is emitted, the promise is fulfilled
// - the 'error' signal is emitted, the promise is rejected
// 'output' type: QPromise<QByteArray>
output.then([](const QByteArray& res) {
// 'res' is the first argument of the 'finished' signal.
}).fail([](ErrorCode err) {
// 'err' is the first argument of the 'error' signal.
}).fail([](const QPromiseContextException& err) {
// the 'sender' object has been destroyed before any of
// the 'finished' or 'error' signals have been emitted.
});
```
See also the [`Qt Signals`](../qtsignals.md) section for more examples.

View File

@ -0,0 +1,41 @@
---
title: each
---
# QtPromise::each
*Since: 0.4.0*
```cpp
QtPromise::each(Sequence<T> values, Functor functor) -> QPromise<Sequence<T>>
// With:
// - Sequence: STL compatible container (e.g. QVector, etc.)
// - Functor: Function(T value, int index) -> void | QPromise<void>
```
Calls the given `functor` on each element in `values` then resolves to the original sequence unmodified. If `functor` throws, `output` is rejected with the new exception.
If `functor` returns a promise (or `QFuture`), the `output` promise is delayed until all the promises are resolved. If any of the promises fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
```cpp
auto output = QtPromise::each(QVector<QUrl>{
QUrl("http://a..."),
QUrl("http://b..."),
QUrl("http://c...")
}, [](const QUrl& url, ...) {
return QPromise<void>([&](auto resolve, auto reject) {
// process url asynchronously ...
})
});
// `output` resolves as soon as all promises returned by
// `functor` are fulfilled or at least one is rejected.
// output type: QPromise<QVector<QUrl>>
output.then([](const QVector<QUrl>& res) {
// 'res' contains the original values
});
```
See also: [`QPromise<T>::each`](../qpromise/each.md)

View File

@ -0,0 +1,46 @@
---
title: filter
---
# QtPromise::filter
*Since: 0.4.0*
```cpp
QtPromise::filter(Sequence<T> values, Filterer filterer) -> QPromise<Sequence<T>>
// With:
// - Sequence: STL compatible container (e.g. QVector, etc.)
// - Filterer: Function(T value, int index) -> bool
```
Iterates over `values` and [filters the sequence](https://en.wikipedia.org/wiki/Filter_%28higher-order_function%29) to another using the given `filterer` function. If `filterer` returns `true`, a copy of the item is put in the `output` sequence, otherwise, the item will not appear in `output`. If `filterer` throws, `output` is rejected with the new exception.
If `filterer` returns a promise (or `QFuture`), the `output` promise is delayed until all the promises are resolved. If any of the promises fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
```cpp
auto output = QtPromise::filter(QVector{
QUrl("http://a..."),
QUrl("http://b..."),
QUrl("http://c...")
}, [](const QUrl& url, ...) {
return QPromise<bool>([&](auto resolve, auto reject) {
// resolve(true) if 'url' is reachable, else resolve(false)
// {...}
});
});
// 'output' resolves as soon as all promises returned by
// 'filterer' are fulfilled or at least one is rejected.
// 'output' type: QPromise<QVector<QUrl>>
output.then([](const QVector<QUrl>& res) {
// 'res' contains only reachable URLs
});
```
::: tip NOTE
The order of the output sequence values is guarantee to be the same as the original sequence, regardless of completion order of the promises returned by `filterer`.
:::
See also: [`QPromise<T>::filter`](../qpromise/filter.md)

View File

@ -0,0 +1,46 @@
---
title: map
---
# QtPromise::map
*Since: 0.4.0*
```cpp
QtPromise::map(Sequence<T> values, Mapper mapper) -> QPromise<QVector<R>>
// With:
// - Sequence: STL compatible container (e.g. QVector, etc.)
// - Mapper: Function(T value, int index) -> R | QPromise<R>
```
Iterates over `values` and [maps the sequence](https://en.wikipedia.org/wiki/Map_%28higher-order_function%29) to another using the given `mapper` function. The type returned by `mapper` determines the type of the `output` promise. If `mapper` throws, `output` is rejected with the new exception.
If `mapper` returns a promise (or `QFuture`), the `output` promise is delayed until all the promises are resolved. If any of the promises fails, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
```cpp
auto output = QtPromise::map(QVector{
QUrl("http://a..."),
QUrl("http://b..."),
QUrl("http://c...")
}, [](const QUrl& url, ...) {
return QPromise<QByteArray>([&](auto resolve, auto reject) {
// download content at url and resolve
// {...}
});
});
// 'output' resolves as soon as all promises returned by
// 'mapper' are fulfilled or at least one is rejected.
// 'output' type: QPromise<QVector<QByteArray>>
output.then([](const QVector<QByteArray>& res) {
// {...}
});
```
::: tip NOTE
The order of the output sequence values is guarantee to be the same as the original sequence, regardless of completion order of the promises returned by `mapper`.
:::
See also: [`QPromise<T>::map`](../qpromise/map.md)

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,21 @@
---
title: resolve
---
# QtPromise::resolve
*Since: 0.5.0*
```
QtPromise::resolve(T value) -> QPromise<R>
```
Similar to the [`QPromise<T>::resolve`](../qpromise/resolve.md) static method, creates a promise resolved from a given `value` but without the extra typing:
```cpp
auto promise = QtPromise::resolve(); // QPromise<void>
auto promise = QtPromise::resolve(42); // QPromise<int>
auto promise = QtPromise::resolve(QString("foo")); // QPromise<QString>
```
This method also allows to convert `QFuture<T>` to `QPromise<T>`, delayed until the `QFuture` is finished ([read more](../qtconcurrent.md#convert)).

View File

@ -0,0 +1,34 @@
---
title: ::all [static]
---
# QPromise::all [static]
*Since: 0.1.0*
```
[static] QPromise<T>::all(Sequence<QPromise<T>> promises) -> QPromise<QVector<T>>
```
Returns a `QPromise<QVector<T>>` that fulfills when **all** `promises` of (the same) type `T` have been fulfilled. The `output` value is a vector containing all the values of `promises`, in the same order, i.e., at the respective positions to the original sequence, regardless of completion order.
If any of the given `promises` fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
`Sequence` is any STL compatible container (eg. `QVector`, `QList`, `std::vector`, etc.)
```cpp
QVector<QPromise<QByteArray> > promises{
download(QUrl("http://a...")),
download(QUrl("http://b...")),
download(QUrl("http://c..."))
};
auto output = QPromise<QByteArray>::all(promises);
// output type: QPromise<QVector<QByteArray>>
output.then([](const QVector<QByteArray>& res) {
// {...}
});
```
See also: [`QtPromise::all`](../helpers/all.md)

View File

@ -0,0 +1,67 @@
---
title: constructor
---
# QPromise::QPromise
*Since: 0.1.0*
```cpp
QPromise<T>::QPromise(Function resolver)
```
Creates a new promise that will be fulfilled or rejected by the given `resolver` lambda:
```cpp
QPromise<int> promise([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>& reject) {
async_method([=](bool success, int result) {
if (success) {
resolve(result);
} else {
reject(customException());
}
});
});
```
::: tip NOTE
`QPromise<void>` is specialized to not contain any value, meaning that the `resolve` callback takes no argument.
:::
**C++14**
```cpp
QPromise<int> promise([](const auto& resolve, const auto& reject) {
// {...}
});
```
**Undefined rejection reason**
*Since: 0.5.0*
While not recommended because it makes tracking errors more difficult, it's also possible to reject a promise without explicit reason, in which case, a built-in [`QPromiseUndefinedException`](../exceptions/undefined.md) is thrown:
```cpp
QPromise<int> promise([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>& reject) {
async_method([=](bool success, int result) {
if (success) {
resolve(result);
} else {
reject();
}
});
});
```
```cpp
// The exception can be caught explicitly
promise.fail([](const QPromiseUndefinedException&) {
// { ... }
})
// ... or implicitly (since undefined)
promise.fail([]() {
// { ... }
})
```

View File

@ -0,0 +1,20 @@
---
title: .delay
---
# QPromise::delay
*Since: 0.2.0*
```cpp
QPromise<T>::delay(int msec) -> QPromise<T>
```
This method returns a promise that will be fulfilled with the same value as the `input` promise and after at least `msec` milliseconds. If the `input` promise is rejected, the `output` promise is immediately rejected with the same reason.
```cpp
QPromise<int> input = {...}
auto output = input.delay(2000).then([](int res) {
// called 2 seconds after `input` is fulfilled
});
```

View File

@ -0,0 +1,56 @@
---
title: .each
---
# QPromise::each
*Since: 0.4.0*
```cpp
QPromise<Sequence<T>>::each(Functor functor) -> QPromise<Sequence<T>>
// With:
// - Sequence: STL compatible container
// - Functor: Function(T value, int index) -> any
```
::: warning IMPORTANT
This method only applies to promise with sequence value.
:::
Calls the given `functor` on each element in the promise value (i.e. `Sequence<T>`), then resolves to the original sequence unmodified. If `functor` throws, `output` is rejected with the new exception.
```cpp
QPromise<QList<QByteArray>> input = {...}
auto output = input.each([](const QByteArray& value, int index) {
// process value ...
});
// output type: QPromise<QList<QByteArray>>
output.then([](const QList<QByteArray>& res) {
// 'res' contains the original values
});
```
If `functor` returns a promise (or `QFuture`), the `output` promise is delayed until all the promises are resolved. If any of the promises fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
```cpp
QPromise<QList<QUrl>> input = {...}
auto output = input.each([](const QUrl& url, ...) {
return QPromise<void>([&](auto resolve, auto reject) {
// process url asynchronously ...
})
});
// `output` resolves as soon as all promises returned by
// `functor` are fulfilled or at least one is rejected.
// output type: QPromise<QList<QUrl>>
output.then([](const QList<QUrl>& res) {
// 'res' contains the original values
});
```
See also: [`QtPromise::each`](../helpers/each.md)

View File

@ -0,0 +1,25 @@
---
title: .fail
---
# QPromise::fail
*Since: 0.1.0*
```cpp
QPromise<T>::fail(Function onRejected) -> QPromise<T>
```
Shorthand to `promise.then(nullptr, onRejected)`, similar to the [`catch` statement](http://en.cppreference.com/w/cpp/language/try_catch):
```cpp
promise.fail([](const MyException&) {
// {...}
}).fail(const QException&) {
// {...}
}).fail(const std::exception&) {
// {...}
}).fail() {
// {...} catch-all
});
```

View File

@ -0,0 +1,49 @@
---
title: .filter
---
# QPromise::filter
*Since: 0.4.0*
```cpp
QPromise<Sequence<T>>::filter(Filter filterer) -> QPromise<Sequence<T>>
// With:
// - Sequence: STL compatible container (e.g. QVector, etc.)
// - Filterer: Function(T value, int index) -> bool
```
::: warning IMPORTANT
This method only applies to promise with sequence value.
:::
Iterates over all the promise values (i.e. `Sequence<T>`) and [filters the sequence](https://en.wikipedia.org/wiki/Filter_%28higher-order_function%29) to another using the given `filterer` function. If `filterer` returns `true`, a copy of the item is put in the `output` sequence, otherwise, the item will not appear in `output`. If `filterer` throws, `output` is rejected with the new exception.
If `filterer` returns a promise (or `QFuture`), the `output` promise is delayed until all the promises are resolved. If any of the promises fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
```cpp
QPromise<QList<QUrl>> input = {...}
auto output = input.filter([](const QUrl& url, ...) {
return url.isValid(); // Keep only valid URLs
}).filter([](const QUrl& url, ...) {
return QPromise<bool>([&](auto resolve, auto reject) {
// resolve(true) if `url` is reachable, else resolve(false)
});
});
// 'output' resolves as soon as all promises returned by
// 'filterer' are fulfilled or at least one is rejected.
// 'output' type: QPromise<QList<QUrl>>
output.then([](const QList<QUrl>& res) {
// 'res' contains only reachable URLs
});
```
::: tip NOTE
The order of the output sequence values is guarantee to be the same as the original sequence, regardless of completion order of the promises returned by `filterer`.
:::
See also: [`QtPromise::filter`](../helpers/filter.md)

View File

@ -0,0 +1,21 @@
---
title: .finally
---
# QPromise::finally
*Since: 0.1.0*
```cpp
QPromise<T>::finally(Function handler) -> QPromise<T>
```
This `handler` is **always** called, without any argument and whatever the `input` promise state (fulfilled or rejected). The `output` promise has the same type as the `input` one but also the same value or error. The finally `handler` **can not modify the fulfilled value** (the returned value is ignored), however, if `handler` throws, `output` is rejected with the new exception.
```cpp
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.

View File

@ -0,0 +1,13 @@
---
title: .isFulfilled
---
# QPromise::isFulfilled
*Since: 0.1.0*
```cpp
QPromise<T>::isFulfilled() -> bool
```
Returns `true` if the promise is fulfilled, otherwise returns `false`.

View File

@ -0,0 +1,13 @@
---
title: .isPending
---
# QPromise::isPending
*Since: 0.1.0*
```cpp
QPromise<T>::isPending() -> bool
```
Returns `true` if the promise is pending (not fulfilled or rejected), otherwise returns `false`.

View File

@ -0,0 +1,14 @@
---
title: .isRejected
---
# QPromise::isRejected
*Since: 0.1.0*
```cpp
QPromise<T>::isRejected() -> bool
```
Returns `true` if the promise is rejected, otherwise returns `false`.

View File

@ -0,0 +1,62 @@
---
title: .map
---
# QPromise::map
*Since: 0.4.0*
```cpp
QPromise<Sequence<T>>::map(Mapper mapper) -> QPromise<QVector<R>>
// With:
// - Sequence: STL compatible container (e.g. QVector, etc.)
// - Mapper: Function(T value, 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 [maps the sequence](https://en.wikipedia.org/wiki/Map_%28higher-order_function%29) to another using the given `mapper` function. The type returned by `mapper` determines the type of the `output` promise. If `mapper` throws, `output` is rejected with the new exception.
If `mapper` returns a promise (or `QFuture`), the `output` promise is delayed until all the promises are resolved. If any of the promises fails, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
```cpp
QPromise<QList<QUrl>> input = {...}
auto output = input.map([](const QUrl& url, int index) {
return QPromise<QByteArray>([&](auto resolve, auto reject) {
// download content at 'url' and resolve
// {...}
});
}).map([](const QByteArray& value, ...) {
// process the downloaded QByteArray
// {...}
return DownloadResult(value);
});
// 'output' resolves as soon as all promises returned by
// 'mapper' are fulfilled or at least one is rejected.
// 'output' type: QPromise<QVector<DownloadResult>>
output.then([](const QVector<DownloadResult>& res) {
// {...}
});
```
::: tip NOTE
The order of the output sequence values is guarantee to be the same as the original sequence, regardless of completion order of the promises returned by `mapper`.
:::
This function is provided for convenience and is similar to:
```cpp
promise.then([](const Sequence<T>& values) {
return QtPromise::map(values, [](const T& value, int index) {
return // {...}
});
});
```
See also: [`QtPromise::map`](../helpers/map.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

@ -0,0 +1,26 @@
---
title: ::reject [static]
---
# QPromise::reject [static]
*Since: 0.1.0*
```cpp
[static] QPromise<T>::reject(any reason) -> QPromise<T>
```
Creates a `QPromise<T>` that is rejected with the given `reason` of *whatever type*:
```cpp
QPromise<int> compute(const QString& type)
{
if (type == "foobar") {
return QPromise<int>::reject(QString("Unknown type: %1").arg(type));
}
return QPromise<int>([](const QPromiseResolve<int>& resolve) {
// {...}
});
}
```

View File

@ -0,0 +1,28 @@
---
title: ::resolve [static]
---
# QPromise::resolve [static]
*Since: 0.1.0*
```
[static] QPromise<T>::resolve(T value) -> QPromise<T>
```
Creates a `QPromise<T>` that is fulfilled with the given `value` of type `T`:
```cpp
QPromise<int> compute(const QString& type)
{
if (type == "magic") {
return QPromise<int>::resolve(42);
}
return QPromise<int>([](const QPromiseResolve<int>& resolve) {
// {...}
});
}
```
See also: [`QtPromise::resolve`](../helpers/resolve.md)

View File

@ -0,0 +1,24 @@
---
title: .tap
---
# QPromise::tap
*Since: 0.2.0*
```cpp
QPromise<T>::tap(Function 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`](finally.md), this handler is **not** called for rejections.
```cpp
QPromise<int> input = {...}
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.

View File

@ -0,0 +1,27 @@
---
title: .tapFail
---
# QPromise::tapFail
*Since: 0.4.0*
```cpp
QPromise<T>::tapFail(Function handler) -> QPromise<T>
```
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<int> 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.

View File

@ -0,0 +1,95 @@
---
title: .then
---
# QPromise::then
*Since: 0.1.0*
```cpp
QPromise<T>::then(Function onFulfilled, Function onRejected) -> QPromise<R>
QPromise<T>::then(Function onFulfilled) -> QPromise<R>
```
See [Promises/A+ `.then`](https://promisesaplus.com/#the-then-method) for details.
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
// called with the 'input' result if fulfilled
}, [](const ReasonType& reason) {
// called with the 'input' reason if rejected
// see QPromise<T>::fail for details
});
```
::: tip NOTE
`onRejected` handler is optional, in which case `output` will be rejected with the same reason as `input`. Also note that it's recommended to use the [`fail`](fail.md) shorthand to handle errors.
:::
The type `<R>` of the `output` promise depends on the return type of the `onFulfilled` handler:
```cpp
QPromise<int> input = {...}
auto output = input.then([](int res) {
return QString::number(res); // -> QPromise<QString>
});
// output type: QPromise<QString>
output.then([](const QString& res) {
// {...}
});
```
::: tip NOTE
Only `onFulfilled` can change the promise type, `onRejected` **must** return the same type as `onFulfilled`. That also means if `onFulfilled` is `nullptr`, `onRejected` must return the same type as the `input` promise.
:::
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
return res + 4;
}, [](const ReasonType& reason) {
return -1;
});
```
If `onFulfilled` doesn't return any value, the `output` type is `QPromise<void>`:
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
// {...}
});
// output type: QPromise<void>
output.then([]() {
// `QPromise<void>` `onFulfilled` handler has no argument
});
```
You can also decide to skip the promise result by omitting the handler argument:
```cpp
QPromise<int> input = {...}
auto output = input.then([]( /* skip int result */ ) {
// {...}
});
```
The `output` promise can be *rejected* by throwing an exception in either `onFulfilled` or `onRejected`:
```cpp
QPromise<int> input = {...}
auto output = input.then([](int res) {
if (res == -1) {
throw ReasonType();
} else {
return res;
}
});
// output.isRejected() is true
```
If an handler returns a promise (or QFuture), the `output` promise is delayed and will be resolved by the returned promise.

View File

@ -0,0 +1,24 @@
---
title: .timeout
---
# QPromise::timeout
*Since: 0.2.0*
```cpp
QPromise<T>::timeout(int msec, any error = QPromiseTimeoutException) -> QPromise<T>
```
This method returns a promise that will be resolved with the `input` promise's fulfillment value or rejection reason. However, if the `input` promise is not fulfilled or rejected within `msec` milliseconds, the `output` promise is rejected with `error` as the reason ([`QPromiseTimeoutException`](../exceptions/timeout.md) by default).
```cpp
QPromise<int> input = {...}
auto output = input.timeout(2000)
.then([](int res) {
// operation succeeded within 2 seconds
})
.fail([](const QPromiseTimeoutException& e) {
// operation timed out!
});
```

View File

@ -0,0 +1,29 @@
---
title: .wait
---
# QPromise::wait
*Since: 0.1.0*
```cpp
QPromise<T>::wait() -> QPromise<T>
```
This method holds the execution of the remaining code until the `input` promise is resolved (either fulfilled or rejected), **without** blocking the event loop of the current thread:
```cpp
int result = -1;
QPromise<int> input = QtPromise::resolve(QtConcurrent::run([]() {
return 42;
})).tap([&](int res) {
result = res;
});
// input.isPending() is true && result is -1
input.wait();
// input.isPending() is false && result is 42
```

View File

@ -0,0 +1,66 @@
# Qt Concurrent
QtPromise integrates with [QtConcurrent](https://doc.qt.io/qt-5/qtconcurrent-index.html) to make easy chaining QFuture with QPromise.
## <a name="qtconcurrent-convert"></a> Convert
Converting `QFuture<T>` to `QPromise<T>` is done using the [`QtPromise::resolve`](helpers/resolve.md) helper:
```cpp
QFuture<int> future = QtConcurrent::run([]() {
// {...}
return 42;
});
QPromise<int> promise = QtPromise::resolve(future);
```
or simply:
```cpp
auto promise = QtPromise::resolve(QtConcurrent::run([]() {
// {...}
}));
```
## Chain
Returning a `QFuture<T>` in [`then`](qpromise/then.md) or [`fail`](qpromise/fail.md) automatically translate to `QPromise<T>`:
```cpp
QPromise<int> input = ...
auto output = input.then([](int res) {
return QtConcurrent::run([]() {
// {...}
return QString("42");
});
});
// output type: QPromise<QString>
output.then([](const QString& res) {
// {...}
});
```
The `output` promise is resolved when the `QFuture` is [finished](https://doc.qt.io/qt-5/qfuture.html#isFinished).
## 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 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 = ...
promise.then([](int res) {
return QtConcurrent::run([]() {
// {...}
if (!success) {
throw CustomException();
}
return QString("42");
});
}).fail(const CustomException& err) {
// {...}
});
```

View File

@ -0,0 +1,89 @@
# Qt Signals
QtPromise supports creating promises that are resolved or rejected by regular [Qt signals](https://doc.qt.io/qt-5/signalsandslots.html).
::: warning IMPORTANT
A promise connected to a signal will be resolved (fulfilled or rejected) **only one time**, no matter if the signals are emitted multiple times. Internally, the promise is disconnected from all signals as soon as one signal is emitted.
:::
## Resolve Signal
The [`QtPromise::connect()`](helpers/connect.md) helper allows to create a promise resolved from a single signal:
```cpp
// [signal] Object::finished(const QByteArray&)
auto output = QtPromise::connect(obj, &Object::finished);
// output type: QPromise<QByteArray>
output.then([](const QByteArray& data) {
// {...}
});
```
If the signal doesn't provide any argument, a `QPromise<void>` is returned:
```cpp
// [signal] Object::done()
auto output = QtPromise::connect(obj, &Object::done);
// output type: QPromise<void>
output.then([]() {
// {...}
});
```
::: tip NOTE
QtPromise currently only supports single argument signals, which means that only the first argument is used to fulfill or reject the connected promise, other arguments being ignored.
:::
## Reject Signal
The [`QtPromise::connect()`](helpers/connect.md) helper also allows to reject the promise from another signal:
```cpp
// [signal] Object::finished(const QByteArray& data)
// [signal] Object::error(ObjectError error)
auto output = QtPromise::connect(obj, &Object::finished, &Object::error);
// output type: QPromise<QByteArray>
output.then([](const QByteArray& data) {
// {...}
}).fail(const ObjectError& error) {
// {...}
});
```
If the rejection signal doesn't provide any argument, the promise will be rejected
with [`QPromiseUndefinedException`](../exceptions/undefined), for example:
```cpp
// [signal] Object::finished()
// [signal] Object::error()
auto output = QtPromise::connect(obj, &Object::finished, &Object::error);
// output type: QPromise<QByteArray>
output.then([]() {
// {...}
}).fail(const QPromiseUndefinedException& error) {
// {...}
});
```
A third variant allows to connect the resolve and reject signals from different objects:
```cpp
// [signal] ObjectA::finished(const QByteArray& data)
// [signal] ObjectB::error(ObjectBError error)
auto output = QtPromise::connect(objA, &ObjectA::finished, objB, &ObjectB::error);
// output type: QPromise<QByteArray>
output.then([](const QByteArray& data) {
// {...}
}).fail(const ObjectBError& error) {
// {...}
});
```
Additionally to the rejection signal, promises created using [`QtPromise::connect()`](helpers/connect.md) are automatically rejected with [`QPromiseContextException`](exceptions/context.md) if the sender is destroyed before fulfilling the promise.
See [`QtPromise::connect()`](helpers/connect.md) for more details.

View File

@ -0,0 +1,7 @@
# Thread-Safety
QPromise is thread-safe and can be copied and accessed across different threads. QPromise relies on [explicitly data sharing](https://doc.qt.io/qt-5/qexplicitlyshareddatapointer.html#details) and thus `auto p2 = p1` represents the same promise: when `p1` resolves, handlers registered on `p1` and `p2` are called, the fulfilled value being shared between both instances.
::: warning IMPORTANT
While it's safe to access the resolved value from different threads using [`then`](qpromise/then.md), QPromise provides no guarantee about the object being pointed to. Thread-safety and reentrancy rules for that object still apply.
:::

View File

@ -2,7 +2,8 @@
#define QTPROMISE_MODULE_H
#include "../src/qtpromise/qpromise.h"
#include "../src/qtpromise/qpromiseconnections.h"
#include "../src/qtpromise/qpromisefuture.h"
#include "../src/qtpromise/qpromisehelpers.h"
#endif // ifndef QTPROMISE_MODULE_H
#endif // QTPROMISE_MODULE_H

View File

@ -10,9 +10,9 @@
"url": "https://github.com/simonbrunel/qtpromise.git"
},
"version": {
"label": "0.1.0"
"label": "0.5.0"
},
"license": "MIT",
"pri_filename": "qtpromise.pri",
"webpage": "https://github.com/simonbrunel/qtpromise"
"webpage": "https://qtpromise.netlify.com"
}

View File

@ -1,9 +1,10 @@
#ifndef QTPROMISE_QPROMISE_H
#define QTPROMISE_QPROMISE_H
// QtPromise
#include "qpromise_p.h"
#include "qpromiseexceptions.h"
#include "qpromiseglobal.h"
#include "qpromiseresolver.h"
// Qt
#include <QExplicitlySharedDataPointer>
@ -16,57 +17,105 @@ class QPromiseBase
public:
using Type = T;
QPromiseBase(const QPromiseBase<T>& other): m_d(other.m_d) {}
QPromiseBase(const QPromise<T>& other): m_d(other.m_d) {}
QPromiseBase(QPromiseBase<T>&& other) { swap(other); }
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type = 0>
inline QPromiseBase(F resolver);
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type = 0>
inline QPromiseBase(F resolver);
QPromiseBase(const QPromiseBase<T>& other): m_d(other.m_d) {}
QPromiseBase(const QPromise<T>& other): m_d(other.m_d) {}
QPromiseBase(QPromiseBase<T>&& other) Q_DECL_NOEXCEPT { swap(other); }
virtual ~QPromiseBase() { }
QPromiseBase<T>& operator=(const QPromiseBase<T>& other) { m_d = other.m_d; return *this;}
QPromiseBase<T>& operator=(QPromiseBase<T>&& other) Q_DECL_NOEXCEPT
{ QPromiseBase<T>(std::move(other)).swap(*this); return *this; }
bool operator==(const QPromiseBase<T>& other) const { return (m_d == other.m_d); }
bool operator!=(const QPromiseBase<T>& other) const { return (m_d != other.m_d); }
void swap(QPromiseBase<T>& other) Q_DECL_NOEXCEPT { qSwap(m_d, other.m_d); }
bool isFulfilled() const { return m_d->isFulfilled(); }
bool isRejected() const { return m_d->isRejected(); }
bool isPending() const { return m_d->isPending(); }
template <typename TFulfilled, typename TRejected = std::nullptr_t>
template <typename TFulfilled, typename TRejected>
inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
then(const TFulfilled& fulfilled, const TRejected& rejected = nullptr) const;
then(const TFulfilled& fulfilled, const TRejected& rejected) const;
template <typename TFulfilled>
inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
then(TFulfilled&& fulfilled) const;
template <typename TRejected>
inline typename QtPromisePrivate::PromiseHandler<T, std::nullptr_t>::Promise
fail(TRejected&& rejected) const;
inline QPromise<T> wait() const;
template <typename THandler>
inline QPromise<T> finally(THandler handler) const;
void swap(QPromiseBase<T>& other) { qSwap(m_d, other.m_d); }
template <typename THandler>
inline QPromise<T> tap(THandler handler) const;
template <typename THandler>
inline QPromise<T> tapFail(THandler handler) const;
template <typename E = QPromiseTimeoutException>
inline QPromise<T> timeout(int msec, E&& error = E()) const;
inline QPromise<T> delay(int msec) const;
inline QPromise<T> wait() const;
public: // STATIC
template <typename E>
inline static QPromise<T> reject(E&& error);
protected:
friend class QPromiseResolve<T>;
friend class QPromiseReject<T>;
friend struct QtPromisePrivate::PromiseFulfill<QPromise<T>>;
friend class QtPromisePrivate::PromiseResolver<T>;
friend struct QtPromisePrivate::PromiseInspect;
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T> > m_d;
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T>> m_d;
};
template <typename T>
class QPromise: public QPromiseBase<T>
class QPromise : public QPromiseBase<T>
{
public:
template <typename F>
QPromise(F&& resolver): QPromiseBase<T>(std::forward<F>(resolver)) { }
template <typename THandler>
inline QPromise<T> finally(THandler handler) const;
template <typename Functor>
inline QPromise<T>
each(Functor fn);
template <typename Functor>
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
inline static QPromise<QVector<T> > all(const QVector<QPromise<T> >& promises);
// DEPRECATED (remove at version 1)
template <template <typename, typename...> class Sequence = QVector, typename ...Args>
Q_DECL_DEPRECATED_X("Use QtPromise::all instead") static inline QPromise<QVector<T>>
all(const Sequence<QPromise<T>, Args...>& promises);
inline static QPromise<T> resolve(const T& value);
inline static QPromise<T> resolve(T&& value);
private:
@ -74,17 +123,19 @@ private:
};
template <>
class QPromise<void>: public QPromiseBase<void>
class QPromise<void> : public QPromiseBase<void>
{
public:
template <typename F>
QPromise(F&& resolver): QPromiseBase<void>(std::forward<F>(resolver)) { }
template <typename THandler>
inline QPromise<void> finally(THandler handler) const;
public: // STATIC
inline static QPromise<void> all(const QVector<QPromise<void> >& promises);
// DEPRECATED (remove at version 1)
template <template <typename, typename...> class Sequence = QVector, typename ...Args>
Q_DECL_DEPRECATED_X("Use QtPromise::all instead") static inline QPromise<void>
all(const Sequence<QPromise<void>, Args...>& promises);
inline static QPromise<void> resolve();
private:
@ -95,4 +146,4 @@ private:
#include "qpromise.inl"
#endif // ifndef QTPROMISE_QPROMISE_H
#endif // QTPROMISE_QPROMISE_H

View File

@ -1,111 +1,38 @@
#include "qpromise.h"
#include "qpromisehelpers.h"
// Qt
#include <QCoreApplication>
#include <QSharedPointer>
#include <QTimer>
namespace QtPromise {
template <class T>
class QPromiseResolve
{
public:
QPromiseResolve(QPromise<T> p)
: m_promise(new QPromise<T>(std::move(p)))
{ }
void operator()(const T& value) const
{
resolve(value);
}
void operator()(T&& value) const
{
resolve(std::move(value));
}
private:
QSharedPointer<QPromise<T> > m_promise;
template <typename U>
void resolve(U&& value) const
{
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->resolve(std::forward<U>(value));
m_promise->m_d->dispatch();
}
}
};
template <>
class QPromiseResolve<void>
{
public:
QPromiseResolve(QPromise<void> p)
: m_promise(new QPromise<void>(std::move(p)))
{ }
void operator()() const
{
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->resolve();
m_promise->m_d->dispatch();
}
}
private:
QSharedPointer<QPromise<void> > m_promise;
};
template <class T>
class QPromiseReject
{
public:
QPromiseReject(QPromise<T> p)
: m_promise(new QPromise<T>(std::move(p)))
{ }
template <typename E>
void operator()(E&& error) const
{
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->reject(std::forward<E>(error));
m_promise->m_d->dispatch();
}
}
private:
QSharedPointer<QPromise<T> > m_promise;
};
template <typename T>
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F resolver)
inline QPromiseBase<T>::QPromiseBase(F callback)
: m_d(new QtPromisePrivate::PromiseData<T>())
{
QPromiseResolve<T> resolve(*this);
QPromiseReject<T> reject(*this);
QtPromisePrivate::PromiseResolver<T> resolver(*this);
try {
resolver(resolve);
callback(QPromiseResolve<T>(resolver));
} catch (...) {
reject(std::current_exception());
resolver.reject(std::current_exception());
}
}
template <typename T>
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F resolver)
inline QPromiseBase<T>::QPromiseBase(F callback)
: m_d(new QtPromisePrivate::PromiseData<T>())
{
QPromiseResolve<T> resolve(*this);
QPromiseReject<T> reject(*this);
QtPromisePrivate::PromiseResolver<T> resolver(*this);
try {
resolver(resolve, reject);
callback(QPromiseResolve<T>(resolver), QPromiseReject<T>(resolver));
} catch (...) {
reject(std::current_exception());
resolver.reject(std::current_exception());
}
}
@ -131,6 +58,14 @@ QPromiseBase<T>::then(const TFulfilled& fulfilled, const TRejected& rejected) co
return next;
}
template <typename T>
template <typename TFulfilled>
inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
QPromiseBase<T>::then(TFulfilled&& fulfilled) const
{
return then(std::forward<TFulfilled>(fulfilled), nullptr);
}
template <typename T>
template <typename TRejected>
inline typename QtPromisePrivate::PromiseHandler<T, std::nullptr_t>::Promise
@ -139,6 +74,66 @@ QPromiseBase<T>::fail(TRejected&& rejected) const
return then(nullptr, std::forward<TRejected>(rejected));
}
template <typename T>
template <typename THandler>
inline QPromise<T> QPromiseBase<T>::finally(THandler handler) const
{
QPromise<T> p = *this;
return p.then(handler, handler).then([=]() {
return p;
});
}
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 THandler>
inline QPromise<T> QPromiseBase<T>::tapFail(THandler handler) const
{
QPromise<T> p = *this;
return p.then([](){}, handler).then([=]() {
return p;
});
}
template <typename T>
template <typename E>
inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const
{
QPromise<T> p = *this;
return QPromise<T>([&](
const QPromiseResolve<T>& resolve,
const QPromiseReject<T>& reject) {
QTimer::singleShot(msec, [=]() {
// we don't need to verify the current promise state, reject()
// takes care of checking if the promise is already resolved,
// and thus will ignore this rejection.
reject(std::move(error));
});
QtPromisePrivate::PromiseFulfill<QPromise<T>>::call(p, resolve, reject);
});
}
template <typename T>
inline QPromise<T> QPromiseBase<T>::delay(int msec) const
{
return tap([=]() {
return QPromise<void>([&](const QPromiseResolve<void>& resolve) {
QTimer::singleShot(msec, resolve);
});
});
}
template <typename T>
inline QPromise<T> QPromiseBase<T>::wait() const
{
@ -161,50 +156,79 @@ inline QPromise<T> QPromiseBase<T>::reject(E&& error)
}
template <typename T>
template <typename THandler>
inline QPromise<T> QPromise<T>::finally(THandler handler) const
template <typename Functor>
inline QPromise<T> QPromise<T>::each(Functor fn)
{
return this->then([=](const T& res) {
return QPromise<void>::resolve().then(handler).then([=](){
return res;
});
}, [=]() {
const auto exception = std::current_exception();
return QPromise<void>::resolve().then(handler).then([=](){
std::rethrow_exception(exception);
return T();
});
return this->tap([=](const T& values) {
int i = 0;
std::vector<QPromise<void>> promises;
for (const auto& v : values) {
promises.push_back(
QtPromise::attempt(fn, v, i)
.then([]() {
// Cast to void in case fn returns a non promise value.
// TODO remove when implicit cast is implemented.
}));
i++;
}
return QtPromise::all(promises);
});
}
template <typename T>
inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises)
template <typename Functor>
inline QPromise<T> QPromise<T>::filter(Functor fn)
{
const int count = promises.size();
if (count == 0) {
return QPromise<QVector<T> >::resolve({});
}
return this->then([=](const T& values) {
return QtPromise::filter(values, fn);
});
}
return QPromise<QVector<T> >([=](
const QPromiseResolve<QVector<T> >& resolve,
const QPromiseReject<QVector<T> >& reject) {
template <typename T>
template <typename Functor>
inline typename QtPromisePrivate::PromiseMapper<T, Functor>::PromiseType
QPromise<T>::map(Functor fn)
{
return this->then([=](const T& values) {
return QtPromise::map(values, fn);
});
}
QSharedPointer<int> remaining(new int(count));
QSharedPointer<QVector<T> > results(new QVector<T>(count));
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);
});
}
for (int i=0; i<count; ++i) {
promises[i].then([=](const T& res) mutable {
(*results)[i] = res;
if (--(*remaining) == 0) {
resolve(*results);
}
}, [=]() mutable {
if (*remaining != -1) {
*remaining = -1;
reject(std::current_exception());
}
});
}
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)
{
return QtPromise::all(promises);
}
template <typename T>
inline QPromise<T> QPromise<T>::resolve(const T& value)
{
return QPromise<T>([&](const QPromiseResolve<T>& resolve) {
resolve(value);
});
}
@ -216,52 +240,15 @@ inline QPromise<T> QPromise<T>::resolve(T&& value)
});
}
template <typename THandler>
inline QPromise<void> QPromise<void>::finally(THandler handler) const
template <template <typename, typename...> class Sequence, typename ...Args>
inline QPromise<void> QPromise<void>::all(const Sequence<QPromise<void>, Args...>& promises)
{
return this->then([=]() {
return QPromise<void>::resolve().then(handler).then([](){});
}, [=]() {
const auto exception = std::current_exception();
return QPromise<void>::resolve().then(handler).then([=](){
std::rethrow_exception(exception);
});
});
}
inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promises)
{
const int count = promises.size();
if (count == 0) {
return QPromise<void>::resolve();
}
return QPromise<void>([=](
const QPromiseResolve<void>& resolve,
const QPromiseReject<void>& reject) {
QSharedPointer<int> remaining(new int(promises.size()));
for (const auto& promise: promises) {
promise.then([=]() {
if (--(*remaining) == 0) {
resolve();
}
}, [=]() {
if (*remaining != -1) {
*remaining = -1;
reject(std::current_exception());
}
});
}
});
return QtPromise::all(promises);
}
inline QPromise<void> QPromise<void>::resolve()
{
return QPromise<void>([](const QPromiseResolve<void>& resolve) {
resolve();
});
return QtPromise::resolve();
}
} // namespace QtPromise

View File

@ -1,8 +1,6 @@
#ifndef QTPROMISE_QPROMISE_P_H
#define QTPROMISE_QPROMISE_P_H
// QtPromise
#include "qpromiseerror.h"
#include "qpromiseglobal.h"
// Qt
@ -32,65 +30,151 @@ namespace QtPromisePrivate {
// https://stackoverflow.com/a/21653558
template <typename F>
static void qtpromise_defer(F&& f, QThread* thread = nullptr)
static void qtpromise_defer(F&& f, const QPointer<QThread>& thread)
{
using FType = typename std::decay<F>::type;
struct Event : public QEvent
{
using FType = typename std::decay<F>::type;
Event(FType&& f) : QEvent(QEvent::None), m_f(std::move(f)) { }
Event(const FType& f) : QEvent(QEvent::None), m_f(f) { }
~Event() { m_f(); }
FType m_f;
};
if (!thread || thread->isFinished()) {
// Make sure to not call `f` if the captured thread doesn't exist anymore,
// which would potentially result in dispatching to the wrong thread (ie.
// nullptr == current thread). Since the target thread is gone, it should
// be safe to simply skip that notification.
return;
}
QObject* target = QAbstractEventDispatcher::instance(thread);
if (!target && QCoreApplication::closingDown()) {
// When the app is shutting down, the even loop is not anymore available
// so we don't have any way to dispatch `f`. This case can happen when a
// promise is resolved after the app is requested to close, in which case
// we should not trigger any error and skip that notification.
return;
}
Q_ASSERT_X(target, "postMetaCall", "Target thread must have an event loop");
QCoreApplication::postEvent(target, new Event(std::forward<F>(f)));
}
template <typename T>
struct PromiseDeduce
template <typename F>
static void qtpromise_defer(F&& f)
{
using Type = QtPromise::QPromise<Unqualified<T> >;
Q_ASSERT(QThread::currentThread());
qtpromise_defer(std::forward<F>(f), QThread::currentThread());
}
template <typename T>
class PromiseValue
{
public:
PromiseValue() { }
PromiseValue(const T& data) : m_data(new T(data)) { }
PromiseValue(T&& data) : m_data(new T(std::move(data))) { }
bool isNull() const { return m_data.isNull(); }
const T& data() const { return *m_data; }
private:
QSharedPointer<T> m_data;
};
class PromiseError
{
public:
template <typename T>
PromiseError(const T& value)
{
try {
throw value;
} catch (...) {
m_data = std::current_exception();
}
}
PromiseError() { }
PromiseError(const std::exception_ptr& exception) : m_data(exception) { }
void rethrow() const { std::rethrow_exception(m_data); }
bool isNull() const { return m_data == nullptr; }
private:
// NOTE(SB) std::exception_ptr is already a shared pointer
std::exception_ptr m_data;
};
template <typename T>
struct PromiseDeduce<QtPromise::QPromise<T> >
struct PromiseDeduce
{
using Type = QtPromise::QPromise<T>;
};
template <typename T>
struct PromiseDeduce<T&>
: public PromiseDeduce<T>
{ };
template <typename T>
struct PromiseDeduce<const T>
: public PromiseDeduce<T>
{ };
template <typename T>
struct PromiseDeduce<const volatile T>
: public PromiseDeduce<T>
{ };
template <typename T>
struct PromiseDeduce<QtPromise::QPromise<T>>
: public PromiseDeduce<T>
{ };
template <typename Functor, typename... Args>
struct PromiseFunctor
{
using ResultType = typename std::result_of<Functor(Args...)>::type;
using PromiseType = typename PromiseDeduce<ResultType>::Type;
};
template <typename T>
struct PromiseFulfill
{
static void call(
T&& value,
const QtPromise::QPromiseResolve<T>& resolve,
const QtPromise::QPromiseReject<T>&)
template <typename V, typename TResolve, typename TReject>
static void call(V&& value, const TResolve& resolve, const TReject&)
{
resolve(std::move(value));
resolve(std::forward<V>(value));
}
};
template <typename T>
struct PromiseFulfill<QtPromise::QPromise<T> >
struct PromiseFulfill<QtPromise::QPromise<T>>
{
template <typename TResolve, typename TReject>
static void call(
const QtPromise::QPromise<T>& promise,
const QtPromise::QPromiseResolve<T>& resolve,
const QtPromise::QPromiseReject<T>& reject)
const TResolve& resolve,
const TReject& reject)
{
promise.then(
[=](const T& value) {
resolve(value);
},
[=]() { // catch all
reject(std::current_exception());
if (promise.isFulfilled()) {
resolve(promise.m_d->value());
} else if (promise.isRejected()) {
reject(promise.m_d->error());
} else {
promise.then([=]() {
resolve(promise.m_d->value());
}, [=]() { // catch all
reject(promise.m_d->error());
});
}
}
};
template <>
struct PromiseFulfill<QtPromise::QPromise<void> >
struct PromiseFulfill<QtPromise::QPromise<void>>
{
template <typename TPromise, typename TResolve, typename TReject>
static void call(
@ -98,61 +182,31 @@ struct PromiseFulfill<QtPromise::QPromise<void> >
const TResolve& resolve,
const TReject& reject)
{
promise.then(
[=]() {
if (promise.isFulfilled()) {
resolve();
} else if (promise.isRejected()) {
reject(promise.m_d->error());
} else {
promise.then([=]() {
resolve();
},
[=]() { // catch all
reject(std::current_exception());
}, [=]() { // catch all
reject(promise.m_d->error());
});
}
}
};
template <typename T, typename TRes>
template <typename Result>
struct PromiseDispatch
{
using Promise = typename PromiseDeduce<TRes>::Type;
using ResType = Unqualified<TRes>;
template <typename THandler, typename TResolve, typename TReject>
static void call(const T& value, THandler handler, const TResolve& resolve, const TReject& reject)
template <typename Resolve, typename Reject, typename Functor, typename... Args>
static void call(const Resolve& resolve, const Reject& reject, Functor fn, Args&&... args)
{
try {
PromiseFulfill<ResType>::call(handler(value), resolve, reject);
} catch (...) {
reject(std::current_exception());
}
}
};
template <typename T>
struct PromiseDispatch<T, void>
{
using Promise = QtPromise::QPromise<void>;
template <typename THandler, typename TResolve, typename TReject>
static void call(const T& value, THandler handler, const TResolve& resolve, const TReject& reject)
{
try {
handler(value);
resolve();
} catch (...) {
reject(std::current_exception());
}
}
};
template <typename TRes>
struct PromiseDispatch<void, TRes>
{
using Promise = typename PromiseDeduce<TRes>::Type;
using ResType = Unqualified<TRes>;
template <typename THandler, typename TResolve, typename TReject>
static void call(THandler handler, const TResolve& resolve, const TReject& reject)
{
try {
PromiseFulfill<ResType>::call(handler(), resolve, reject);
PromiseFulfill<Unqualified<Result>>::call(
fn(std::forward<Args>(args)...),
resolve,
reject);
} catch (...) {
reject(std::current_exception());
}
@ -160,15 +214,13 @@ struct PromiseDispatch<void, TRes>
};
template <>
struct PromiseDispatch<void, void>
struct PromiseDispatch<void>
{
using Promise = QtPromise::QPromise<void>;
template <typename THandler, typename TResolve, typename TReject>
static void call(THandler handler, const TResolve& resolve, const TReject& reject)
template <typename Resolve, typename Reject, typename Functor, typename... Args>
static void call(const Resolve& resolve, const Reject& reject, Functor fn, Args&&... args)
{
try {
handler();
fn(std::forward<Args>(args)...);
resolve();
} catch (...) {
reject(std::current_exception());
@ -180,7 +232,7 @@ template <typename T, typename THandler, typename TArg = typename ArgsOf<THandle
struct PromiseHandler
{
using ResType = typename std::result_of<THandler(T)>::type;
using Promise = typename PromiseDispatch<T, ResType>::Promise;
using Promise = typename PromiseDeduce<ResType>::Type;
template <typename TResolve, typename TReject>
static std::function<void(const T&)> create(
@ -189,7 +241,7 @@ struct PromiseHandler
const TReject& reject)
{
return [=](const T& value) {
PromiseDispatch<T, ResType>::call(value, std::move(handler), resolve, reject);
PromiseDispatch<ResType>::call(resolve, reject, handler, value);
};
}
};
@ -198,7 +250,7 @@ template <typename T, typename THandler>
struct PromiseHandler<T, THandler, void>
{
using ResType = typename std::result_of<THandler()>::type;
using Promise = typename PromiseDispatch<T, ResType>::Promise;
using Promise = typename PromiseDeduce<ResType>::Type;
template <typename TResolve, typename TReject>
static std::function<void(const T&)> create(
@ -207,7 +259,7 @@ struct PromiseHandler<T, THandler, void>
const TReject& reject)
{
return [=](const T&) {
PromiseDispatch<void, ResType>::call(handler, resolve, reject);
PromiseDispatch<ResType>::call(resolve, reject, handler);
};
}
};
@ -216,7 +268,7 @@ template <typename THandler>
struct PromiseHandler<void, THandler, void>
{
using ResType = typename std::result_of<THandler()>::type;
using Promise = typename PromiseDispatch<void, ResType>::Promise;
using Promise = typename PromiseDeduce<ResType>::Type;
template <typename TResolve, typename TReject>
static std::function<void()> create(
@ -225,7 +277,7 @@ struct PromiseHandler<void, THandler, void>
const TReject& reject)
{
return [=]() {
PromiseDispatch<void, ResType>::call(handler, resolve, reject);
PromiseDispatch<ResType>::call(resolve, reject, handler);
};
}
};
@ -274,16 +326,16 @@ struct PromiseCatcher
using ResType = typename std::result_of<THandler(TArg)>::type;
template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create(
static std::function<void(const PromiseError&)> create(
const THandler& handler,
const TResolve& resolve,
const TReject& reject)
{
return [=](const QtPromise::QPromiseError& error) {
return [=](const PromiseError& error) {
try {
error.rethrow();
} catch (const TArg& error) {
PromiseDispatch<TArg, ResType>::call(error, handler, resolve, reject);
PromiseDispatch<ResType>::call(resolve, reject, handler, error);
} catch (...) {
reject(std::current_exception());
}
@ -297,16 +349,16 @@ struct PromiseCatcher<T, THandler, void>
using ResType = typename std::result_of<THandler()>::type;
template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create(
static std::function<void(const PromiseError&)> create(
const THandler& handler,
const TResolve& resolve,
const TReject& reject)
{
return [=](const QtPromise::QPromiseError& error) {
return [=](const PromiseError& error) {
try {
error.rethrow();
} catch (...) {
PromiseDispatch<void, ResType>::call(handler, resolve, reject);
PromiseDispatch<ResType>::call(resolve, reject, handler);
}
};
}
@ -316,12 +368,12 @@ template <typename T>
struct PromiseCatcher<T, std::nullptr_t, void>
{
template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create(
static std::function<void(const PromiseError&)> create(
std::nullptr_t,
const TResolve&,
const TReject& reject)
{
return [=](const QtPromise::QPromiseError& error) {
return [=](const PromiseError& error) {
// 2.2.7.4. If onRejected is not a function and promise1 is rejected,
// promise2 must be rejected with the same reason as promise1
reject(error);
@ -329,14 +381,26 @@ struct PromiseCatcher<T, std::nullptr_t, void>
}
};
template <typename T, typename F>
struct PromiseMapper
{ };
template <typename T, typename F, template <typename, typename...> class Sequence, typename ...Args>
struct PromiseMapper<Sequence<T, Args...>, F>
{
using ReturnType = typename std::result_of<F(T, int)>::type;
using ResultType = QVector<typename PromiseDeduce<ReturnType>::Type::Type>;
using PromiseType = QtPromise::QPromise<ResultType>;
};
template <typename T> class PromiseData;
template <typename T>
template <typename T, typename F>
class PromiseDataBase : public QSharedData
{
public:
using Error = QtPromise::QPromiseError;
using Catcher = std::pair<QPointer<QThread>, std::function<void(const Error&)> >;
using Handler = std::pair<QPointer<QThread>, std::function<F>>;
using Catcher = std::pair<QPointer<QThread>, std::function<void(const PromiseError&)>>;
virtual ~PromiseDataBase() {}
@ -356,42 +420,63 @@ public:
return !m_settled;
}
void addCatcher(std::function<void(const Error&)> catcher)
void addHandler(std::function<F> handler)
{
QWriteLocker lock(&m_lock);
m_handlers.append({QThread::currentThread(), std::move(handler)});
}
void addCatcher(std::function<void(const PromiseError&)> catcher)
{
QWriteLocker lock(&m_lock);
m_catchers.append({QThread::currentThread(), std::move(catcher)});
}
void reject(Error error)
template <typename E>
void reject(E&& error)
{
Q_ASSERT(isPending());
Q_ASSERT(m_error.isNull());
m_error.reset(new Error(std::move(error)));
m_error = PromiseError(std::forward<E>(error));
setSettled();
}
const PromiseError& error() const
{
Q_ASSERT(isRejected());
return m_error;
}
void dispatch()
{
if (isPending()) {
return;
}
if (m_error.isNull()) {
notify();
return;
}
// A promise can't be resolved multiple times so once settled, its state can't
// change. When fulfilled, handlers must be called (a single time) and catchers
// ignored indefinitely (or vice-versa when the promise is rejected), so make
// sure to clear both handlers AND catchers when dispatching. This also prevents
// shared pointer circular reference memory leaks when the owning promise is
// captured in the handler and/or catcher lambdas.
m_lock.lockForWrite();
QVector<Handler> handlers(std::move(m_handlers));
QVector<Catcher> catchers(std::move(m_catchers));
m_lock.unlock();
QSharedPointer<Error> error = m_error;
if (m_error.isNull()) {
notify(handlers);
return;
}
PromiseError error(m_error);
Q_ASSERT(!error.isNull());
for (const auto& catcher: catchers) {
const auto& fn = catcher.second;
qtpromise_defer([=]() {
fn(*error);
fn(error);
}, catcher.first);
}
}
@ -406,92 +491,82 @@ protected:
m_settled = true;
}
virtual void notify() = 0;
virtual void notify(const QVector<Handler>&) = 0;
private:
bool m_settled = false;
QVector<Handler> m_handlers;
QVector<Catcher> m_catchers;
QSharedPointer<Error> m_error;
PromiseError m_error;
};
template <typename T>
class PromiseData : public PromiseDataBase<T>
class PromiseData : public PromiseDataBase<T, void(const T&)>
{
using Handler = std::pair<QPointer<QThread>, std::function<void(const T&)> >;
using Handler = typename PromiseDataBase<T, void(const T&)>::Handler;
public:
void addHandler(std::function<void(const T&)> handler)
{
QWriteLocker lock(&this->m_lock);
m_handlers.append({QThread::currentThread(), std::move(handler)});
}
void resolve(T&& value)
template <typename V>
void resolve(V&& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value.reset(new T(std::move(value)));
m_value = PromiseValue<T>(std::forward<V>(value));
this->setSettled();
}
void resolve(const T& value)
const PromiseValue<T>& value() const
{
Q_ASSERT(m_value.isNull());
m_value.reset(new T(value));
this->setSettled();
Q_ASSERT(this->isFulfilled());
return m_value;
}
void notify() Q_DECL_OVERRIDE
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
{
this->m_lock.lockForWrite();
QVector<Handler> handlers(std::move(m_handlers));
this->m_lock.unlock();
QSharedPointer<T> value(m_value);
PromiseValue<T> value(m_value);
Q_ASSERT(!value.isNull());
for (const auto& handler: handlers) {
const auto& fn = handler.second;
qtpromise_defer([=]() {
fn(*value);
fn(value.data());
}, handler.first);
}
}
private:
QVector<Handler> m_handlers;
QSharedPointer<T> m_value;
PromiseValue<T> m_value;
};
template <>
class PromiseData<void> : public PromiseDataBase<void>
class PromiseData<void> : public PromiseDataBase<void, void()>
{
using Handler = std::pair<QPointer<QThread>, std::function<void()> >;
using Handler = PromiseDataBase<void, void()>::Handler;
public:
void addHandler(std::function<void()> handler)
void resolve()
{
QWriteLocker lock(&m_lock);
m_handlers.append({QThread::currentThread(), std::move(handler)});
setSettled();
}
void resolve() { setSettled(); }
protected:
void notify() Q_DECL_OVERRIDE
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
{
this->m_lock.lockForWrite();
QVector<Handler> handlers(std::move(m_handlers));
this->m_lock.unlock();
for (const auto& handler: handlers) {
qtpromise_defer(handler.second, handler.first);
}
}
private:
QVector<Handler> m_handlers;
};
} // namespace QtPromise
struct PromiseInspect
{
template <typename T>
static inline PromiseData<T>* get(const QtPromise::QPromise<T>& p)
{
return p.m_d.data();
}
};
#endif // ifndef QTPROMISE_QPROMISE_H
} // namespace QtPromisePrivate
#endif // QTPROMISE_QPROMISE_H

View File

@ -0,0 +1,48 @@
#ifndef QTPROMISE_QPROMISECONNECTIONS_H
#define QTPROMISE_QPROMISECONNECTIONS_H
// Qt
#include <QSharedPointer>
namespace QtPromise {
class QPromiseConnections
{
public:
QPromiseConnections() : m_d(new Data()) { }
int count() const { return m_d->connections.count(); }
void disconnect() const { m_d->disconnect(); }
void operator<<(QMetaObject::Connection&& other) const
{
m_d->connections.append(std::move(other));
}
private:
struct Data
{
QVector<QMetaObject::Connection> connections;
~Data() {
if (!connections.empty()) {
qWarning("QPromiseConnections: destroyed with unhandled connections.");
disconnect();
}
}
void disconnect() {
for (const auto& connection: connections) {
QObject::disconnect(connection);
}
connections.clear();
}
};
QSharedPointer<Data> m_d;
};
} // namespace QtPromise
#endif // QTPROMISE_QPROMISECONNECTIONS_H

View File

@ -1,58 +0,0 @@
#ifndef QTPROMISE_QPROMISEERROR_H
#define QTPROMISE_QPROMISEERROR_H
// QtPromise
#include "qpromiseglobal.h"
namespace QtPromise {
class QPromiseError
{
public:
template <typename T>
QPromiseError(const T& value)
{
try {
throw value;
} catch (...) {
m_exception = std::current_exception();
}
}
QPromiseError(const std::exception_ptr& exception)
: m_exception(exception)
{ }
QPromiseError(const QPromiseError& error)
: m_exception(error.m_exception)
{ }
QPromiseError(QPromiseError&& other)
: m_exception(nullptr)
{
swap(other);
}
QPromiseError& operator =(QPromiseError other)
{
swap(other);
return *this;
}
void swap(QPromiseError& other)
{
qSwap(m_exception, other.m_exception);
}
void rethrow() const
{
std::rethrow_exception(m_exception);
}
private:
std::exception_ptr m_exception;
};
} // namespace QtPromise
#endif // QTPROMISE_QPROMISEERROR_H

View File

@ -0,0 +1,60 @@
#ifndef QTPROMISE_QPROMISEEXCEPTIONS_H
#define QTPROMISE_QPROMISEEXCEPTIONS_H
#include "qpromise_p.h"
#include "qpromiseglobal.h"
// Qt
#include <QException>
namespace QtPromise {
class QPromiseCanceledException : public QException
{
public:
void raise() const Q_DECL_OVERRIDE { throw *this; }
QPromiseCanceledException* clone() const Q_DECL_OVERRIDE
{
return new QPromiseCanceledException(*this);
}
};
class QPromiseContextException : public QException
{
public:
void raise() const Q_DECL_OVERRIDE { throw *this; }
QPromiseContextException* clone() const Q_DECL_OVERRIDE
{
return new QPromiseContextException(*this);
}
};
class QPromiseTimeoutException : public QException
{
public:
void raise() const Q_DECL_OVERRIDE { throw *this; }
QPromiseTimeoutException* clone() const Q_DECL_OVERRIDE
{
return new QPromiseTimeoutException(*this);
}
};
class QPromiseUndefinedException : public QException
{
public:
void raise() const Q_DECL_OVERRIDE { throw *this; }
QPromiseUndefinedException* clone() const Q_DECL_OVERRIDE
{
return new QPromiseUndefinedException(*this);
}
};
// QPromiseError is provided for backward compatibility and will be
// removed in the next major version: it wasn't intended to be used
// directly and thus should not be part of the public API.
// TODO Remove QPromiseError at version 1.0
using QPromiseError = QtPromisePrivate::PromiseError;
} // namespace QtPromise
#endif // QTPROMISE_QPROMISEEXCEPTIONS_H

View File

@ -1,33 +1,21 @@
#ifndef QTPROMISE_QPROMISEFUTURE_P_H
#define QTPROMISE_QPROMISEFUTURE_P_H
#include "qpromiseexceptions.h"
// Qt
#include <QFutureWatcher>
#include <QFuture>
namespace QtPromise {
class QPromiseCanceledException : public QException
{
public:
void raise() const Q_DECL_OVERRIDE { throw *this; }
QPromiseCanceledException* clone() const Q_DECL_OVERRIDE
{
return new QPromiseCanceledException(*this);
}
};
} // namespace QtPromise
namespace QtPromisePrivate {
template <typename T>
struct PromiseDeduce<QFuture<T> >
struct PromiseDeduce<QFuture<T>>
: public PromiseDeduce<T>
{ };
template <typename T>
struct PromiseFulfill<QFuture<T> >
struct PromiseFulfill<QFuture<T>>
{
static void call(
const QFuture<T>& future,
@ -62,7 +50,7 @@ struct PromiseFulfill<QFuture<T> >
};
template <>
struct PromiseFulfill<QFuture<void> >
struct PromiseFulfill<QFuture<void>>
{
static void call(
const QFuture<void>& future,

View File

@ -16,18 +16,18 @@ using Unqualified = typename std::remove_cv<typename std::remove_reference<T>::t
/*!
* \struct HasCallOperator
* http://stackoverflow.com/a/5839442
* http://stackoverflow.com/a/5117641
*/
template <typename T>
struct HasCallOperator
{
template <class U>
static auto check(const U* u)
-> decltype(&U::operator(), char(0));
template <typename U>
static char check(decltype(&U::operator(), char(0)));
static std::array<char, 2> check(...);
template <typename U>
static char (&check(...))[2];
static const bool value = (sizeof(check((T*)0)) == 1);
static const bool value = (sizeof(check<T>(0)) == 1);
};
/*!
@ -85,7 +85,7 @@ struct ArgsOf<TReturn(T::*)(Args...) const volatile> : public ArgsTraits<Args...
{ };
template <typename T>
struct ArgsOf<std::function<T> > : public ArgsOf<T>
struct ArgsOf<std::function<T>> : public ArgsOf<T>
{ };
template <typename T>
@ -122,4 +122,4 @@ struct ArgsOf<const volatile T&&> : public ArgsOf<T>
} // namespace QtPromisePrivate
#endif // ifndef QTPROMISE_QPROMISEGLOBAL_H
#endif // QTPROMISE_QPROMISEGLOBAL_H

View File

@ -1,40 +1,297 @@
#ifndef QTPROMISE_QPROMISEHELPERS_H
#define QTPROMISE_QPROMISEHELPERS_H
// QtPromise
#include "qpromise_p.h"
#include "qpromisehelpers_p.h"
namespace QtPromise {
template <typename T>
typename QtPromisePrivate::PromiseDeduce<T>::Type qPromise(T&& value)
static inline typename QtPromisePrivate::PromiseDeduce<T>::Type
resolve(T&& value)
{
using namespace QtPromisePrivate;
using Promise = typename PromiseDeduce<T>::Type;
return Promise([&](
const QPromiseResolve<typename Promise::Type>& resolve,
const QPromiseReject<typename Promise::Type>& reject) {
PromiseFulfill<T>::call(std::forward<T>(value), resolve, reject);
});
}
using PromiseType = typename PromiseDeduce<T>::Type;
using ValueType = typename PromiseType::Type;
using ResolveType = QPromiseResolve<ValueType>;
using RejectType = QPromiseReject<ValueType>;
QPromise<void> qPromise()
{
return QPromise<void>([](
const QPromiseResolve<void>& resolve) {
resolve();
return PromiseType([&](ResolveType&& resolve, RejectType&& reject) {
PromiseFulfill<Unqualified<T>>::call(
std::forward<T>(value),
std::forward<ResolveType>(resolve),
std::forward<RejectType>(reject));
});
}
template <typename T>
QPromise<QVector<T> > qPromiseAll(const QVector<QPromise<T> >& promises)
static inline QPromise<T>
resolve(QPromise<T> value)
{
return QPromise<T>::all(promises);
return std::move(value);
}
QPromise<void> qPromiseAll(const QVector<QPromise<void> >& promises)
static inline QPromise<void>
resolve()
{
return QPromise<void>::all(promises);
return QPromise<void>([](const QPromiseResolve<void>& resolve) {
resolve();
});
}
template <typename T, template <typename, typename...> class Sequence = QVector, typename ...Args>
static inline QPromise<QVector<T>>
all(const Sequence<QPromise<T>, Args...>& promises)
{
const int count = static_cast<int>(promises.size());
if (count == 0) {
return QtPromise::resolve(QVector<T>{});
}
return QPromise<QVector<T>>([=](
const QPromiseResolve<QVector<T>>& resolve,
const QPromiseReject<QVector<T>>& reject) {
QSharedPointer<int> remaining(new int(count));
QSharedPointer<QVector<T>> results(new QVector<T>(count));
int i = 0;
for (const auto& promise: promises) {
promise.then([=](const T& res) mutable {
(*results)[i] = res;
if (--(*remaining) == 0) {
resolve(*results);
}
}, [=]() mutable {
if (*remaining != -1) {
*remaining = -1;
reject(std::current_exception());
}
});
i++;
}
});
}
template <template <typename, typename...> class Sequence = QVector, typename ...Args>
static inline QPromise<void>
all(const Sequence<QPromise<void>, Args...>& promises)
{
const int count = static_cast<int>(promises.size());
if (count == 0) {
return QtPromise::resolve();
}
return QPromise<void>([=](
const QPromiseResolve<void>& resolve,
const QPromiseReject<void>& reject) {
QSharedPointer<int> remaining(new int(count));
for (const auto& promise: promises) {
promise.then([=]() {
if (--(*remaining) == 0) {
resolve();
}
}, [=]() {
if (*remaining != -1) {
*remaining = -1;
reject(std::current_exception());
}
});
}
});
}
template <typename Functor, typename... Args>
static inline typename QtPromisePrivate::PromiseFunctor<Functor, Args...>::PromiseType
attempt(Functor&& fn, Args&&... args)
{
using namespace QtPromisePrivate;
using FunctorType = PromiseFunctor<Functor, Args...>;
using PromiseType = typename FunctorType::PromiseType;
using ValueType = typename PromiseType::Type;
// NOTE: std::forward<T<U>>: MSVC 2013 fails when forwarding
// template type (error: "expects 4 arguments - 0 provided").
// However it succeeds with type alias.
// TODO: should we expose QPromise::ResolveType & RejectType?
using ResolveType = QPromiseResolve<ValueType>;
using RejectType = QPromiseReject<ValueType>;
return PromiseType(
[&](ResolveType&& resolve, RejectType&& reject) {
PromiseDispatch<typename FunctorType::ResultType>::call(
std::forward<ResolveType>(resolve),
std::forward<RejectType>(reject),
std::forward<Functor>(fn),
std::forward<Args>(args)...);
});
}
template <typename Sender, typename Signal>
static inline typename QtPromisePrivate::PromiseFromSignal<Signal>
connect(const Sender* sender, Signal signal)
{
using namespace QtPromisePrivate;
using T = typename PromiseFromSignal<Signal>::Type;
return QPromise<T>(
[&](const QPromiseResolve<T>& resolve, const QPromiseReject<T>& reject) {
QPromiseConnections connections;
connectSignalToResolver(connections, resolve, sender, signal);
connectDestroyedToReject(connections, reject, sender);
});
}
template <typename FSender, typename FSignal, typename RSender, typename RSignal>
static inline typename QtPromisePrivate::PromiseFromSignal<FSignal>
connect(const FSender* fsender, FSignal fsignal, const RSender* rsender, RSignal rsignal)
{
using namespace QtPromisePrivate;
using T = typename PromiseFromSignal<FSignal>::Type;
return QPromise<T>(
[&](const QPromiseResolve<T>& resolve, const QPromiseReject<T>& reject) {
QPromiseConnections connections;
connectSignalToResolver(connections, resolve, fsender, fsignal);
connectSignalToResolver(connections, reject, rsender, rsignal);
connectDestroyedToReject(connections, reject, fsender);
});
}
template <typename Sender, typename FSignal, typename RSignal>
static inline typename QtPromisePrivate::PromiseFromSignal<FSignal>
connect(const Sender* sender, FSignal fsignal, RSignal rsignal)
{
return connect(sender, fsignal, sender, rsignal);
}
template <typename Sequence, typename Functor>
static inline QPromise<Sequence>
each(const Sequence& values, Functor&& fn)
{
return QPromise<Sequence>::resolve(values).each(std::forward<Functor>(fn));
}
template <typename Sequence, typename Functor>
static inline typename QtPromisePrivate::PromiseMapper<Sequence, Functor>::PromiseType
map(const Sequence& values, Functor fn)
{
using namespace QtPromisePrivate;
using MapperType = PromiseMapper<Sequence, Functor>;
using ResType = typename MapperType::ResultType::value_type;
using RetType = typename MapperType::ReturnType;
int i = 0;
std::vector<QPromise<ResType>> promises;
for (const auto& v : values) {
promises.push_back(QPromise<ResType>([&](
const QPromiseResolve<ResType>& resolve,
const QPromiseReject<ResType>& reject) {
PromiseFulfill<RetType>::call(fn(v, i), resolve, reject);
}));
i++;
}
return QtPromise::all(promises);
}
template <typename Sequence, typename Functor>
static inline QPromise<Sequence>
filter(const Sequence& values, Functor fn)
{
return QtPromise::map(values, fn)
.then([=](const QVector<bool>& filters) {
Sequence filtered;
auto filter = filters.begin();
for (auto& value : values) {
if (*filter) {
filtered.push_back(std::move(value));
}
filter++;
}
return filtered;
});
}
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>
Q_DECL_DEPRECATED_X("Use QtPromise::resolve instead")
static inline auto
qPromise(Args&&... args)
-> decltype(QtPromise::resolve(std::forward<Args>(args)...))
{
return QtPromise::resolve(std::forward<Args>(args)...);
}
template <typename... Args>
Q_DECL_DEPRECATED_X("Use QtPromise::all instead")
static inline auto
qPromiseAll(Args&&... args)
-> decltype(QtPromise::all(std::forward<Args>(args)...))
{
return QtPromise::all(std::forward<Args>(args)...);
}
} // namespace QtPromise

View File

@ -0,0 +1,91 @@
#ifndef QTPROMISE_QPROMISEHELPERS_P_H
#define QTPROMISE_QPROMISEHELPERS_P_H
#include "qpromiseconnections.h"
#include "qpromiseexceptions.h"
namespace QtPromisePrivate {
// TODO: Suppress QPrivateSignal trailing private signal args
// TODO: Support deducing tuple from args (might require MSVC2017)
template <typename Signal>
using PromiseFromSignal = typename QtPromise::QPromise<Unqualified<typename ArgsOf<Signal>::first>>;
// Connect signal() to QPromiseResolve
template <typename Sender, typename Signal>
typename std::enable_if<(ArgsOf<Signal>::count == 0)>::type
connectSignalToResolver(
const QtPromise::QPromiseConnections& connections,
const QtPromise::QPromiseResolve<void>& resolve,
const Sender* sender,
Signal signal)
{
connections << QObject::connect(sender, signal, [=]() {
connections.disconnect();
resolve();
});
}
// Connect signal() to QPromiseReject
template <typename T, typename Sender, typename Signal>
typename std::enable_if<(ArgsOf<Signal>::count == 0)>::type
connectSignalToResolver(
const QtPromise::QPromiseConnections& connections,
const QtPromise::QPromiseReject<T>& reject,
const Sender* sender,
Signal signal)
{
connections << QObject::connect(sender, signal, [=]() {
connections.disconnect();
reject(QtPromise::QPromiseUndefinedException());
});
}
// Connect signal(args...) to QPromiseResolve
template <typename T, typename Sender, typename Signal>
typename std::enable_if<(ArgsOf<Signal>::count >= 1)>::type
connectSignalToResolver(
const QtPromise::QPromiseConnections& connections,
const QtPromise::QPromiseResolve<T>& resolve,
const Sender* sender,
Signal signal)
{
connections << QObject::connect(sender, signal, [=](const T& value) {
connections.disconnect();
resolve(value);
});
}
// Connect signal(args...) to QPromiseReject
template <typename T, typename Sender, typename Signal>
typename std::enable_if<(ArgsOf<Signal>::count >= 1)>::type
connectSignalToResolver(
const QtPromise::QPromiseConnections& connections,
const QtPromise::QPromiseReject<T>& reject,
const Sender* sender,
Signal signal)
{
using V = Unqualified<typename ArgsOf<Signal>::first>;
connections << QObject::connect(sender, signal, [=](const V& value) {
connections.disconnect();
reject(value);
});
}
// Connect QObject::destroyed signal to QPromiseReject
template <typename T, typename Sender>
void connectDestroyedToReject(
const QtPromise::QPromiseConnections& connections,
const QtPromise::QPromiseReject<T>& reject,
const Sender* sender)
{
connections << QObject::connect(sender, &QObject::destroyed, [=]() {
connections.disconnect();
reject(QtPromise::QPromiseContextException());
});
}
} // namespace QtPromisePrivate
#endif // QTPROMISE_QPROMISEHELPERS_P_H

View File

@ -0,0 +1,142 @@
#ifndef QTPROMISE_QPROMISERESOLVER_H
#define QTPROMISE_QPROMISERESOLVER_H
#include "qpromiseexceptions.h"
// Qt
#include <QExplicitlySharedDataPointer>
namespace QtPromise {
template <typename T> class QPromise;
} // namespace QtPromise
namespace QtPromisePrivate {
template <typename T>
class PromiseResolver
{
public:
PromiseResolver(QtPromise::QPromise<T> promise)
: m_d(new Data())
{
m_d->promise = new QtPromise::QPromise<T>(std::move(promise));
}
template <typename E>
void reject(E&& error)
{
auto promise = m_d->promise;
if (promise) {
Q_ASSERT(promise->isPending());
promise->m_d->reject(std::forward<E>(error));
promise->m_d->dispatch();
release();
}
}
void reject()
{
auto promise = m_d->promise;
if (promise) {
Q_ASSERT(promise->isPending());
promise->m_d->reject(QtPromise::QPromiseUndefinedException());
promise->m_d->dispatch();
release();
}
}
template <typename V>
void resolve(V&& value)
{
auto promise = m_d->promise;
if (promise) {
Q_ASSERT(promise->isPending());
promise->m_d->resolve(std::forward<V>(value));
promise->m_d->dispatch();
release();
}
}
void resolve()
{
auto promise = m_d->promise;
if (promise) {
Q_ASSERT(promise->isPending());
promise->m_d->resolve();
promise->m_d->dispatch();
release();
}
}
private:
struct Data : public QSharedData
{
QtPromise::QPromise<T>* promise = nullptr;
};
QExplicitlySharedDataPointer<Data> m_d;
void release()
{
Q_ASSERT(m_d->promise);
Q_ASSERT(!m_d->promise->isPending());
delete m_d->promise;
m_d->promise = nullptr;
}
};
} // QtPromisePrivate
namespace QtPromise {
template <class T>
class QPromiseResolve
{
public:
QPromiseResolve(QtPromisePrivate::PromiseResolver<T> resolver)
: m_resolver(std::move(resolver))
{ }
template <typename V>
void operator()(V&& value) const
{
m_resolver.resolve(std::forward<V>(value));
}
void operator()() const
{
m_resolver.resolve();
}
private:
mutable QtPromisePrivate::PromiseResolver<T> m_resolver;
};
template <class T>
class QPromiseReject
{
public:
QPromiseReject(QtPromisePrivate::PromiseResolver<T> resolver)
: m_resolver(std::move(resolver))
{ }
template <typename E>
void operator()(E&& error) const
{
m_resolver.reject(std::forward<E>(error));
}
void operator()() const
{
m_resolver.reject();
}
private:
mutable QtPromisePrivate::PromiseResolver<T> m_resolver;
};
} // namespace QtPromise
#endif // QTPROMISE_QPROMISERESOLVER_H

View File

@ -2,7 +2,10 @@ HEADERS += \
$$PWD/qpromise.h \
$$PWD/qpromise.inl \
$$PWD/qpromise_p.h \
$$PWD/qpromiseerror.h \
$$PWD/qpromiseconnections.h \
$$PWD/qpromiseexceptions.h \
$$PWD/qpromisefuture.h \
$$PWD/qpromiseglobal.h \
$$PWD/qpromisehelpers.h
$$PWD/qpromisehelpers.h \
$$PWD/qpromisehelpers_p.h \
$$PWD/qpromiseresolver.h

View File

@ -1,8 +1,2 @@
TEMPLATE = subdirs
SUBDIRS += \
benchmark \
future \
helpers \
qpromise \
requirements \
thread
SUBDIRS += qtpromise

View File

@ -1,4 +0,0 @@
TARGET = tst_helpers
SOURCES += $$PWD/tst_helpers.cpp
include(../tests.pri)

View File

@ -1,244 +0,0 @@
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_helpers : public QObject
{
Q_OBJECT
private Q_SLOTS:
void resolve();
void resolve_void();
void resolve_promise();
void resolve_promise_void();
void allFulfilled();
void allFulfilled_void();
void allRejected();
void allRejected_void();
void allEmpty();
void allEmpty_void();
}; // class tst_helpers
QTEST_MAIN(tst_helpers)
#include "tst_helpers.moc"
void tst_helpers::resolve()
{
int value = -1;
auto p = QtPromise::qPromise(42);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(p.isFulfilled(), true);
p.then([&](int res) {
value = res;
}).wait();
QCOMPARE(value, 42);
}
void tst_helpers::resolve_void()
{
int value = -1;
auto p = QtPromise::qPromise();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(p.isFulfilled(), true);
p.then([&]() {
value = 42;
}).wait();
QCOMPARE(value, 42);
}
void tst_helpers::resolve_promise()
{
QString value;
auto p = QtPromise::qPromise(
QPromise<QString>([](const QPromiseResolve<QString>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve("foo");
});
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString> >::value));
QCOMPARE(p.isPending(), true);
p.then([&](const QString& res) {
value = res;
}).wait();
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, QString("foo"));
}
void tst_helpers::resolve_promise_void()
{
QList<int> values;
auto p = QtPromise::qPromise(
QPromise<void>([&](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=, &values](){
values << 42;
resolve();
});
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(p.isPending(), true);
p.then([&]() {
values << 43;
}).wait();
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(values, QList<int>({42, 43}));
}
void tst_helpers::allFulfilled()
{
auto p0 = QtPromise::qPromise(42);
auto p1 = QtPromise::qPromise(44);
auto p2 = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve(43);
});
});
auto p = qPromiseAll(QVector<QPromise<int> >{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QVector<int> values;
p.then([&](const QVector<int>& res) {
values = res;
}).wait();
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(p2.isFulfilled(), true);
QCOMPARE(values, QVector<int>({42, 43, 44}));
}
void tst_helpers::allFulfilled_void()
{
auto p0 = QtPromise::qPromise();
auto p1 = QtPromise::qPromise();
auto p2 = QPromise<void>([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve();
});
});
auto p = qPromiseAll(QVector<QPromise<void> >{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
p.wait();
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(p2.isFulfilled(), true);
}
void tst_helpers::allRejected()
{
auto p0 = QtPromise::qPromise(42);
auto p1 = QtPromise::qPromise(44);
auto p2 = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=](){
reject(QString("foo"));
});
});
auto p = qPromiseAll(QVector<QPromise<int> >{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QString error;
p.fail([&](const QString& err) {
error = err;
return QVector<int>();
}).wait();
QCOMPARE(p.isRejected(), true);
QCOMPARE(p2.isRejected(), true);
QCOMPARE(error, QString("foo"));
}
void tst_helpers::allRejected_void()
{
auto p0 = QtPromise::qPromise();
auto p1 = QtPromise::qPromise();
auto p2 = QPromise<void>([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=](){
reject(QString("foo"));
});
});
auto p = qPromiseAll(QVector<QPromise<void> >{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QString error;
p.fail([&](const QString& err) {
error = err;
}).wait();
QCOMPARE(p.isRejected(), true);
QCOMPARE(p2.isRejected(), true);
QCOMPARE(error, QString("foo"));
}
void tst_helpers::allEmpty()
{
auto p = qPromiseAll(QVector<QPromise<int> >());
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
QCOMPARE(p.isFulfilled(), true);
QVector<int> values;
p.then([&](const QVector<int>& res) {
values = res;
}).wait();
QCOMPARE(values, QVector<int>());
}
void tst_helpers::allEmpty_void()
{
auto p = qPromiseAll(QVector<QPromise<void> >());
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(p.isFulfilled(), true);
}

View File

@ -1,4 +0,0 @@
TARGET = tst_qpromise
SOURCES += $$PWD/tst_qpromise.cpp
include(../tests.pri)

View File

@ -1,528 +0,0 @@
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
using namespace QtPromisePrivate;
class tst_qpromise : public QObject
{
Q_OBJECT
private Q_SLOTS:
void resolveSync();
void resolveSync_void();
void resolveDelayed();
void rejectSync();
void rejectDelayed();
void rejectThrows();
void thenReturns();
void thenThrows();
void thenNullPtr();
void thenSkipResult();
void thenDelayedResolved();
void thenDelayedRejected();
void failSameType();
void failBaseClass();
void failCatchAll();
void finallyReturns();
void finallyReturns_void();
void finallyThrows();
void finallyThrows_void();
void finallyDelayedResolved();
void finallyDelayedRejected();
}; // class tst_qpromise
QTEST_MAIN(tst_qpromise)
#include "tst_qpromise.moc"
template <typename T>
T waitForValue(const QPromise<T>& promise, const T& initial)
{
T value(initial);
promise.then([&](const T& res) {
value = res;
}).wait();
return value;
}
template <typename T>
T waitForValue(const QPromise<void>& promise, const T& initial, const T& expected)
{
T value(initial);
promise.then([&]() {
value = expected;
}).wait();
return value;
}
template <typename T, typename E>
E waitForError(const QPromise<T>& promise, const E& initial)
{
E error(initial);
promise.fail([&](const E& err) {
error = err;
return T();
}).wait();
return error;
}
template <typename E>
E waitForError(const QPromise<void>& promise, const E& initial)
{
E error(initial);
promise.fail([&](const E& err) {
error = err;
}).wait();
return error;
}
void tst_qpromise::resolveSync()
{
{ // resolver(resolve)
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
resolve(42);
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
}
{ // resolver(resolve, reject)
QPromise<int> p([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>&) {
resolve(42);
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
}
}
void tst_qpromise::resolveSync_void()
{
{ // resolver(resolve)
QPromise<void> p([](const QPromiseResolve<void>& resolve) {
resolve();
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
}
{ // resolver(resolve, reject)
QPromise<void> p([](const QPromiseResolve<void>& resolve, const QPromiseReject<void>&) {
resolve();
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
}
}
void tst_qpromise::resolveDelayed()
{
{ // resolver(resolve)
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
}
{ // resolver(resolve, reject)
QPromise<int> p([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>&) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
}
}
void tst_qpromise::rejectSync()
{
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
reject(QString("foo"));
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise::rejectDelayed()
{
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise::rejectThrows()
{
{ // resolver(resolve)
QPromise<int> p([](const QPromiseResolve<int>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
{ // resolver(resolve, reject)
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
}
void tst_qpromise::thenReturns()
{
auto p = QPromise<int>::resolve(42);
QVariantList values;
p.then([&](int res) {
values << res;
return QString::number(res+1);
}).then([&](const QString& res) {
values << res;
}).then([&]() {
values << 44;
}).wait();
QCOMPARE(values, QVariantList({42, QString("43"), 44}));
}
void tst_qpromise::thenThrows()
{
auto input = QPromise<int>::resolve(42);
auto output = input.then([](int res) {
throw QString("foo%1").arg(res);
return 42;
});
QString error;
output.then([&](int res) {
error += "bar" + QString::number(res);
}).fail([&](const QString& err) {
error += err;
}).wait();
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isRejected(), true);
QCOMPARE(error, QString("foo42"));
}
void tst_qpromise::thenNullPtr()
{
{ // resolved
auto p = QPromise<int>::resolve(42).then(nullptr);
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
}
{ // rejected
auto p = QPromise<int>::reject(QString("foo")).then(nullptr);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
}
void tst_qpromise::thenSkipResult()
{
auto p = QPromise<int>::resolve(42);
int value = -1;
p.then([&]() {
value = 43;
}).wait();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(value, 43);
}
void tst_qpromise::thenDelayedResolved()
{
auto p = QPromise<int>::resolve(42).then([](int res) {
return QPromise<QString>([=](const QPromiseResolve<QString>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(QString("foo%1").arg(res));
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString> >::value));
QCOMPARE(waitForValue(p, QString()), QString("foo42"));
}
void tst_qpromise::thenDelayedRejected()
{
auto p = QPromise<int>::resolve(42).then([](int res) {
return QPromise<void>([=](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo%1").arg(res));
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForError(p, QString()), QString("foo42"));
}
void tst_qpromise::failSameType()
{
// http://en.cppreference.com/w/cpp/error/exception
auto p = QPromise<int>::reject(std::out_of_range("foo"));
QString error;
p.fail([&](const std::domain_error& e) {
error += QString(e.what()) + "0";
return -1;
}).fail([&](const std::out_of_range& e) {
error += QString(e.what()) + "1";
return -1;
}).fail([&](const std::exception& e) {
error += QString(e.what()) + "2";
return -1;
}).wait();
QCOMPARE(error, QString("foo1"));
}
void tst_qpromise::failBaseClass()
{
// http://en.cppreference.com/w/cpp/error/exception
auto p = QPromise<int>::reject(std::out_of_range("foo"));
QString error;
p.fail([&](const std::runtime_error& e) {
error += QString(e.what()) + "0";
return -1;
}).fail([&](const std::logic_error& e) {
error += QString(e.what()) + "1";
return -1;
}).fail([&](const std::exception& e) {
error += QString(e.what()) + "2";
return -1;
}).wait();
QCOMPARE(error, QString("foo1"));
}
void tst_qpromise::failCatchAll()
{
auto p = QPromise<int>::reject(std::out_of_range("foo"));
QString error;
p.fail([&](const std::runtime_error& e) {
error += QString(e.what()) + "0";
return -1;
}).fail([&]() {
error += "bar";
return -1;
}).fail([&](const std::exception& e) {
error += QString(e.what()) + "2";
return -1;
}).wait();
QCOMPARE(error, QString("bar"));
}
void tst_qpromise::finallyReturns()
{
{ // fulfilled
int value = -1;
auto p = QPromise<int>::resolve(42).finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 8);
}
{ // rejected
int value = -1;
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, 8);
}
}
void tst_qpromise::finallyReturns_void()
{
{ // fulfilled
int value = -1;
auto p = QPromise<void>::resolve().finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 8);
}
{ // rejected
int value = -1;
auto p = QPromise<void>::reject(QString("foo")).finally([&]() {
value = 8;
return 16; // ignored!
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, 8);
}
}
void tst_qpromise::finallyThrows()
{
{ // fulfilled
auto p = QPromise<int>::resolve(42).finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
{ // rejected
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
}
void tst_qpromise::finallyThrows_void()
{
{ // fulfilled
auto p = QPromise<void>::resolve().finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
{ // rejected
auto p = QPromise<void>::reject(QString("foo")).finally([&]() {
throw QString("bar");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
}
void tst_qpromise::finallyDelayedResolved()
{
{ // fulfilled
QVector<int> values;
auto p = QPromise<int>::resolve(42).finally([&]() {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=, &values]() {
values << 64;
resolve(16); // ignored!
});
});
values << 8;
return p;
});
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(values, QVector<int>({8, 64}));
}
{ // rejected
QVector<int> values;
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=, &values]() {
values << 64;
resolve(16); // ignored!
});
});
values << 8;
return p;
});
p.then([&](int r) {
values << r;
}).wait();
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(values, QVector<int>({8, 64}));
}
}
void tst_qpromise::finallyDelayedRejected()
{
{ // fulfilled
auto p = QPromise<int>::resolve(42).finally([]() {
return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
reject(QString("bar"));
});
});
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
{ // rejected
auto p = QPromise<int>::reject(QString("foo")).finally([]() {
return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
reject(QString("bar"));
});
});
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
}

View File

@ -1,4 +1,4 @@
TARGET = tst_benchmark
SOURCES += $$PWD/tst_benchmark.cpp
include(../tests.pri)
include(../qtpromise.pri)

View File

@ -1,9 +1,19 @@
#include "../shared/data.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
#ifdef Q_CC_MSVC
// MSVC calls the copy constructor on std::current_exception AND std::rethrow_exception
// https://stackoverflow.com/a/31820854
#define EXCEPT_CALL_COPY_CTOR 1
#else
#define EXCEPT_CALL_COPY_CTOR 0
#endif
using namespace QtPromise;
class tst_benchmark : public QObject
@ -14,6 +24,9 @@ private Q_SLOTS:
void valueResolve();
void valueReject();
void valueThen();
void valueFinally();
void valueTap();
void valueDelayed();
void errorReject();
void errorThen();
@ -22,43 +35,6 @@ private Q_SLOTS:
QTEST_MAIN(tst_benchmark)
#include "tst_benchmark.moc"
struct Logs {
int ctor = 0;
int copy = 0;
int move = 0;
int refs = 0;
void reset() {
ctor = 0;
copy = 0;
move = 0;
refs = 0;
}
};
struct Logger
{
Logger() { logs().ctor++; logs().refs++; }
Logger(const Logger&) { logs().copy++; logs().refs++; }
Logger(Logger&&) { logs().move++; logs().refs++; }
~Logger() { logs().refs--; }
Logger& operator=(const Logger&) { logs().copy++; return *this; }
Logger& operator=(Logger&&) { logs().move++; return *this; }
public: // STATICS
static Logs& logs() { static Logs logs; return logs; }
};
struct Data : public Logger
{
Data(int v): Logger(), m_value(v) {}
int value() const { return m_value; }
private:
int m_value;
};
void tst_benchmark::valueResolve()
{
{ // should move the value when resolved by rvalue
@ -128,7 +104,7 @@ void tst_benchmark::valueThen()
QCOMPARE(Data::logs().ctor, 0);
QCOMPARE(Data::logs().copy, 0);
QCOMPARE(Data::logs().move, 0); // move value to the promise data
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
QCOMPARE(error, QString("foo"));
QCOMPARE(value, -1);
@ -162,6 +138,96 @@ void tst_benchmark::valueThen()
}
}
void tst_benchmark::valueDelayed()
{
{ // should not copy the value on continutation if fulfilled
int value = -1;
Data::logs().reset();
QPromise<int>::resolve(42).then([&](int res) {
return QPromise<Data>::resolve(Data(res + 1));
}).then([&](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 promise data
QCOMPARE(Data::logs().refs, 0);
QCOMPARE(value, 43);
}
{ // should not create value on continutation if rejected
Data::logs().reset();
QPromise<int>::resolve(42).then([&]() {
return QPromise<Data>::reject(QString("foo"));
}).wait();
QCOMPARE(Data::logs().ctor, 0);
QCOMPARE(Data::logs().copy, 0);
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
}
void tst_benchmark::valueFinally()
{
{ // should not copy the value on continutation if fulfilled
int value = -1;
Data::logs().reset();
QPromise<Data>::resolve(Data(42)).finally([&]() {
value = 42;
}).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")).finally([&]() {
value = 42;
}).wait();
QCOMPARE(Data::logs().ctor, 0);
QCOMPARE(Data::logs().copy, 0);
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
QCOMPARE(value, 42);
}
}
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()
{
{ // should create one copy of the error when rejected by rvalue
@ -171,7 +237,7 @@ void tst_benchmark::errorReject()
}).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // copy value in std::exception_ptr
QCOMPARE(Data::logs().copy, 1 + EXCEPT_CALL_COPY_CTOR); // copy value in std::exception_ptr
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
@ -183,7 +249,7 @@ void tst_benchmark::errorReject()
}).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // copy value to the promise data
QCOMPARE(Data::logs().copy, 1 + EXCEPT_CALL_COPY_CTOR); // copy value to the promise data
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
@ -199,7 +265,7 @@ void tst_benchmark::errorThen()
}).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // (initial) copy value in std::exception_ptr
QCOMPARE(Data::logs().copy, 1 + 2 * EXCEPT_CALL_COPY_CTOR); // (initial) copy value in std::exception_ptr
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
QCOMPARE(value, 42);
@ -214,7 +280,7 @@ void tst_benchmark::errorThen()
}).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // (initial) copy value in std::exception_ptr
QCOMPARE(Data::logs().copy, 1 + 4 * EXCEPT_CALL_COPY_CTOR); // (initial) copy value in std::exception_ptr
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
QCOMPARE(value, 42);

View File

@ -0,0 +1,5 @@
include(../qtpromise.pri)
DEFINES -= QT_DEPRECATED_WARNINGS
gcc:QMAKE_CXXFLAGS += -Wno-deprecated-declarations
msvc:QMAKE_CXXFLAGS -= -wd4996

View File

@ -0,0 +1,3 @@
TEMPLATE = subdirs
SUBDIRS += \
helpers

View File

@ -0,0 +1,4 @@
TEMPLATE = subdirs
SUBDIRS += \
qpromise \
qpromiseall

View File

@ -0,0 +1,5 @@
QT += concurrent
TARGET = tst_deprecations_helpers_qpromise
SOURCES += $$PWD/tst_qpromise.cpp
include(../../deprecations.pri)

View File

@ -0,0 +1,268 @@
#include "../../../shared/data.h"
#include "../../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtConcurrent>
#include <QtTest>
// STL
#include <memory>
using namespace QtPromise;
class tst_deprecations_helpers_qpromise : public QObject
{
Q_OBJECT
private Q_SLOTS:
void value();
void noValue();
void moveRValue();
void copyLValue();
void qtSharedPtr();
void stdSharedPtr();
void typedPromise();
void voidPromise();
void typedFuture();
void voidFuture();
};
QTEST_MAIN(tst_deprecations_helpers_qpromise)
#include "tst_qpromise.moc"
void tst_deprecations_helpers_qpromise::value()
{
int v0 = 42;
const int v1 = 42;
auto p0 = qPromise(42);
auto p1 = qPromise(v0);
auto p2 = qPromise(v1);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<int>>::value));
for (const auto& p : {p0, p1, p2}) {
QCOMPARE(p.isFulfilled(), true);
}
for (const auto& p : {p0, p1, p2}) {
QCOMPARE(waitForValue(p, -1), 42);
}
}
void tst_deprecations_helpers_qpromise::noValue()
{
auto p = qPromise();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}
void tst_deprecations_helpers_qpromise::moveRValue()
{
Data::logs().reset();
{
auto p = qPromise(Data(42)).wait();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<Data>>::value));
}
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 0);
QCOMPARE(Data::logs().move, 1);
QCOMPARE(Data::logs().refs, 0);
}
void tst_deprecations_helpers_qpromise::copyLValue()
{
Data::logs().reset();
{
Data value(42);
auto p = qPromise(value).wait();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<Data>>::value));
}
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1);
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_deprecations_helpers_qpromise::qtSharedPtr()
{
Data::logs().reset();
QWeakPointer<Data> wptr;
{
QSharedPointer<Data> sptr0(new Data(42));
const QSharedPointer<Data> sptr1 = sptr0;
auto p0 = qPromise(QSharedPointer<Data>(new Data(42)));
auto p1 = qPromise(sptr0);
auto p2 = qPromise(sptr1);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<QSharedPointer<Data>>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<QSharedPointer<Data>>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<QSharedPointer<Data>>>::value));
QCOMPARE(waitForValue(p1, QSharedPointer<Data>()), sptr0);
QCOMPARE(waitForValue(p2, QSharedPointer<Data>()), sptr1);
wptr = sptr0;
QCOMPARE(wptr.isNull(), false);
QCOMPARE(Data::logs().refs, 2);
}
QCOMPARE(wptr.isNull(), true);
QCOMPARE(Data::logs().ctor, 2);
QCOMPARE(Data::logs().copy, 0);
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_deprecations_helpers_qpromise::stdSharedPtr()
{
Data::logs().reset();
std::weak_ptr<Data> wptr;
{
std::shared_ptr<Data> sptr0(new Data(42));
const std::shared_ptr<Data> sptr1 = sptr0;
auto p0 = qPromise(std::shared_ptr<Data>(new Data(42)));
auto p1 = qPromise(sptr0);
auto p2 = qPromise(sptr1);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<std::shared_ptr<Data>>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<std::shared_ptr<Data>>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<std::shared_ptr<Data>>>::value));
QCOMPARE(waitForValue(p1, std::shared_ptr<Data>()), sptr0);
QCOMPARE(waitForValue(p2, std::shared_ptr<Data>()), sptr1);
wptr = sptr0;
QCOMPARE(wptr.use_count(), 4l);
QCOMPARE(Data::logs().refs, 2);
}
QCOMPARE(wptr.use_count(), 0l);
QCOMPARE(Data::logs().ctor, 2);
QCOMPARE(Data::logs().copy, 0);
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
void tst_deprecations_helpers_qpromise::typedPromise()
{
auto resolver = [](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve(42);
});
};
QPromise<int> v0(resolver);
const QPromise<int> v1 = v0;
auto p0 = qPromise(QPromise<int>(resolver));
auto p1 = qPromise(v0);
auto p2 = qPromise(v1);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<int>>::value));
for (const auto& promise : {p0, p1, p2}) {
QCOMPARE(promise.isPending(), true);
}
for (const auto& promise : {p0, p1, p2}) {
QCOMPARE(waitForValue(promise, -1), 42);
}
}
void tst_deprecations_helpers_qpromise::voidPromise()
{
auto resolver = [](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve();
});
};
QPromise<void> v0(resolver);
const QPromise<void> v1 = v0;
auto p0 = qPromise(QPromise<void>(resolver));
auto p1 = qPromise(v0);
auto p2 = qPromise(v1);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<void>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<void>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<void>>::value));
for (const auto& promise : {p0, p1, p2}) {
QCOMPARE(promise.isPending(), true);
}
for (const auto& promise : {p0, p1, p2}) {
QCOMPARE(waitForValue(promise, -1, 42), 42);
}
}
void tst_deprecations_helpers_qpromise::typedFuture()
{
auto fn = [](){ return 42; };
QFuture<int> v0 = QtConcurrent::run(fn);
const QFuture<int> v1 = v0;
auto p0 = qPromise(QtConcurrent::run(fn));
auto p1 = qPromise(v0);
auto p2 = qPromise(v1);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<int>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<int>>::value));
for (const auto& promise : {p0, p1, p2}) {
QCOMPARE(promise.isPending(), true);
}
for (const auto& promise : {p0, p1, p2}) {
QCOMPARE(waitForValue(promise, -1), 42);
}
}
void tst_deprecations_helpers_qpromise::voidFuture()
{
auto fn = [](){ };
QFuture<void> v0 = QtConcurrent::run(fn);
const QFuture<void> v1 = v0;
auto p0 = qPromise(QtConcurrent::run(fn));
auto p1 = qPromise(v0);
auto p2 = qPromise(v1);
Q_STATIC_ASSERT((std::is_same<decltype(p0), QPromise<void>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1), QPromise<void>>::value));
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<void>>::value));
for (const auto& promise : {p0, p1, p2}) {
QCOMPARE(promise.isPending(), true);
}
for (const auto& promise : {p0, p1, p2}) {
QCOMPARE(waitForValue(promise, -1, 42), 42);
}
}

View File

@ -0,0 +1,4 @@
TARGET = tst_deprecations_helpers_qpromiseall
SOURCES += $$PWD/tst_qpromiseall.cpp
include(../../deprecations.pri)

View File

@ -0,0 +1,220 @@
#include "../../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_deprecations_helpers_qpromiseall : public QObject
{
Q_OBJECT
private Q_SLOTS:
void emptySequence();
void emptySequence_void();
void allPromisesSucceed();
void allPromisesSucceed_void();
void atLeastOnePromiseReject();
void atLeastOnePromiseReject_void();
void preserveOrder();
void sequenceTypes();
void sequenceTypes_void();
};
QTEST_MAIN(tst_deprecations_helpers_qpromiseall)
#include "tst_qpromiseall.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
Q_STATIC_ASSERT((std::is_same<typename Sequence::value_type, QPromise<int>>::value));
static void exec()
{
Sequence promises{
QtPromise::resolve(42),
QtPromise::resolve(43),
QtPromise::resolve(44)
};
promises.push_back(QtPromise::resolve(45));
promises.insert(++promises.begin(), QtPromise::resolve(46));
promises.pop_back();
auto p = qPromiseAll(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 46, 43, 44}));
}
};
template <template <typename, typename...> class Sequence, typename ...Args>
struct SequenceTester<Sequence<QPromise<void>, Args...>>
{
static void exec()
{
Sequence<QPromise<void>, Args...> promises{
QtPromise::resolve(),
QtPromise::resolve(),
QtPromise::resolve()
};
promises.push_back(QtPromise::resolve());
promises.insert(++promises.begin(), QtPromise::resolve());
promises.pop_back();
auto p = qPromiseAll(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}
};
} // anonymous namespace
void tst_deprecations_helpers_qpromiseall::emptySequence()
{
auto p = qPromiseAll(QVector<QPromise<int>>());
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({}));
}
void tst_deprecations_helpers_qpromiseall::emptySequence_void()
{
auto p = qPromiseAll(QVector<QPromise<void>>());
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}
void tst_deprecations_helpers_qpromiseall::allPromisesSucceed()
{
auto p0 = QtPromise::resolve(42);
auto p1 = QtPromise::resolve(44);
auto p2 = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve(43);
});
});
auto p = qPromiseAll(QVector<QPromise<int>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QCOMPARE(p2.isFulfilled(), true);
}
void tst_deprecations_helpers_qpromiseall::allPromisesSucceed_void()
{
auto p0 = QtPromise::resolve();
auto p1 = QtPromise::resolve();
auto p2 = QPromise<void>([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve();
});
});
auto p = qPromiseAll(QVector<QPromise<void>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p2.isFulfilled(), true);
}
void tst_deprecations_helpers_qpromiseall::atLeastOnePromiseReject()
{
auto p0 = QtPromise::resolve(42);
auto p1 = QtPromise::resolve(44);
auto p2 = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=](){
reject(QString("foo"));
});
});
auto p = qPromiseAll(QVector<QPromise<int>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p2.isRejected(), true);
}
void tst_deprecations_helpers_qpromiseall::atLeastOnePromiseReject_void()
{
auto p0 = QtPromise::resolve();
auto p1 = QtPromise::resolve();
auto p2 = QPromise<void>([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=](){
reject(QString("foo"));
});
});
auto p = qPromiseAll(QVector<QPromise<void>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p2.isRejected(), true);
}
void tst_deprecations_helpers_qpromiseall::preserveOrder()
{
auto p0 = QtPromise::resolve(42).delay(500);
auto p1 = QtPromise::resolve(43).delay(100);
auto p2 = QtPromise::resolve(44).delay(250);
auto p = qPromiseAll(QVector<QPromise<int>>{p0, p1, p2});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isFulfilled(), true);
}
// QVector::push_back/append isn't supported since it requires a default
// constructor (see https://github.com/simonbrunel/qtpromise/issues/3)
void tst_deprecations_helpers_qpromiseall::sequenceTypes()
{
SequenceTester<QList<QPromise<int>>>::exec();
//SequenceTester<QVector<QPromise<int>>>::exec();
SequenceTester<std::list<QPromise<int>>>::exec();
SequenceTester<std::vector<QPromise<int>>>::exec();
}
void tst_deprecations_helpers_qpromiseall::sequenceTypes_void()
{
SequenceTester<QList<QPromise<void>>>::exec();
//SequenceTester<QVector<QPromise<void>>>::exec();
SequenceTester<std::list<QPromise<void>>>::exec();
SequenceTester<std::vector<QPromise<void>>>::exec();
}

View File

@ -0,0 +1,4 @@
TARGET = tst_deprecations_qpromise_all
SOURCES += $$PWD/tst_all.cpp
include(../../deprecations.pri)

View File

@ -0,0 +1,229 @@
#include "../../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_deprecations_qpromise_all : public QObject
{
Q_OBJECT
private Q_SLOTS:
void emptySequence();
void emptySequence_void();
void allPromisesSucceed();
void allPromisesSucceed_void();
void atLeastOnePromiseReject();
void atLeastOnePromiseReject_void();
void preserveOrder();
void sequenceTypes();
void sequenceTypes_void();
};
QTEST_MAIN(tst_deprecations_qpromise_all)
#include "tst_all.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
Q_STATIC_ASSERT((std::is_same<typename Sequence::value_type, QPromise<int>>::value));
static void exec()
{
Sequence promises{
QtPromise::resolve(42),
QtPromise::resolve(43),
QtPromise::resolve(44)
};
promises.push_back(QtPromise::resolve(45));
promises.insert(++promises.begin(), QtPromise::resolve(46));
promises.pop_back();
auto p = QPromise<int>::all(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 46, 43, 44}));
}
};
template <template <typename, typename...> class Sequence, typename ...Args>
struct SequenceTester<Sequence<QPromise<void>, Args...>>
{
static void exec()
{
Sequence<QPromise<void>, Args...> promises{
QtPromise::resolve(),
QtPromise::resolve(),
QtPromise::resolve()
};
promises.push_back(QtPromise::resolve());
promises.insert(++promises.begin(), QtPromise::resolve());
promises.pop_back();
auto p = QPromise<void>::all(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}
};
} // anonymous namespace
void tst_deprecations_qpromise_all::emptySequence()
{
auto p = QPromise<int>::all({});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>{});
}
void tst_deprecations_qpromise_all::emptySequence_void()
{
auto p = QPromise<void>::all({});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}
void tst_deprecations_qpromise_all::allPromisesSucceed()
{
auto p0 = QtPromise::resolve(42);
auto p1 = QtPromise::resolve(44);
auto p2 = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve(43);
});
});
auto p = QPromise<int>::all({p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QCOMPARE(p2.isFulfilled(), true);
}
void tst_deprecations_qpromise_all::allPromisesSucceed_void()
{
auto p0 = QtPromise::resolve();
auto p1 = QtPromise::resolve();
auto p2 = QPromise<void>([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve();
});
});
auto p = QPromise<void>::all({p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p2.isFulfilled(), true);
}
void tst_deprecations_qpromise_all::atLeastOnePromiseReject()
{
auto p0 = QtPromise::resolve(42);
auto p1 = QtPromise::resolve(44);
auto p2 = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=](){
reject(QString("foo"));
});
});
auto p = QPromise<int>::all({p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p2.isRejected(), true);
}
void tst_deprecations_qpromise_all::atLeastOnePromiseReject_void()
{
auto p0 = QtPromise::resolve();
auto p1 = QtPromise::resolve();
auto p2 = QPromise<void>([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=](){
reject(QString("foo"));
});
});
auto p = QPromise<void>::all({p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p2.isRejected(), true);
}
void tst_deprecations_qpromise_all::preserveOrder()
{
auto p0 = QtPromise::resolve(42).delay(500);
auto p1 = QtPromise::resolve(43).delay(100);
auto p2 = QtPromise::resolve(44).delay(250);
auto p = QPromise<int>::all({p0, p1, p2});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isFulfilled(), true);
}
// QVector::push_back/append isn't supported since it requires a default
// constructor (see https://github.com/simonbrunel/qtpromise/issues/3)
void tst_deprecations_qpromise_all::sequenceTypes()
{
SequenceTester<QList<QPromise<int>>>::exec();
//SequenceTester<QVector<QPromise<int>>>::exec();
SequenceTester<std::list<QPromise<int>>>::exec();
SequenceTester<std::vector<QPromise<int>>>::exec();
}
void tst_deprecations_qpromise_all::sequenceTypes_void()
{
SequenceTester<QList<QPromise<void>>>::exec();
//SequenceTester<QVector<QPromise<void>>>::exec();
SequenceTester<std::list<QPromise<void>>>::exec();
SequenceTester<std::vector<QPromise<void>>>::exec();
}

View File

@ -0,0 +1,3 @@
TEMPLATE = subdirs
SUBDIRS += \
all

View File

@ -0,0 +1,5 @@
QT += concurrent
TARGET = tst_exceptions
SOURCES += $$PWD/tst_exceptions.cpp
include(../qtpromise.pri)

View File

@ -0,0 +1,58 @@
#include "../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtConcurrent>
#include <QtTest>
using namespace QtPromise;
class tst_exceptions : public QObject
{
Q_OBJECT
private Q_SLOTS:
void canceled();
void context();
void timeout();
void undefined();
}; // class tst_exceptions
QTEST_MAIN(tst_exceptions)
#include "tst_exceptions.moc"
namespace {
template <class E>
void verify()
{
auto p = QtPromise::resolve(QtConcurrent::run([]() { throw E(); }));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForRejected<E>(p), true);
QCOMPARE(p.isRejected(), true);
}
} // anonymous namespace
void tst_exceptions::canceled()
{
verify<QPromiseCanceledException>();
}
void tst_exceptions::context()
{
verify<QPromiseContextException>();
}
void tst_exceptions::timeout()
{
verify<QPromiseTimeoutException>();
}
void tst_exceptions::undefined()
{
verify<QPromiseUndefinedException>();
}

View File

@ -2,4 +2,4 @@ QT += concurrent
TARGET = tst_future
SOURCES += $$PWD/tst_future.cpp
include(../tests.pri)
include(../qtpromise.pri)

View File

@ -52,11 +52,11 @@ QTEST_MAIN(tst_future)
void tst_future::fulfilled()
{
int result = -1;
auto p = qPromise(QtConcurrent::run([]() {
auto p = QtPromise::resolve(QtConcurrent::run([]() {
return 42;
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
p.then([&](int res) {
@ -70,9 +70,9 @@ void tst_future::fulfilled()
void tst_future::fulfilled_void()
{
int result = -1;
auto p = qPromise(QtConcurrent::run([]() { }));
auto p = QtPromise::resolve(QtConcurrent::run([]() { }));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
p.then([&]() {
@ -86,12 +86,12 @@ void tst_future::fulfilled_void()
void tst_future::rejected()
{
QString error;
auto p = qPromise(QtConcurrent::run([]() {
auto p = QtPromise::resolve(QtConcurrent::run([]() {
throw MyException("foo");
return 42;
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
p.fail([&](const MyException& e) {
@ -106,11 +106,11 @@ void tst_future::rejected()
void tst_future::rejected_void()
{
QString error;
auto p = qPromise(QtConcurrent::run([]() {
auto p = QtPromise::resolve(QtConcurrent::run([]() {
throw MyException("foo");
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
@ -125,12 +125,12 @@ void tst_future::rejected_void()
void tst_future::unhandled()
{
QString error;
auto p = qPromise(QtConcurrent::run([]() {
auto p = QtPromise::resolve(QtConcurrent::run([]() {
throw QString("foo");
return 42;
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
@ -149,11 +149,11 @@ void tst_future::unhandled()
void tst_future::unhandled_void()
{
QString error;
auto p = qPromise(QtConcurrent::run([]() {
auto p = QtPromise::resolve(QtConcurrent::run([]() {
throw QString("foo");
}));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
p.fail([&](const QString& err) {
@ -169,7 +169,7 @@ void tst_future::unhandled_void()
void tst_future::canceled()
{
QString error;
auto p = qPromise(QFuture<int>()); // Constructs an empty, canceled future.
auto p = QtPromise::resolve(QFuture<int>()); // Constructs an empty, canceled future.
QCOMPARE(p.isPending(), true);
@ -185,7 +185,7 @@ void tst_future::canceled()
void tst_future::canceled_void()
{
QString error;
auto p = qPromise(QFuture<void>()); // Constructs an empty, canceled future.
auto p = QtPromise::resolve(QFuture<void>()); // Constructs an empty, canceled future.
QCOMPARE(p.isPending(), true);
@ -200,7 +200,7 @@ void tst_future::canceled_void()
void tst_future::canceledFromThread()
{
QString error;
auto p = qPromise(QtConcurrent::run([]() {
auto p = QtPromise::resolve(QtConcurrent::run([]() {
throw QPromiseCanceledException();
}));
@ -217,7 +217,7 @@ void tst_future::canceledFromThread()
void tst_future::then()
{
QString result;
auto input = qPromise(42);
auto input = QtPromise::resolve(42);
auto output = input.then([](int res) {
return QtConcurrent::run([=]() {
return QString("foo%1").arg(res);
@ -238,7 +238,7 @@ void tst_future::then()
void tst_future::then_void()
{
QString result;
auto input = qPromise();
auto input = QtPromise::resolve();
auto output = input.then([&]() {
return QtConcurrent::run([&]() {
result = "foo";
@ -261,9 +261,9 @@ void tst_future::fail()
QString result;
auto input = QPromise<QString>::reject(MyException("bar"));
auto output = input.fail([](const MyException& e) {
return QtConcurrent::run([=]() {
return QString("foo") + e.error();
});
return QtConcurrent::run([](const QString& error) {
return QString("foo%1").arg(error);
}, e.error());
});
QCOMPARE(input.isRejected(), true);
@ -282,9 +282,9 @@ void tst_future::fail_void()
QString result;
auto input = QPromise<void>::reject(MyException("bar"));
auto output = input.fail([&](const MyException& e) {
return QtConcurrent::run([&]() {
result = e.error();
});
return QtConcurrent::run([&](const QString& error) {
result = error;
}, e.error());
});
QCOMPARE(input.isRejected(), true);
@ -300,14 +300,14 @@ void tst_future::fail_void()
void tst_future::finally()
{
auto input = qPromise(42);
auto input = QtPromise::resolve(42);
auto output = input.finally([]() {
return QtConcurrent::run([]() {
return QString("foo");
});
});
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int>>::value));
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isPending(), true);
@ -323,14 +323,14 @@ void tst_future::finally()
void tst_future::finallyRejected()
{
auto input = qPromise(42);
auto input = QtPromise::resolve(42);
auto output = input.finally([]() {
return QtConcurrent::run([]() {
throw MyException("foo");
});
});
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int>>::value));
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isPending(), true);

View File

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

View File

@ -0,0 +1,220 @@
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_helpers_all : public QObject
{
Q_OBJECT
private Q_SLOTS:
void emptySequence();
void emptySequence_void();
void allPromisesSucceed();
void allPromisesSucceed_void();
void atLeastOnePromiseReject();
void atLeastOnePromiseReject_void();
void preserveOrder();
void sequenceTypes();
void sequenceTypes_void();
};
QTEST_MAIN(tst_helpers_all)
#include "tst_all.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
Q_STATIC_ASSERT((std::is_same<typename Sequence::value_type, QPromise<int>>::value));
static void exec()
{
Sequence promises{
QtPromise::resolve(42),
QtPromise::resolve(43),
QtPromise::resolve(44)
};
promises.push_back(QtPromise::resolve(45));
promises.insert(++promises.begin(), QtPromise::resolve(46));
promises.pop_back();
auto p = QtPromise::all(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 46, 43, 44}));
}
};
template <template <typename, typename...> class Sequence, typename ...Args>
struct SequenceTester<Sequence<QPromise<void>, Args...>>
{
static void exec()
{
Sequence<QPromise<void>, Args...> promises{
QtPromise::resolve(),
QtPromise::resolve(),
QtPromise::resolve()
};
promises.push_back(QtPromise::resolve());
promises.insert(++promises.begin(), QtPromise::resolve());
promises.pop_back();
auto p = QtPromise::all(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}
};
} // anonymous namespace
void tst_helpers_all::emptySequence()
{
auto p = QtPromise::all(QVector<QPromise<int>>());
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({}));
}
void tst_helpers_all::emptySequence_void()
{
auto p = QtPromise::all(QVector<QPromise<void>>());
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}
void tst_helpers_all::allPromisesSucceed()
{
auto p0 = QtPromise::resolve(42);
auto p1 = QtPromise::resolve(44);
auto p2 = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve(43);
});
});
auto p = QtPromise::all(QVector<QPromise<int>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QCOMPARE(p2.isFulfilled(), true);
}
void tst_helpers_all::allPromisesSucceed_void()
{
auto p0 = QtPromise::resolve();
auto p1 = QtPromise::resolve();
auto p2 = QPromise<void>([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=](){
resolve();
});
});
auto p = QtPromise::all(QVector<QPromise<void>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p2.isFulfilled(), true);
}
void tst_helpers_all::atLeastOnePromiseReject()
{
auto p0 = QtPromise::resolve(42);
auto p1 = QtPromise::resolve(44);
auto p2 = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=](){
reject(QString("foo"));
});
});
auto p = QtPromise::all(QVector<QPromise<int>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p2.isRejected(), true);
}
void tst_helpers_all::atLeastOnePromiseReject_void()
{
auto p0 = QtPromise::resolve();
auto p1 = QtPromise::resolve();
auto p2 = QPromise<void>([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=](){
reject(QString("foo"));
});
});
auto p = QtPromise::all(QVector<QPromise<void>>{p0, p2, p1});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p2.isRejected(), true);
}
void tst_helpers_all::preserveOrder()
{
auto p0 = QtPromise::resolve(42).delay(500);
auto p1 = QtPromise::resolve(43).delay(100);
auto p2 = QtPromise::resolve(44).delay(250);
auto p = QtPromise::all(QVector<QPromise<int>>{p0, p1, p2});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(p2.isPending(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(p2.isFulfilled(), true);
}
// QVector::push_back/append isn't supported since it requires a default
// constructor (see https://github.com/simonbrunel/qtpromise/issues/3)
void tst_helpers_all::sequenceTypes()
{
SequenceTester<QList<QPromise<int>>>::exec();
//SequenceTester<QVector<QPromise<int>>>::exec();
SequenceTester<std::list<QPromise<int>>>::exec();
SequenceTester<std::vector<QPromise<int>>>::exec();
}
void tst_helpers_all::sequenceTypes_void()
{
SequenceTester<QList<QPromise<void>>>::exec();
//SequenceTester<QVector<QPromise<void>>>::exec();
SequenceTester<std::list<QPromise<void>>>::exec();
SequenceTester<std::vector<QPromise<void>>>::exec();
}

View File

@ -0,0 +1,5 @@
QT += concurrent
TARGET = tst_helpers_attempt
SOURCES += $$PWD/tst_attempt.cpp
include(../../qtpromise.pri)

View File

@ -0,0 +1,98 @@
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtConcurrent>
#include <QtTest>
// STL
#include <memory>
using namespace QtPromise;
class tst_helpers_attempt : public QObject
{
Q_OBJECT
private Q_SLOTS:
void voidResult();
void typedResult();
void futureResult();
void promiseResult();
void functorThrows();
void callWithParams();
};
QTEST_MAIN(tst_helpers_attempt)
#include "tst_attempt.moc"
void tst_helpers_attempt::voidResult()
{
auto p = QtPromise::attempt([]() {});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}
void tst_helpers_attempt::typedResult()
{
auto p = QtPromise::attempt([]() {
return QString("foo");
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, QString()), QString("foo"));
}
void tst_helpers_attempt::futureResult()
{
auto p = QtPromise::attempt([]() {
return QtConcurrent::run([]() {
return QString("foo");
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QString()), QString("foo"));
}
void tst_helpers_attempt::promiseResult()
{
auto p = QtPromise::attempt([]() {
return QtPromise::resolve(42).delay(200);
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), 42);
}
void tst_helpers_attempt::functorThrows()
{
auto p = QtPromise::attempt([]() {
if (true) {
throw QString("bar");
}
return 42;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForError(p, QString()), QString("bar"));
}
void tst_helpers_attempt::callWithParams()
{
auto p = QtPromise::attempt([&](int i, const QString& s) {
return QString("%1:%2").arg(i).arg(s);
}, 42, "foo");
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForValue(p, QString()), QString("42:foo"));
}

View File

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

View File

@ -0,0 +1,211 @@
#include "../../shared/object.h"
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_helpers_connect : public QObject
{
Q_OBJECT
private Q_SLOTS:
// connect(QObject* sender, Signal resolver)
void resolveOneSenderNoArg();
void resolveOneSenderOneArg();
void resolveOneSenderManyArgs();
// connect(QObject* sender, Signal resolver, Signal rejecter)
void rejectOneSenderNoArg();
void rejectOneSenderOneArg();
void rejectOneSenderManyArgs();
void rejectOneSenderDestroyed();
// connect(QObject* s0, Signal resolver, QObject* s1, Signal rejecter)
void rejectTwoSendersNoArg();
void rejectTwoSendersOneArg();
void rejectTwoSendersManyArgs();
void rejectTwoSendersDestroyed();
};
QTEST_MAIN(tst_helpers_connect)
#include "tst_connect.moc"
void tst_helpers_connect::resolveOneSenderNoArg()
{
Object sender;
QtPromisePrivate::qtpromise_defer([&]() {
Q_EMIT sender.noArgSignal();
});
auto p = QtPromise::connect(&sender, &Object::noArgSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(sender.hasConnections(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(sender.hasConnections(), false);
}
void tst_helpers_connect::resolveOneSenderOneArg()
{
Object sender;
QtPromisePrivate::qtpromise_defer([&]() {
Q_EMIT sender.oneArgSignal("foo");
});
auto p = QtPromise::connect(&sender, &Object::oneArgSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(sender.hasConnections(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, QString()), QString("foo"));
QCOMPARE(sender.hasConnections(), false);
}
void tst_helpers_connect::resolveOneSenderManyArgs()
{
Object sender;
QtPromisePrivate::qtpromise_defer([&]() {
Q_EMIT sender.twoArgsSignal(42, "foo");
});
auto p = QtPromise::connect(&sender, &Object::twoArgsSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(sender.hasConnections(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(sender.hasConnections(), false);
}
void tst_helpers_connect::rejectOneSenderNoArg()
{
Object sender;
QtPromisePrivate::qtpromise_defer([&]() {
Q_EMIT sender.noArgSignal();
});
auto p = QtPromise::connect(&sender, &Object::oneArgSignal, &Object::noArgSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString>>::value));
QCOMPARE(sender.hasConnections(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForRejected<QPromiseUndefinedException>(p), true);
QCOMPARE(sender.hasConnections(), false);
}
void tst_helpers_connect::rejectOneSenderOneArg()
{
Object sender;
QtPromisePrivate::qtpromise_defer([&]() {
Q_EMIT sender.oneArgSignal("bar");
});
auto p = QtPromise::connect(&sender, &Object::noArgSignal, &Object::oneArgSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(sender.hasConnections(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(sender.hasConnections(), false);
}
void tst_helpers_connect::rejectOneSenderManyArgs()
{
Object sender;
QtPromisePrivate::qtpromise_defer([&]() {
Q_EMIT sender.twoArgsSignal(42, "bar");
});
auto p = QtPromise::connect(&sender, &Object::noArgSignal, &Object::twoArgsSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(sender.hasConnections(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, -1), 42);
QCOMPARE(sender.hasConnections(), false);
}
void tst_helpers_connect::rejectOneSenderDestroyed()
{
Object* sender = new Object();
QtPromisePrivate::qtpromise_defer([&]() {
sender->deleteLater();
});
auto p = QtPromise::connect(sender, &Object::twoArgsSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForRejected<QPromiseContextException>(p), true);
}
void tst_helpers_connect::rejectTwoSendersNoArg()
{
Object s0, s1;
QtPromisePrivate::qtpromise_defer([&]() {
Q_EMIT s1.noArgSignal();
});
auto p = QtPromise::connect(&s0, &Object::noArgSignal, &s1, &Object::noArgSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(s0.hasConnections(), true);
QCOMPARE(s1.hasConnections(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForRejected<QPromiseUndefinedException>(p), true);
QCOMPARE(s0.hasConnections(), false);
QCOMPARE(s1.hasConnections(), false);
}
void tst_helpers_connect::rejectTwoSendersOneArg()
{
Object s0, s1;
QtPromisePrivate::qtpromise_defer([&]() {
Q_EMIT s1.oneArgSignal("bar");
});
auto p = QtPromise::connect(&s0, &Object::noArgSignal, &s1, &Object::oneArgSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(s0.hasConnections(), true);
QCOMPARE(s1.hasConnections(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(s0.hasConnections(), false);
QCOMPARE(s1.hasConnections(), false);
}
void tst_helpers_connect::rejectTwoSendersManyArgs()
{
Object s0, s1;
QtPromisePrivate::qtpromise_defer([&]() {
Q_EMIT s1.twoArgsSignal(42, "bar");
});
auto p = QtPromise::connect(&s0, &Object::noArgSignal, &s1, &Object::twoArgsSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(s0.hasConnections(), true);
QCOMPARE(s1.hasConnections(), true);
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, -1), 42);
QCOMPARE(s0.hasConnections(), false);
QCOMPARE(s1.hasConnections(), false);
}
void tst_helpers_connect::rejectTwoSendersDestroyed()
{
Object* s0 = new Object();
Object* s1 = new Object();
QtPromisePrivate::qtpromise_defer([&]() {
QObject::connect(s1, &QObject::destroyed, [&]() {
// Let's first delete s1, then resolve s0 and make sure
// we don't reject when the rejecter object is destroyed.
Q_EMIT s0->noArgSignal();
});
s1->deleteLater();
});
auto p = QtPromise::connect(s0, &Object::noArgSignal, s1, &Object::twoArgsSignal);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void>>::value));
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
}

View File

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

View File

@ -0,0 +1,153 @@
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_helpers_each : public QObject
{
Q_OBJECT
private Q_SLOTS:
void emptySequence();
void preserveValues();
void ignoreResult();
void delayedFulfilled();
void delayedRejected();
void functorThrows();
void functorArguments();
void sequenceTypes();
};
QTEST_MAIN(tst_helpers_each)
#include "tst_each.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
static void exec()
{
QVector<int> values;
auto p = QtPromise::each(Sequence{42, 43, 44}, [&](int v, int i) {
values << i << v;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<Sequence>>::value));
QCOMPARE(waitForValue(p, Sequence()), Sequence({42, 43, 44}));
QCOMPARE(values, QVector<int>({0, 42, 1, 43, 2, 44}));
}
};
} // anonymous namespace
void tst_helpers_each::emptySequence()
{
QVector<int> values;
auto p = QtPromise::each(QVector<int>{}, [&](int v, ...) {
values << v;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>());
QCOMPARE(values, QVector<int>({}));
}
void tst_helpers_each::preserveValues()
{
QVector<int> values;
auto p = QtPromise::each(QVector<int>{42, 43, 44}, [&](int v, ...) {
values << v + 1;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QCOMPARE(values, QVector<int>({43, 44, 45}));
}
void tst_helpers_each::ignoreResult()
{
QVector<int> values;
auto p = QtPromise::each(QVector<int>{42, 43, 44}, [&](int v, ...) {
values << v + 1;
return "Foo";
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QCOMPARE(values, QVector<int>({43, 44, 45}));
}
void tst_helpers_each::delayedFulfilled()
{
QMap<int, int> values;
auto p = QtPromise::each(QVector<int>{42, 43, 44}, [&](int v, int index) {
return QPromise<int>([&](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=, &values]() {
values[v] = index;
resolve(42);
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QMap<int, int> expected{{42, 0}, {43, 1}, {44, 2}};
QCOMPARE(values, expected);
}
void tst_helpers_each::delayedRejected()
{
auto p = QtPromise::each(QVector<int>{42, 43, 44}, [](int v, ...) {
return QPromise<int>([&](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
if (v == 43) {
reject(QString("foo"));
}
resolve(v);
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_helpers_each::functorThrows()
{
auto p = QtPromise::each(QVector<int>{42, 43, 44}, [](int v, ...) {
if (v == 44) {
throw QString("foo");
}
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_helpers_each::functorArguments()
{
QVector<int> values;
auto p = QtPromise::each(QVector<int>{42, 43, 44}, [&](int v, int i) {
values << i << v;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 43, 44}));
QCOMPARE(values, QVector<int>({0, 42, 1, 43, 2, 44}));
}
void tst_helpers_each::sequenceTypes()
{
SequenceTester<QList<int>>::exec();
SequenceTester<QVector<int>>::exec();
SequenceTester<std::list<int>>::exec();
SequenceTester<std::vector<int>>::exec();
}

View File

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

View File

@ -0,0 +1,146 @@
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_helpers_filter : public QObject
{
Q_OBJECT
private Q_SLOTS:
void emptySequence();
void filterValues();
void delayedFulfilled();
void delayedRejected();
void functorThrows();
void functorArguments();
void preserveOrder();
void sequenceTypes();
};
QTEST_MAIN(tst_helpers_filter)
#include "tst_filter.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
static void exec()
{
auto inputs = Sequence{42, 43, 44, 45, 46, 47, 48, 49, 50, 51};
auto p = QtPromise::filter(inputs, [](int v, ...) {
return v % 3 == 0;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<Sequence>>::value));
QCOMPARE(waitForValue(p, Sequence()), Sequence({42, 45, 48, 51}));
}
};
} // anonymous namespace
#include <QtConcurrent/QtConcurrent>
void tst_helpers_filter::emptySequence()
{
auto p = QtPromise::filter(QVector<int>{}, [](int v, ...) {
return v % 2 == 0;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>{});
}
void tst_helpers_filter::filterValues()
{
auto p = QtPromise::filter(QVector<int>{42, 43, 44}, [](int v, ...) {
return v % 2 == 0;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 44}));
}
void tst_helpers_filter::delayedFulfilled()
{
auto p = QtPromise::filter(QVector<int>{42, 43, 44}, [](int v, ...) {
return QPromise<bool>([&](const QPromiseResolve<bool>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(v % 2 == 0);
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 44}));
}
void tst_helpers_filter::delayedRejected()
{
auto p = QtPromise::filter(QVector<int>{42, 43, 44}, [](int v, ...) {
return QPromise<bool>([&](
const QPromiseResolve<bool>& resolve,
const QPromiseReject<bool>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
if (v == 44) {
reject(QString("foo"));
}
resolve(true);
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_helpers_filter::functorThrows()
{
auto p = QtPromise::filter(QVector<int>{42, 43, 44}, [](int v, ...) {
if (v == 44) {
throw QString("foo");
}
return true;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_helpers_filter::functorArguments()
{
QMap<int, int> args;
auto p = QtPromise::filter(QVector<int>{42, 43, 44}, [&](int v, int i) {
args[v] = i;
return i % 2 == 0;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({42, 44}));
QMap<int, int> expected{{42, 0}, {43, 1}, {44, 2}};
QCOMPARE(args, expected);
}
void tst_helpers_filter::preserveOrder()
{
auto p = QtPromise::filter(QVector<int>{500, 100, 300, 250, 400}, [](int v, ...) {
return QPromise<bool>::resolve(v > 200).delay(v);
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({500, 300, 250, 400}));
}
void tst_helpers_filter::sequenceTypes()
{
SequenceTester<QList<int>>::exec();
SequenceTester<QVector<int>>::exec();
SequenceTester<std::list<int>>::exec();
SequenceTester<std::vector<int>>::exec();
}

View File

@ -0,0 +1,11 @@
TEMPLATE = subdirs
SUBDIRS += \
all \
attempt \
connect \
each \
filter \
map \
reduce \
reject \
resolve

View File

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

View File

@ -0,0 +1,150 @@
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_helpers_map : public QObject
{
Q_OBJECT
private Q_SLOTS:
void emptySequence();
void modifyValues();
void convertValues();
void delayedFulfilled();
void delayedRejected();
void functorThrows();
void functorArguments();
void preserveOrder();
void sequenceTypes();
};
QTEST_MAIN(tst_helpers_map)
#include "tst_map.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
static void exec()
{
auto p = QtPromise::map(Sequence{42, 43, 44}, [](int v, ...) {
return QString::number(v + 1);
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<QString>>>::value));
QCOMPARE(waitForValue(p, QVector<QString>()), QVector<QString>({"43", "44", "45"}));
}
};
} // anonymous namespace
void tst_helpers_map::emptySequence()
{
auto p = QtPromise::map(QVector<int>{}, [](int v, ...) {
return v + 1;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({}));
}
void tst_helpers_map::modifyValues()
{
auto p = QtPromise::map(QVector<int>{42, 43, 44}, [](int v, ...) {
return v + 1;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({43, 44, 45}));
}
void tst_helpers_map::convertValues()
{
auto p = QtPromise::map(QVector<int>{42, 43, 44}, [](int v, ...) {
return QString::number(v + 1);
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<QString>>>::value));
QCOMPARE(waitForValue(p, QVector<QString>()), QVector<QString>({"43", "44", "45"}));
}
void tst_helpers_map::delayedFulfilled()
{
auto p = QtPromise::map(QVector<int>{42, 43, 44}, [](int v, ...) {
return QPromise<int>([&](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(v + 1);
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({43, 44, 45}));
}
void tst_helpers_map::delayedRejected()
{
auto p = QtPromise::map(QVector<int>{42, 43, 44}, [](int v, ...) {
return QPromise<int>([&](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
if (v == 43) {
reject(QString("foo"));
}
resolve(v);
});
});
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_helpers_map::functorThrows()
{
auto p = QtPromise::map(QVector<int>{42, 43, 44}, [](int v, ...) {
if (v == 43) {
throw QString("foo");
}
return v + 1;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_helpers_map::functorArguments()
{
auto p = QtPromise::map(QVector<int>{42, 42, 42}, [](int v, int i) {
return v * i;
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({0, 42, 84}));
}
void tst_helpers_map::preserveOrder()
{
auto p = QtPromise::map(QVector<int>{500, 100, 250}, [](int v, ...) {
return QPromise<int>::resolve(v + 1).delay(v);
});
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int>>>::value));
QCOMPARE(waitForValue(p, QVector<int>()), QVector<int>({501, 101, 251}));
}
void tst_helpers_map::sequenceTypes()
{
SequenceTester<QList<int>>::exec();
SequenceTester<QVector<int>>::exec();
SequenceTester<std::list<int>>::exec();
SequenceTester<std::vector<int>>::exec();
}

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

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

View File

@ -0,0 +1,73 @@
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
// STL
#include <memory>
using namespace QtPromise;
class tst_helpers_reject : public QObject
{
Q_OBJECT
private Q_SLOTS:
void rejectWithValue();
void rejectWithQSharedPtr();
void rejectWithStdSharedPtr();
};
QTEST_MAIN(tst_helpers_reject)
#include "tst_reject.moc"
void tst_helpers_reject::rejectWithValue()
{
auto p = QPromise<int>::reject(42);
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForError(p, -1), 42);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_helpers_reject::rejectWithQSharedPtr()
{
QWeakPointer<int> wptr;
{
QSharedPointer<int> sptr(new int(42));
auto p = QPromise<int>::reject(sptr);
QCOMPARE(waitForError(p, QSharedPointer<int>()), sptr);
wptr = sptr;
sptr.reset();
QCOMPARE(wptr.isNull(), false); // "p" still holds a reference
}
QCOMPARE(wptr.isNull(), true);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_helpers_reject::rejectWithStdSharedPtr()
{
std::weak_ptr<int> wptr;
{
std::shared_ptr<int> sptr(new int(42));
auto p = QPromise<int>::reject(sptr);
QCOMPARE(waitForError(p, std::shared_ptr<int>()), sptr);
wptr = sptr;
sptr.reset();
QCOMPARE(wptr.use_count(), 1l); // "p" still holds a reference
}
QCOMPARE(wptr.use_count(), 0l);
}

View File

@ -0,0 +1,5 @@
QT += concurrent
TARGET = tst_helpers_resolve
SOURCES += $$PWD/tst_resolve.cpp
include(../../qtpromise.pri)

Some files were not shown because too many files have changed in this diff Show More