20 Commits

Author SHA1 Message Date
50216b65da - wip - 2018-03-15 10:55:45 +01: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
96 changed files with 3455 additions and 1101 deletions

12
.gitignore vendored
View File

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

View File

@ -30,3 +30,7 @@ script:
after_success:
- bash <(curl -s https://codecov.io/bash) -f coverage.info
# gitbook install .
# gitbook build . dist/docs

403
README.md
View File

@ -1,407 +1,18 @@
<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).
## 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)
* [Thread-Safety](https://qtpromise.netlify.com/qtpromise/thread-safety)
* [QtConcurrent](https://qtpromise.netlify.com/qtpromise/qtconcurrent)
* [API Reference](https://qtpromise.netlify.com/qtpromise/api-reference)
## License
QtPromise is available under the [MIT license](LICENSE).

40
book.json Normal file
View File

@ -0,0 +1,40 @@
{
"title": "QtPromise",
"description": "Promises/A+ implementation for Qt/C++",
"author": "Simon Brunel",
"gitbook": "3.2.3",
"root": "docs",
"plugins": [
"-lunr",
"-search",
"search-plus",
"anchorjs",
"edit-link",
"expand-active-chapter",
"ga",
"github"
],
"pluginsConfig": {
"anchorjs": {
"icon": "#",
"placement": "left",
"visible": "always"
},
"edit-link": {
"base": "https://github.com/simonbrunel/qtpromise/edit/master/docs"
},
"ga": {
"token": "UA-113899811-1",
"configuration": "auto"
},
"github": {
"url": "https://github.com/simonbrunel/qtpromise"
},
"theme-default": {
"showLevel": false,
"styles": {
"website": "assets/style.css"
}
}
}
}

15
docs/README.md Normal file
View File

@ -0,0 +1,15 @@
<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.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).
## QtPromise for C++
* [Getting Started](qtpromise/getting-started.md)
* [Thread-Safety](qtpromise/thread-safety.md)
* [QtConcurrent](qtpromise/qtconcurrent.md)
* [API Reference](qtpromise/api-reference.md)
## License
QtPromise is available under the [MIT license](https://github.com/simonbrunel/qtpromise/blob/master/LICENSE).

21
docs/SUMMARY.md Normal file
View File

@ -0,0 +1,21 @@
### QtPromise for C++
* [Getting Started](qtpromise/getting-started.md)
* [QtConcurrent](qtpromise/qtconcurrent.md)
* [Thread-Safety](qtpromise/thread-safety.md)
* [API Reference](qtpromise/api-reference.md)
* [QPromise](qtpromise/qpromise/constructor.md)
* [.delay](qtpromise/qpromise/delay.md)
* [.fail](qtpromise/qpromise/fail.md)
* [.finally](qtpromise/qpromise/finally.md)
* [.isFulfilled](qtpromise/qpromise/isfulfilled.md)
* [.isPending](qtpromise/qpromise/ispending.md)
* [.isRejected](qtpromise/qpromise/isrejected.md)
* [.tap](qtpromise/qpromise/tap.md)
* [.then](qtpromise/qpromise/then.md)
* [.timeout](qtpromise/qpromise/timeout.md)
* [.wait](qtpromise/qpromise/wait.md)
* [::all (static)](qtpromise/qpromise/all.md)
* [::reject (static)](qtpromise/qpromise/reject.md)
* [::resolve (static)](qtpromise/qpromise/resolve.md)
* [qPromise](qtpromise/helpers/qpromise.md)
* [qPromiseAll](qtpromise/helpers/qpromiseall.md)

15
docs/assets/style.css Normal file
View File

@ -0,0 +1,15 @@
a.anchorjs-link {
color: rgba(65, 131, 196, 0.1);
font-weight: 400;
text-decoration: none;
transition: color 100ms ease-out;
z-index: 999;
}
a.anchorjs-link:hover {
color: rgba(65, 131, 196, 1);
}
sup {
font-size: 0.75em !important;
}

View File

@ -0,0 +1,26 @@
## QPromise
### Public Members
* [`QPromise<T>::QPromise`](qpromise/constructor.md)
* [`QPromise<T>::delay`](qpromise/delay.md)
* [`QPromise<T>::fail`](qpromise/fail.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>::tap`](qpromise/tap.md)
* [`QPromise<T>::then`](qpromise/then.md)
* [`QPromise<T>::timeout`](qpromise/timeout.md)
* [`QPromise<T>::wait`](qpromise/wait.md)
### Public Static Members
* [`[static] QPromise<T>::all`](qpromise/all.md)
* [`[static] QPromise<T>::reject`](qpromise/reject.md)
* [`[static] QPromise<T>::resolve`](qpromise/resolve.md)
## Helpers
* [`qPromise`](helpers/qpromise.md)
* [`qPromiseAll`](helpers/qpromiseall.md)

View File

@ -0,0 +1,93 @@
## 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 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.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,15 @@
## `qPromise`
```
qPromise(T value) -> QPromise<R>
```
Similar to the [`QPromise<T>::resolve`](../qpromise/resolve.md) 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.md#convert)).

View File

@ -0,0 +1,16 @@
## `qPromiseAll`
```
qPromiseAll(Sequence<QPromise<T>> promises) -> QPromise<QVector<T>>
qPromiseAll(Sequence<QPromise<void>> promises) -> QPromise<void>
```
This method simply calls the appropriated [`QPromise<T>::all`](../qpromise/all.md) 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)
```

View File

@ -0,0 +1,26 @@
## `[static] QPromise<T>::all`
```
[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. 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: [`qPromiseAll`](../helpers/qpromiseall.md)

View File

@ -0,0 +1,29 @@
## `QPromise<T>::QPromise`
```
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());
}
});
});
```
> **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) {
// {...}
});
```

View File

@ -0,0 +1,14 @@
## `QPromise<T>::delay`
```
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,19 @@
## `QPromise<T>::fail`
```
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,15 @@
## `QPromise<T>::finally`
```
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,7 @@
# `QPromise<T>::isFulfilled`
```
QPromise<T>::isFulfilled() -> bool
```
Returns `true` if the promise is fulfilled, otherwise returns `false`.

View File

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

View File

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

View File

@ -0,0 +1,20 @@
## `[static] QPromise<T>::reject`
```
[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,22 @@
## `[static] QPromise<T>::resolve`
```
[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: [`qPromise`](../helpers/qpromise.md)

View File

@ -0,0 +1,18 @@
## `QPromise<T>::tap`
```
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,87 @@
## `QPromise<T>::then`
```
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
});
```
> **Note**: `onRejected` handler is optional, `output` will be rejected with the same reason as `input`.
> **Note**: 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) {
// {...}
});
```
> **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,18 @@
## `QPromise<T>::timeout`
```
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` 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,23 @@
## `QPromise<T>::wait`
```
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 = qPromise(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 @@
## QtConcurrent
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 [`qPromise`](helpers/qpromise.md) 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.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 with be rejected with [`QUnhandledException`](https://doc.qt.io/qt-5/qunhandledexception.html#details) (this restriction only applies to exceptions thrown from a QtConcurrent thread, [read more](https://doc.qt.io/qt-5/qexception.html#details)).
```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,5 @@
## 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.
> **Note:** 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.

6
include/QtQmlPromise Normal file
View File

@ -0,0 +1,6 @@
#ifndef QTQMLPROMISE_MODULE_H
#define QTQMLPROMISE_MODULE_H
#include "../src/qtqmlpromise/qjspromise.h"
#endif // ifndef QTQMLPROMISE_MODULE_H

View File

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

View File

@ -1,3 +1,11 @@
INCLUDEPATH += $$PWD/include $$PWD/src
DEPENDPATH += $$PWD/include $$PWD/src
CONFIG += c++11
qtpromise-qml {
QML_IMPORT_PATH += $$shadowed($$PWD)/qml
# To avoid carrying an extra library dependency, the QJSPromise definition is
# embedded in the QML plugin, so we need to link against the plugin itself.
LIBS += -L$$shadowed($$PWD)/qml/QtPromise -l$$qtLibraryTarget(qtpromiseplugin)
}

View File

@ -1,10 +1,9 @@
TEMPLATE = subdirs
SUBDIRS = \
src \
tests
_qt_creator_ {
SUBDIRS += src
}
tests.depends = src
OTHER_FILES = \
package/features/*.prf \

25
src/imports/imports.pro Normal file
View File

@ -0,0 +1,25 @@
TEMPLATE = lib
CONFIG += plugin exceptions
QT += qml
IMPORT_VERSION = 1.0
DEFINES += QTQMLPROMISE_LIBRARY
TARGET = $$qtLibraryTarget(qtpromiseplugin)
DESTDIR = $$shadowed($$PWD/../../qml/QtPromise)
include(../qtqmlpromise/qtqmlpromise.pri)
include(../../qtpromise.pri)
SOURCES += \
$$PWD/plugin.cpp
QMLFILES += \
$$PWD/plugins.qmltypes \
$$PWD/qmldir
RESOURCES += $$PWD/imports.qrc
qmlfiles.files = $$QMLFILES
qmlfiles.path = $$DESTDIR
COPIES += qmlfiles

5
src/imports/imports.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/QtPromise">
<file>qtqmlpromise.js</file>
</qresource>
</RCC>

76
src/imports/plugin.cpp Normal file
View File

@ -0,0 +1,76 @@
// QtPromise
#include <QtQmlPromise>
// Qt
#include <QQmlEngine>
#include <QQmlExtensionPlugin>
static const char* kJSPromisePrivateNamespace = "__qtpromise_private__";
using namespace QtPromise;
class QtQmlPromiseObject : public QObject
{
Q_OBJECT
public:
QtQmlPromiseObject(QJSEngine* engine)
: QObject(engine)
, m_engine(engine)
{
Q_ASSERT(engine);
}
Q_INVOKABLE QJSValue create(const QJSValue& resolver, const QJSValue& prototype)
{
QJSValue value = m_engine->toScriptValue(QJSPromise(m_engine, resolver));
value.setPrototype(prototype);
return value;
}
Q_INVOKABLE QtPromise::QJSPromise resolve(QJSValue value)
{
return QJSPromise::resolve(std::move(value));
}
Q_INVOKABLE QtPromise::QJSPromise reject(QJSValue error)
{
return QJSPromise::reject(std::move(error));
}
Q_INVOKABLE QtPromise::QJSPromise all(QJSValue input)
{
return QJSPromise::all(m_engine, std::move(input));
}
private:
QJSEngine* m_engine;
};
class QtQmlPromisePlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
void registerTypes(const char* uri) Q_DECL_OVERRIDE
{
Q_ASSERT(QLatin1String(uri) == QLatin1String("QtPromise"));
Q_UNUSED(uri);
qRegisterMetaType<QJSPromise>();
}
void initializeEngine(QQmlEngine* engine, const char* uri) Q_DECL_OVERRIDE
{
Q_ASSERT(QLatin1String(uri) == QLatin1String("QtPromise"));
Q_UNUSED(uri);
QJSValue global = engine->globalObject();
QJSValue object = engine->newQObject(new QtQmlPromiseObject(engine));
global.setProperty(kJSPromisePrivateNamespace, object);
}
}; // class QtQmlPromisePlugin
#include "plugin.moc"

View File

@ -0,0 +1,5 @@
import QtQuick.tooling 1.2
Module {
dependencies: []
}

5
src/imports/qmldir Normal file
View File

@ -0,0 +1,5 @@
module QtPromise
plugin qtpromiseplugin
typeinfo plugins.qmltypes
classname QtQmlPromisePlugin
Promise 1.0 qrc:///QtPromise/qtqmlpromise.js

View File

@ -0,0 +1,16 @@
.pragma library
(function(global) {
var private = global.__qtpromise_private__;
delete global.__qtpromise_private__;
var Promise = global.Promise = function(resolver) {
return private.create(function(proxy) {
resolver(proxy.resolve, proxy.reject);
}, this);
};
['all', 'reject', 'resolve'].forEach(function(method) {
Promise[method] = private[method];
});
})(this);

View File

@ -16,39 +16,61 @@ 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 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 struct QtPromisePrivate::PromiseFulfill<QPromise<T> >;
friend class QPromiseResolve<T>;
friend class QPromiseReject<T>;
@ -56,17 +78,17 @@ protected:
};
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;
public: // STATIC
inline static QPromise<QVector<T> > all(const QVector<QPromise<T> >& promises);
template <template <typename, typename...> class Sequence = QVector, typename ...Args>
inline static 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 +96,16 @@ 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);
template <template <typename, typename...> class Sequence = QVector, typename ...Args>
inline static QPromise<void> all(const Sequence<QPromise<void>, Args...>& promises);
inline static QPromise<void> resolve();
private:

View File

@ -1,6 +1,7 @@
// Qt
#include <QCoreApplication>
#include <QSharedPointer>
#include <QTimer>
namespace QtPromise {
@ -12,28 +13,18 @@ public:
: m_promise(new QPromise<T>(std::move(p)))
{ }
void operator()(const T& value) const
template <typename V>
void operator()(V&& value) const
{
resolve(value);
}
void operator()(T&& value) const
{
resolve(std::move(value));
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->resolve(std::forward<V>(value));
m_promise->m_d->dispatch();
}
}
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 <>
@ -131,6 +122,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 +138,56 @@ 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 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,26 +210,10 @@ inline QPromise<T> QPromiseBase<T>::reject(E&& error)
}
template <typename T>
template <typename THandler>
inline QPromise<T> QPromise<T>::finally(THandler handler) const
template <template <typename, typename...> class Sequence, typename ...Args>
inline QPromise<QVector<T> > QPromise<T>::all(const Sequence<QPromise<T>, Args...>& promises)
{
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();
});
});
}
template <typename T>
inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises)
{
const int count = promises.size();
const int count = (int)promises.size();
if (count == 0) {
return QPromise<QVector<T> >::resolve({});
}
@ -192,8 +225,9 @@ inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promi
QSharedPointer<int> remaining(new int(count));
QSharedPointer<QVector<T> > results(new QVector<T>(count));
for (int i=0; i<count; ++i) {
promises[i].then([=](const T& res) mutable {
int i = 0;
for (const auto& promise: promises) {
promise.then([=](const T& res) mutable {
(*results)[i] = res;
if (--(*remaining) == 0) {
resolve(*results);
@ -204,10 +238,20 @@ inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promi
reject(std::current_exception());
}
});
i++;
}
});
}
template <typename T>
inline QPromise<T> QPromise<T>::resolve(const T& value)
{
return QPromise<T>([&](const QPromiseResolve<T>& resolve) {
resolve(value);
});
}
template <typename T>
inline QPromise<T> QPromise<T>::resolve(T&& value)
{
@ -216,22 +260,10 @@ 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();
const int count = (int)promises.size();
if (count == 0) {
return QPromise<void>::resolve();
}
@ -240,7 +272,7 @@ inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promis
const QPromiseResolve<void>& resolve,
const QPromiseReject<void>& reject) {
QSharedPointer<int> remaining(new int(promises.size()));
QSharedPointer<int> remaining(new int(count));
for (const auto& promise: promises) {
promise.then([=]() {

View File

@ -32,7 +32,7 @@ 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)
{
struct Event : public QEvent
{
@ -43,11 +43,34 @@ static void qtpromise_defer(F&& f, QThread* thread = nullptr)
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 F>
static void qtpromise_defer(F&& f)
{
Q_ASSERT(QThread::currentThread());
qtpromise_defer(std::forward<F>(f), QThread::currentThread());
}
template <typename T>
struct PromiseDeduce
{
@ -79,13 +102,17 @@ struct PromiseFulfill<QtPromise::QPromise<T> >
const QtPromise::QPromiseResolve<T>& resolve,
const QtPromise::QPromiseReject<T>& 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());
});
}
}
};
@ -98,13 +125,17 @@ 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());
});
}
}
};
@ -331,11 +362,12 @@ struct PromiseCatcher<T, std::nullptr_t, void>
template <typename T> class PromiseData;
template <typename T>
template <typename T, typename F>
class PromiseDataBase : public QSharedData
{
public:
using Error = QtPromise::QPromiseError;
using Handler = std::pair<QPointer<QThread>, std::function<F> >;
using Catcher = std::pair<QPointer<QThread>, std::function<void(const Error&)> >;
virtual ~PromiseDataBase() {}
@ -356,6 +388,12 @@ public:
return !m_settled;
}
void addHandler(std::function<F> handler)
{
QWriteLocker lock(&m_lock);
m_handlers.append({QThread::currentThread(), std::move(handler)});
}
void addCatcher(std::function<void(const Error&)> catcher)
{
QWriteLocker lock(&m_lock);
@ -370,21 +408,43 @@ public:
setSettled();
}
void reject(const QSharedPointer<Error>& error)
{
Q_ASSERT(isPending());
Q_ASSERT(m_error.isNull());
m_error = error;
this->setSettled();
}
const QSharedPointer<Error>& 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();
if (m_error.isNull()) {
notify(handlers);
return;
}
QSharedPointer<Error> error = m_error;
Q_ASSERT(!error.isNull());
@ -406,28 +466,24 @@ 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;
};
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)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value.reset(new T(std::move(value)));
this->setSettled();
@ -435,17 +491,28 @@ public:
void resolve(const T& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value.reset(new T(value));
this->setSettled();
}
void notify() Q_DECL_OVERRIDE
void resolve(const QSharedPointer<T>& value)
{
this->m_lock.lockForWrite();
QVector<Handler> handlers(std::move(m_handlers));
this->m_lock.unlock();
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value = value;
this->setSettled();
}
const QSharedPointer<T>& value() const
{
Q_ASSERT(this->isFulfilled());
return m_value;
}
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
{
QSharedPointer<T> value(m_value);
Q_ASSERT(!value.isNull());
@ -458,38 +525,27 @@ public:
}
private:
QVector<Handler> m_handlers;
QSharedPointer<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

View File

@ -4,6 +4,9 @@
// QtPromise
#include "qpromiseglobal.h"
// Qt
#include <QException>
namespace QtPromise {
class QPromiseError
@ -33,7 +36,7 @@ public:
swap(other);
}
QPromiseError& operator =(QPromiseError other)
QPromiseError& operator=(QPromiseError other)
{
swap(other);
return *this;
@ -53,6 +56,16 @@ private:
std::exception_ptr m_exception;
};
class QPromiseTimeoutException : public QException
{
public:
void raise() const Q_DECL_OVERRIDE { throw *this; }
QPromiseTimeoutException* clone() const Q_DECL_OVERRIDE
{
return new QPromiseTimeoutException(*this);
}
};
} // namespace QtPromise
#endif // QTPROMISE_QPROMISEERROR_H

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);
};
/*!

View File

@ -7,7 +7,7 @@
namespace QtPromise {
template <typename T>
typename QtPromisePrivate::PromiseDeduce<T>::Type qPromise(T&& value)
static inline typename QtPromisePrivate::PromiseDeduce<T>::Type qPromise(T&& value)
{
using namespace QtPromisePrivate;
using Promise = typename PromiseDeduce<T>::Type;
@ -18,7 +18,7 @@ typename QtPromisePrivate::PromiseDeduce<T>::Type qPromise(T&& value)
});
}
QPromise<void> qPromise()
static inline QPromise<void> qPromise()
{
return QPromise<void>([](
const QPromiseResolve<void>& resolve) {
@ -26,13 +26,14 @@ QPromise<void> qPromise()
});
}
template <typename T>
QPromise<QVector<T> > qPromiseAll(const QVector<QPromise<T> >& promises)
template <typename T, template <typename, typename...> class Sequence = QVector, typename ...Args>
static inline QPromise<QVector<T> > qPromiseAll(const Sequence<QPromise<T>, Args...>& promises)
{
return QPromise<T>::all(promises);
}
QPromise<void> qPromiseAll(const QVector<QPromise<void> >& promises)
template <template <typename, typename...> class Sequence = QVector, typename ...Args>
static inline QPromise<void> qPromiseAll(const Sequence<QPromise<void>, Args...>& promises)
{
return QPromise<void>::all(promises);
}

View File

@ -1,5 +1,5 @@
TEMPLATE = lib
CONFIG += c++11 qt thread warn_on
TEMPLATE = aux
CONFIG += c++11 force_qt thread warn_on
DEFINES += QT_DEPRECATED_WARNINGS
include(qtpromise.pri)

View File

@ -0,0 +1,175 @@
// QtQmlPromise
#include "qjspromise.h"
#include "qjspromise_p.h"
// Qt
#include <QJSValueIterator>
#include <QJSEngine>
using namespace QtPromise;
namespace {
QJSValue toJSArray(QJSEngine* engine, const QVector<QJSValue>& values)
{
Q_ASSERT(engine);
const int count = values.count();
QJSValue array = engine->newArray(count);
for (int i = 0; i < count; ++i) {
array.setProperty(i, values[i]);
}
return array;
}
template <typename T>
QJSValue newError(const QJSValue& source, const T& value)
{
QJSEngine* engine = source.engine();
QJSValue error = engine->evaluate("throw new Error('')");
error.setProperty("message", engine->toScriptValue(value));
return error;
}
} // anonymous namespace
QJSPromise::QJSPromise()
: m_promise(QPromise<QJSValue>::resolve(QJSValue()))
{ }
QJSPromise::QJSPromise(QPromise<QJSValue>&& promise)
: m_promise(std::move(promise))
{
}
QJSPromise::QJSPromise(QJSEngine* engine, QJSValue resolver)
: m_promise([=](
const QPromiseResolve<QJSValue>& resolve,
const QPromiseReject<QJSValue>& reject) mutable {
// resolver is part of the Promise wrapper in qtqmlpromise.js
Q_ASSERT(resolver.isCallable());
Q_ASSERT(engine);
auto proxy = QtPromisePrivate::JSPromiseResolver(resolve, reject);
auto ret = resolver.call(QJSValueList() << engine->toScriptValue(proxy));
if (ret.isError()) {
throw ret;
}
})
{ }
QJSPromise QJSPromise::then(QJSValue fulfilled, QJSValue rejected) const
{
const bool fulfillable = fulfilled.isCallable();
const bool rejectable = rejected.isCallable();
if (!fulfillable && !rejectable) {
return *this;
}
auto _rejected = [=]() mutable {
QJSValue error;
try {
throw;
} catch (const QJSValue& err) {
error = err;
} catch (const QVariant& err) {
error = newError(rejected, err);
} catch (const std::exception& e) {
error = newError(rejected, QString(e.what()));
} catch (...) {
error = newError(rejected, QString("Unknown error"));
}
return rejected.call({error});
};
if (!fulfillable) {
return m_promise.then(nullptr, _rejected);
}
auto _fulfilled = [=](const QJSValue& res) mutable {
return fulfilled.call(QJSValueList() << res);
};
if (!rejectable) {
return m_promise.then(_fulfilled);
}
return m_promise.then(_fulfilled, _rejected);
}
QJSPromise QJSPromise::fail(QJSValue handler) const
{
return then(QJSValue(), handler);
}
QJSPromise QJSPromise::finally(QJSValue handler) const
{
return m_promise.finally([=]() mutable {
return handler.call();
});
}
QJSPromise QJSPromise::tap(QJSValue handler) const
{
return m_promise.tap([=](const QJSValue& res) mutable {
return handler.call(QJSValueList() << res);
});
}
QJSPromise QJSPromise::delay(int msec) const
{
return m_promise.delay(msec);
}
QJSPromise QJSPromise::wait() const
{
return m_promise.wait();
}
QJSPromise QJSPromise::resolve(QJSValue&& value)
{
return QPromise<QJSValue>::resolve(std::forward<QJSValue>(value));
}
QJSPromise QJSPromise::reject(QJSValue&& error)
{
return QPromise<QJSValue>::reject(std::forward<QJSValue>(error));
}
QJSPromise QJSPromise::all(QJSEngine* engine, QJSValue&& input)
{
Q_ASSERT(engine);
if (!input.isArray()) {
// TODO TYPEERROR!
return QPromise<QJSValue>::reject("foobar");
}
Q_ASSERT(input.hasProperty("length"));
const int count = input.property("length").toInt();
if (!count) {
return QPromise<QJSValue>::resolve(QJSValue(input));
}
QList<QPromise<QJSValue> > promises;
for (int i = 0; i < count; ++i) {
QJSValue value = input.property(i);
const QVariant variant = value.toVariant();
if (variant.userType() == qMetaTypeId<QJSPromise>()) {
promises << variant.value<QJSPromise>().m_promise;
} else {
promises << QPromise<QJSValue>::resolve(std::move(value));
}
}
return QPromise<QJSValue>::all(promises)
.then([engine](const QVector<QJSValue>& results) {
return toJSArray(engine, results);
});
}

View File

@ -0,0 +1,52 @@
#ifndef QTQMLPROMISE_QJSPROMISE_H
#define QTQMLPROMISE_QJSPROMISE_H
// QtQmlPromise
#include "qtqmlpromiseglobal.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QJSValue>
namespace QtPromise {
class QTQMLPROMISE_EXPORT QJSPromise
{
Q_GADGET
public:
QJSPromise();
QJSPromise(QJSEngine* engine, QJSValue resolver);
QJSPromise(QPromise<QJSValue>&& promise);
Q_INVOKABLE bool isFulfilled() const { return m_promise.isFulfilled(); }
Q_INVOKABLE bool isRejected() const { return m_promise.isRejected(); }
Q_INVOKABLE bool isPending() const{ return m_promise.isPending(); }
Q_INVOKABLE QtPromise::QJSPromise then(QJSValue fulfilled, QJSValue rejected = QJSValue()) const;
Q_INVOKABLE QtPromise::QJSPromise fail(QJSValue handler) const;
Q_INVOKABLE QtPromise::QJSPromise finally(QJSValue handler) const;
Q_INVOKABLE QtPromise::QJSPromise tap(QJSValue handler) const;
Q_INVOKABLE QtPromise::QJSPromise delay(int msec) const;
Q_INVOKABLE QtPromise::QJSPromise wait() const;
public: // STATICS
static QJSPromise resolve(QJSValue&& value);
static QJSPromise reject(QJSValue&& error);
static QJSPromise all(QJSEngine* engine, QJSValue&& input);
private:
friend struct QtPromisePrivate::PromiseFulfill<QJSValue>;
QPromise<QJSValue> m_promise;
}; // class QJSPromise
} // namespace QtPromise
Q_DECLARE_TYPEINFO(QtPromise::QJSPromise, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(QtPromise::QJSPromise)
#endif // ifndef QTQMLPROMISE_QJSPROMISE_H

View File

@ -0,0 +1,71 @@
#ifndef QTQMLPROMISE_QJSPROMISE_P_H
#define QTQMLPROMISE_QJSPROMISE_P_H
// QtQmlPromise
#include "qjspromise.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QJSValue>
namespace QtPromisePrivate {
template <>
struct PromiseFulfill<QJSValue>
{
static void call(
const QJSValue& value,
const QtPromise::QPromiseResolve<QJSValue>& resolve,
const QtPromise::QPromiseReject<QJSValue>& reject)
{
using namespace QtPromise;
if (value.isObject()) {
const QVariant variant = value.toVariant();
if (variant.userType() == qMetaTypeId<QJSPromise>()) {
const auto promise = variant.value<QJSPromise>().m_promise;
PromiseFulfill<QPromise<QJSValue> >::call(promise, resolve, reject);
return;
}
}
if (value.isError()) {
reject(value);
} else {
resolve(value);
}
}
};
class JSPromiseResolver
{
Q_GADGET
public:
JSPromiseResolver() {}
JSPromiseResolver(
const QtPromise::QPromiseResolve<QJSValue>& resolve,
const QtPromise::QPromiseReject<QJSValue>& reject)
: m_d(new Data{resolve, reject})
{ }
Q_INVOKABLE void resolve(QJSValue value = QJSValue()) { m_d->resolve(std::move(value)); }
Q_INVOKABLE void reject(QJSValue error = QJSValue()) { m_d->reject(std::move(error)); }
private:
struct Data
{
QtPromise::QPromiseResolve<QJSValue> resolve;
QtPromise::QPromiseReject<QJSValue> reject;
}; // struct Data
QSharedPointer<Data> m_d;
};
} // namespace QtPromisePrivate
Q_DECLARE_METATYPE(QtPromisePrivate::JSPromiseResolver)
#endif // ifndef QTQMLPROMISE_QJSPROMISE_P_H

View File

@ -0,0 +1,7 @@
HEADERS += \
$$PWD/qjspromise.h \
$$PWD/qjspromise_p.h \
$$PWD/qtqmlpromiseglobal.h
SOURCES += \
$$PWD/qjspromise.cpp

View File

@ -0,0 +1,8 @@
TEMPLATE = aux
CONFIG += c++11 force_qt thread warn_on
DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += QTQMLPROMISE_LIBRARY
QT += qml
include($$PWD/../../qtpromise.pri)
include($$PWD/qtqmlpromise.pri)

View File

@ -0,0 +1,16 @@
#ifndef QTQMLPROMISE_QTQMLPROMISEGLOBAL_H
#define QTQMLPROMISE_QTQMLPROMISEGLOBAL_H
// QtPromise
#include <QtPromise>
// QtCore
#include <QtGlobal>
#ifdef QTQMLPROMISE_LIBRARY
# define QTQMLPROMISE_EXPORT Q_DECL_EXPORT
#else
# define QTQMLPROMISE_EXPORT Q_DECL_IMPORT
#endif
#endif // ifndef QTQMLPROMISE_QTQMLPROMISEGLOBAL_H

View File

@ -1,2 +1,5 @@
TEMPLATE = subdirs
SUBDIRS = qtpromise
SUBDIRS += \
imports \
qtpromise \
qtqmlpromise

View File

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

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

@ -4,6 +4,14 @@
// 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
@ -12,8 +20,12 @@ class tst_benchmark : public QObject
private Q_SLOTS:
void valueResolve();
void valueResolveStatic();
void valueReject();
void valueThen();
void valueFinally();
void valueTap();
void valueDelayed();
void errorReject();
void errorThen();
@ -55,6 +67,35 @@ struct Data : public Logger
Data(int v): Logger(), m_value(v) {}
int value() const { return m_value; }
// MSVC 2013 doesn't support implicit generation of the move constructor and
// operator, so we need to explicitly define these methods and thus the copy
// constructor and operator also need to be explicitly defined (error C2280).
// https://stackoverflow.com/a/26581337
Data(const Data& other)
: Logger(other)
, m_value(other.m_value)
{ }
Data(Data&& other) : Logger(std::forward<Data>(other))
{
qSwap(m_value, other.m_value);
}
Data& operator=(const Data& other)
{
Logger::operator=(other);
m_value = other.m_value;
return *this;
}
Data& operator=(Data&& other)
{
Logger::operator=(std::forward<Data>(other));
qSwap(m_value, other.m_value);
return *this;
}
private:
int m_value;
};
@ -86,6 +127,31 @@ void tst_benchmark::valueResolve()
}
}
void tst_benchmark::valueResolveStatic()
{
{ // should move the value when resolved by rvalue
Data::logs().reset();
QPromise<Data>::resolve(Data(42)).wait();
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 0);
QCOMPARE(Data::logs().move, 1); // move value to the promise data
QCOMPARE(Data::logs().refs, 0);
}
{ // should create one copy of the value when resolved by lvalue
{
Data::logs().reset();
Data value(42);
QPromise<Data>::resolve(value).wait();
}
QCOMPARE(Data::logs().ctor, 1);
QCOMPARE(Data::logs().copy, 1); // copy value to the promise data
QCOMPARE(Data::logs().move, 0);
QCOMPARE(Data::logs().refs, 0);
}
}
void tst_benchmark::valueReject()
{
{ // should not create any data if rejected
@ -128,7 +194,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 +228,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 +327,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 +339,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 +355,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 +370,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

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

View File

@ -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);

View File

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

View File

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

View File

@ -0,0 +1,103 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_all : public QObject
{
Q_OBJECT
private Q_SLOTS:
void qList();
//void qVector();
void stdList();
void stdVector();
};
QTEST_MAIN(tst_qpromise_all)
#include "tst_all.moc"
namespace {
template <class Sequence>
struct SequenceTester
{
};
template <template <typename, typename...> class Sequence, typename ...Args>
struct SequenceTester<Sequence<QPromise<int>, Args...> >
{
static void exec()
{
Sequence<QPromise<int>, Args...> promises{
QPromise<int>::resolve(42),
QPromise<int>::resolve(43),
QPromise<int>::resolve(44)
};
promises.push_back(QPromise<int>::resolve(45));
promises.insert(++promises.begin(), QPromise<int>::resolve(46));
promises.pop_back();
auto p = QPromise<int>::all(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
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{
QPromise<void>::resolve(),
QPromise<void>::resolve(),
QPromise<void>::resolve()
};
promises.push_back(QPromise<void>::resolve());
promises.insert(++promises.begin(), QPromise<void>::resolve());
promises.pop_back();
auto p = QPromise<void>::all(promises);
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
QCOMPARE(waitForValue(p, -1, 42), 42);
}
};
} // anonymous namespace
void tst_qpromise_all::qList()
{
SequenceTester<QList<QPromise<int> > >::exec();
SequenceTester<QList<QPromise<void> > >::exec();
}
// QVector::push_back/append isn't supported since it requires a default
// constructor (see https://github.com/simonbrunel/qtpromise/issues/3)
//void tst_qpromise_all::qVector()
//{
// SequenceTester<QVector<QPromise<int> > >::exec();
// SequenceTester<QVector<QPromise<void> > >::exec();
//}
void tst_qpromise_all::stdList()
{
SequenceTester<std::list<QPromise<int> > >::exec();
SequenceTester<std::list<QPromise<void> > >::exec();
}
void tst_qpromise_all::stdVector()
{
SequenceTester<std::vector<QPromise<int> > >::exec();
SequenceTester<std::vector<QPromise<void> > >::exec();
}

View File

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

View File

@ -0,0 +1,230 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_construct : public QObject
{
Q_OBJECT
private Q_SLOTS:
void resolveSyncOneArg();
void resolveSyncOneArg_void();
void resolveSyncTwoArgs();
void resolveSyncTwoArgs_void();
void resolveAsyncOneArg();
void resolveAsyncOneArg_void();
void resolveAsyncTwoArgs();
void resolveAsyncTwoArgs_void();
void rejectThrowOneArg();
void rejectThrowOneArg_void();
void rejectThrowTwoArgs();
void rejectThrowTwoArgs_void();
void rejectSync();
void rejectSync_void();
void rejectAsync();
void rejectAsync_void();
};
QTEST_MAIN(tst_qpromise_construct)
#include "tst_construct.moc"
void tst_qpromise_construct::resolveSyncOneArg()
{
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
resolve(42);
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1), 42);
}
void tst_qpromise_construct::resolveSyncOneArg_void()
{
QPromise<void> p([](const QPromiseResolve<void>& resolve) {
resolve();
});
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
}
void tst_qpromise_construct::resolveSyncTwoArgs()
{
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_construct::resolveSyncTwoArgs_void()
{
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_construct::resolveAsyncOneArg()
{
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);
}
void tst_qpromise_construct::resolveAsyncOneArg_void()
{
QPromise<void> p([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve();
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
}
void tst_qpromise_construct::resolveAsyncTwoArgs()
{
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_construct::resolveAsyncTwoArgs_void()
{
QPromise<void> p([](const QPromiseResolve<void>& resolve, const QPromiseReject<void>&) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve();
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForError(p, QString()), QString());
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
}
void tst_qpromise_construct::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_construct::rejectSync_void()
{
QPromise<void> p([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
reject(QString("foo"));
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1, 42), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise_construct::rejectAsync()
{
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_construct::rejectAsync_void()
{
QPromise<void> p([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_construct::rejectThrowOneArg()
{
QPromise<int> p([](const QPromiseResolve<int>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise_construct::rejectThrowOneArg_void()
{
QPromise<void> p([](const QPromiseResolve<void>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1, 42), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}
void tst_qpromise_construct::rejectThrowTwoArgs()
{
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_construct::rejectThrowTwoArgs_void()
{
QPromise<void> p([](const QPromiseResolve<void>&, const QPromiseReject<void>&) {
throw QString("foo");
});
QCOMPARE(p.isRejected(), true);
QCOMPARE(waitForValue(p, -1, 42), -1);
QCOMPARE(waitForError(p, QString()), QString("foo"));
}

View File

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

View File

@ -0,0 +1,55 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_delay : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilled();
void rejected();
};
QTEST_MAIN(tst_qpromise_delay)
#include "tst_delay.moc"
void tst_qpromise_delay::fulfilled()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>::resolve(42).delay(1000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QVERIFY(elapsed >= 1000 * 0.95); // Qt::CoarseTimer (default) Coarse timers try to
QVERIFY(elapsed <= 1000 * 1.05); // keep accuracy within 5% of the desired interval.
}
void tst_qpromise_delay::rejected()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>::reject(QString("foo")).delay(1000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QVERIFY(elapsed < 5);
}

View File

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

View File

@ -0,0 +1,83 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_fail : public QObject
{
Q_OBJECT
private Q_SLOTS:
void sameType();
void baseClass();
void catchAll();
// TODO: sync / async
};
QTEST_MAIN(tst_qpromise_fail)
#include "tst_fail.moc"
void tst_qpromise_fail::sameType()
{
// 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_fail::baseClass()
{
// 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_fail::catchAll()
{
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"));
}

View File

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

View File

@ -0,0 +1,204 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_finally : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilledSync();
void fulfilledSync_void();
void fulfilledThrows();
void fulfilledThrows_void();
void fulfilledAsyncResolve();
void fulfilledAsyncReject();
void rejectedSync();
void rejectedSync_void();
void rejectedThrows();
void rejectedThrows_void();
void rejectedAsyncResolve();
void rejectedAsyncReject();
};
QTEST_MAIN(tst_qpromise_finally)
#include "tst_finally.moc"
void tst_qpromise_finally::fulfilledSync()
{
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);
}
void tst_qpromise_finally::fulfilledSync_void()
{
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);
}
void tst_qpromise_finally::fulfilledThrows()
{
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);
}
void tst_qpromise_finally::fulfilledThrows_void()
{
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);
}
void tst_qpromise_finally::fulfilledAsyncResolve()
{
QVector<int> values;
auto p = QPromise<int>::resolve(42).finally([&]() {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::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}));
}
void tst_qpromise_finally::fulfilledAsyncReject()
{
auto p = QPromise<int>::resolve(42).finally([]() {
return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("bar"));
});
});
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_finally::rejectedSync()
{
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_finally::rejectedSync_void()
{
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_finally::rejectedThrows()
{
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_finally::rejectedThrows_void()
{
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_finally::rejectedAsyncResolve()
{
QVector<int> values;
auto p = QPromise<int>::reject(QString("foo")).finally([&]() {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::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_finally::rejectedAsyncReject()
{
auto p = QPromise<int>::reject(QString("foo")).finally([]() {
return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("bar"));
});
});
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}

View File

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

View File

@ -0,0 +1,230 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_operators : public QObject
{
Q_OBJECT
private Q_SLOTS:
void move();
void move_void();
void copy();
void copy_void();
void equalTo();
void equalTo_void();
void notEqualTo();
void notEqualTo_void();
void chaining();
void chaining_void();
};
QTEST_MAIN(tst_qpromise_operators)
#include "tst_operators.moc"
void tst_qpromise_operators::move()
{
auto p0 = QPromise<int>::resolve(42);
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(waitForValue(p0, -1), 42);
p0 = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
QCOMPARE(p0.isPending(), true);
QCOMPARE(waitForError(p0, QString()), QString("foo"));
p0 = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(43);
});
});
QCOMPARE(p0.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 43);
}
void tst_qpromise_operators::move_void()
{
auto p0 = QPromise<void>::resolve();
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(waitForValue(p0, -1, 42), 42);
p0 = QPromise<void>([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
QCOMPARE(p0.isPending(), true);
QCOMPARE(waitForError(p0, QString()), QString("foo"));
p0 = QPromise<void>([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve();
});
});
QCOMPARE(p0.isPending(), true);
QCOMPARE(waitForValue(p0, -1, 43), 43);
}
void tst_qpromise_operators::copy()
{
auto p0 = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
auto p1 = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
QCOMPARE(p0 == p1, false);
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
p0 = p1;
QCOMPARE(p0 == p1, true);
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
QCOMPARE(waitForValue(p0, -1), 42);
QCOMPARE(waitForValue(p1, -1), 42);
}
void tst_qpromise_operators::copy_void()
{
auto p0 = QPromise<void>([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
QtPromisePrivate::qtpromise_defer([=]() {
reject(QString("foo"));
});
});
auto p1 = QPromise<void>([](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=]() {
resolve();
});
});
QCOMPARE(p0 == p1, false);
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
p0 = p1;
QCOMPARE(p0 == p1, true);
QCOMPARE(p0.isPending(), true);
QCOMPARE(p1.isPending(), true);
p0.wait();
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
}
void tst_qpromise_operators::equalTo()
{
auto p0 = QPromise<int>::resolve(42);
auto p1 = QPromise<int>::resolve(42);
auto p2 = p1;
auto p3(p2);
QCOMPARE(p0 == p1, false);
QCOMPARE(p0 == p2, false);
QCOMPARE(p0 == p3, false);
QCOMPARE(p1 == p2, true);
QCOMPARE(p1 == p3, true);
QCOMPARE(p2 == p3, true);
}
void tst_qpromise_operators::equalTo_void()
{
auto p0 = QPromise<void>::resolve();
auto p1 = QPromise<void>::resolve();
auto p2 = p1;
auto p3(p2);
QCOMPARE(p0 == p1, false);
QCOMPARE(p0 == p2, false);
QCOMPARE(p0 == p3, false);
QCOMPARE(p1 == p2, true);
QCOMPARE(p1 == p3, true);
QCOMPARE(p2 == p3, true);
}
void tst_qpromise_operators::notEqualTo()
{
auto p0 = QPromise<int>::resolve(42);
auto p1 = QPromise<int>::resolve(42);
auto p2 = p1;
auto p3(p2);
QCOMPARE(p0 != p1, true);
QCOMPARE(p0 != p2, true);
QCOMPARE(p0 != p3, true);
QCOMPARE(p1 != p2, false);
QCOMPARE(p1 != p3, false);
QCOMPARE(p2 != p3, false);
}
void tst_qpromise_operators::notEqualTo_void()
{
auto p0 = QPromise<void>::resolve();
auto p1 = QPromise<void>::resolve();
auto p2 = p1;
auto p3(p2);
QCOMPARE(p0 != p1, true);
QCOMPARE(p0 != p2, true);
QCOMPARE(p0 != p3, true);
QCOMPARE(p1 != p2, false);
QCOMPARE(p1 != p3, false);
QCOMPARE(p2 != p3, false);
}
void tst_qpromise_operators::chaining()
{
auto p = QPromise<int>::resolve(1);
for (int i=0; i<4; ++i) {
p = p.then([](int res) {
return QPromise<int>::resolve(res * 2);
});
}
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1), 16);
}
void tst_qpromise_operators::chaining_void()
{
QVector<int> values;
auto p = QPromise<void>::resolve();
for (int i=0; i<4; ++i) {
p = p.then([i, &values]() {
values.append(i * 2);
return QPromise<void>::resolve();
});
}
QCOMPARE(p.isPending(), true);
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(values, QVector<int>({0, 2, 4, 6}));
}

View File

@ -0,0 +1,12 @@
TEMPLATE = subdirs
SUBDIRS += \
all \
construct \
delay \
fail \
finally \
operators \
resolve \
tap \
then \
timeout

View File

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

View File

@ -0,0 +1,41 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_resolve : public QObject
{
Q_OBJECT
private Q_SLOTS:
void value();
void empty_void();
};
QTEST_MAIN(tst_qpromise_resolve)
#include "tst_resolve.moc"
void tst_qpromise_resolve::value()
{
const int value = 42;
auto p0 = QPromise<int>::resolve(value);
auto p1 = QPromise<int>::resolve(43);
QCOMPARE(p0.isFulfilled(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(waitForValue(p0, -1), 42);
QCOMPARE(waitForValue(p1, -1), 43);
}
void tst_qpromise_resolve::empty_void()
{
auto p = QPromise<void>::resolve();
QCOMPARE(p.isFulfilled(), true);
}

View File

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

View File

@ -0,0 +1,145 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_tap : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilledSync();
void fulfilledSync_void();
void fulfilledThrows();
void fulfilledThrows_void();
void fulfilledAsyncResolve();
void fulfilledAsyncReject();
void rejectedSync();
void rejectedSync_void();
};
QTEST_MAIN(tst_qpromise_tap)
#include "tst_tap.moc"
void tst_qpromise_tap::fulfilledSync()
{
int value = -1;
auto p = QPromise<int>::resolve(42).tap([&](int res) {
value = res + 1;
return 8;
});
QCOMPARE(waitForValue(p, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 43);
}
void tst_qpromise_tap::fulfilledSync_void()
{
int value = -1;
auto p = QPromise<void>::resolve().tap([&]() {
value = 43;
return 8;
});
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, 43);
}
void tst_qpromise_tap::fulfilledThrows()
{
auto p = QPromise<int>::resolve(42).tap([&](int) {
throw QString("foo");
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_tap::fulfilledThrows_void()
{
auto p = QPromise<void>::resolve().tap([&]() {
throw QString("foo");
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_tap::fulfilledAsyncResolve()
{
QVector<int> values;
auto p = QPromise<int>::resolve(1).tap([&](int) {
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 3;
resolve(4); // ignored!
});
});
values << 2;
return p;
});
p.then([&](int r) {
values << r;
}).wait();
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(values, QVector<int>({2, 3, 1}));
}
void tst_qpromise_tap::fulfilledAsyncReject()
{
QVector<int> values;
auto p = QPromise<int>::resolve(1).tap([&](int) {
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 3;
reject(QString("foo"));
});
});
values << 2;
return p;
});
p.then([&](int r) {
values << r;
}).wait();
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(values, QVector<int>({2, 3}));
}
void tst_qpromise_tap::rejectedSync()
{
int value = -1;
auto p = QPromise<int>::reject(QString("foo")).tap([&](int res) {
value = res + 1;
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, -1);
}
void tst_qpromise_tap::rejectedSync_void()
{
int value = -1;
auto p = QPromise<void>::reject(QString("foo")).tap([&]() {
value = 43;
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QCOMPARE(value, -1);
}

View File

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

View File

@ -0,0 +1,126 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_then : public QObject
{
Q_OBJECT
private Q_SLOTS:
void resolveSync();
void resolveAsync();
void rejectSync();
void rejectAsync();
void skipResult();
void noHandler();
};
QTEST_MAIN(tst_qpromise_then)
#include "tst_then.moc"
void tst_qpromise_then::resolveSync()
{
QVariantList values;
auto input = QPromise<int>::resolve(42);
auto output = input.then([&](int res) {
values << res;
return QString::number(res+1);
});
output.then([&](const QString& res) {
values << res;
}).then([&]() {
values << 44;
}).wait();
QCOMPARE(values, QVariantList({42, QString("43"), 44}));
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isFulfilled(), true);
}
void tst_qpromise_then::resolveAsync()
{
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"));
QCOMPARE(p.isFulfilled(), true);
}
void tst_qpromise_then::rejectSync()
{
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(error, QString("foo42"));
QCOMPARE(input.isFulfilled(), true);
QCOMPARE(output.isRejected(), true);
}
void tst_qpromise_then::rejectAsync()
{
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"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_then::skipResult()
{
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_then::noHandler()
{
{ // 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);
}
}

View File

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

View File

@ -0,0 +1,92 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_timeout : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilled();
void rejected();
void timeout();
};
QTEST_MAIN(tst_qpromise_timeout)
#include "tst_timeout.moc"
void tst_qpromise_timeout::fulfilled()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QTimer::singleShot(1000, [=]() {
resolve(42);
});
}).timeout(2000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForValue(p, -1), 42);
QCOMPARE(p.isFulfilled(), true);
QVERIFY(elapsed < 2000);
}
void tst_qpromise_timeout::rejected()
{
QElapsedTimer timer;
qint64 elapsed = -1;
timer.start();
auto p = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QTimer::singleShot(1000, [=]() {
reject(QString("foo"));
});
}).timeout(2000).finally([&]() {
elapsed = timer.elapsed();
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(p.isRejected(), true);
QVERIFY(elapsed < 2000);
}
void tst_qpromise_timeout::timeout()
{
QElapsedTimer timer;
qint64 elapsed = -1;
bool failed = false;
timer.start();
auto p = QPromise<int>([](const QPromiseResolve<int>& resolve) {
QTimer::singleShot(4000, [=]() {
resolve(42);
});
}).timeout(2000).finally([&]() {
elapsed = timer.elapsed();
});
p.fail([&](const QPromiseTimeoutException&) {
failed = true;
return -1;
}).wait();
QCOMPARE(waitForValue(p, -1), -1);
QCOMPARE(p.isRejected(), true);
QCOMPARE(failed, true);
QVERIFY(elapsed >= 2000 * 0.95); // Qt::CoarseTimer (default) Coarse timers try to
QVERIFY(elapsed <= 2000 * 1.05); // keep accuracy within 5% of the desired interval.
}

View File

@ -5,8 +5,8 @@ QT -= gui
DEFINES += QT_DEPRECATED_WARNINGS
coverage: {
gcc: {
coverage {
gcc {
message("Code coverage enabled (gcov)")
QMAKE_CXXFLAGS += --coverage -O0 -g
QMAKE_LFLAGS += --coverage -O0 -g
@ -15,4 +15,7 @@ coverage: {
}
}
include(../../qtpromise.pri)
HEADERS += \
$$PWD/shared/utils.h
include(../../../qtpromise.pri)

View File

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

View File

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

View File

@ -5,7 +5,6 @@
#include <QtTest>
using namespace QtPromise;
using namespace QtPromisePrivate;
// https://promisesaplus.com/#requirements
class tst_requirements : public QObject
@ -41,7 +40,7 @@ void tst_requirements::statePending()
// 2.1.1.1. may transition to either the fulfilled state
{
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=]() { resolve(42); });
QtPromisePrivate::qtpromise_defer([=]() { resolve(42); });
});
QVERIFY(p.isPending());
@ -58,7 +57,7 @@ void tst_requirements::statePending()
// 2.1.1.1. ... or the rejected state
{
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=]() { reject(QString("foo")); });
QtPromisePrivate::qtpromise_defer([=]() { reject(QString("foo")); });
});
QVERIFY(p.isPending());
@ -82,7 +81,7 @@ void tst_requirements::stateFulfilled()
QPromise<int> p([](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
// 2.1.2.2. must have a value, which must not change.
resolve(42);
resolve(43);
@ -115,7 +114,7 @@ void tst_requirements::stateRejected()
QPromise<int> p([](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
// 2.1.3.2. must have a reason, which must not change.
reject(QString("foo"));
reject(QString("bar"));
@ -196,7 +195,7 @@ void tst_requirements::thenOnFulfilled()
// 2.2.2. If onFulfilled is a function:
QVector<int> values;
QPromise<int> p0([](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
// 2.2.2.3. it must not be called more than once
resolve(42);
resolve(43);
@ -216,7 +215,7 @@ void tst_requirements::thenOnFulfilled()
// with promises value as its first argument.
QVERIFY(p0.isFulfilled());
QVERIFY(p1.isFulfilled());
QCOMPARE(values, QVector<int>({42}));
QCOMPARE(values, QVector<int>{42});
}
void tst_requirements::thenOnRejected()
@ -224,7 +223,7 @@ void tst_requirements::thenOnRejected()
// 2.2.3. If onRejected is a function:
QStringList errors;
QPromise<void> p0([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
// 2.2.3.3. it must not be called more than once.
reject(QString("foo"));
reject(QString("bar"));
@ -275,7 +274,7 @@ void tst_requirements::thenMultipleCalls()
{
QVector<int> values;
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
resolve(42);
});
});
@ -294,7 +293,7 @@ void tst_requirements::thenMultipleCalls()
{
QVector<int> values;
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
QtPromisePrivate::qtpromise_defer([=]() {
reject(8);
});
});

View File

@ -0,0 +1,47 @@
#ifndef QTPROMISE_TESTS_AUTO_SHARED_UTILS_H
#define QTPROMISE_TESTS_AUTO_SHARED_UTILS_H
#include <QtPromise>
template <typename T>
static inline T waitForValue(const QtPromise::QPromise<T>& promise, const T& initial)
{
T value(initial);
promise.then([&](const T& res) {
value = res;
}).wait();
return value;
}
template <typename T>
static inline T waitForValue(const QtPromise::QPromise<void>& promise, const T& initial, const T& expected)
{
T value(initial);
promise.then([&]() {
value = expected;
}).wait();
return value;
}
template <typename T, typename E>
static inline E waitForError(const QtPromise::QPromise<T>& promise, const E& initial)
{
E error(initial);
promise.fail([&](const E& err) {
error = err;
return T();
}).wait();
return error;
}
template <typename E>
static inline E waitForError(const QtPromise::QPromise<void>& promise, const E& initial)
{
E error(initial);
promise.fail([&](const E& err) {
error = err;
}).wait();
return error;
}
#endif // QTPROMISE_TESTS_AUTO_SHARED_UTILS_H

View File

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

View File

@ -0,0 +1,5 @@
TARGET = tst_qtqmlpromise_extension
SOURCES += $$PWD/tst_extension.cpp
OTHER_FILES += $$PWD/*.qml
include(../qtqmlpromise.pri)

View File

@ -0,0 +1,10 @@
#include <QtQuickTest/quicktest.h>
static void initialize()
{
qputenv("QML2_IMPORT_PATH", QTPROMISE_IMPORTPATH);
}
Q_COREAPP_STARTUP_FUNCTION(initialize)
QUICK_TEST_MAIN(extension)

View File

@ -0,0 +1,39 @@
import QtQuick 2.3
import QtPromise 1.0
import QtTest 1.0
TestCase {
name: "Extension"
function test_global() {
compare(typeof __qtpromise_private__, 'undefined');
compare(typeof Promise, 'function');
compare(typeof Promise.resolve, 'function');
compare(typeof Promise.reject, 'function');
}
function test_instance() {
var p = new Promise(function() {});
compare(Object.prototype.toString(p), '[object Object]');
compare(p instanceof Promise, true);
compare(typeof p, 'object');
}
function test_prototype() {
var p = new Promise(function() {});
[
'delay',
'fail',
'finally',
'isFulfilled',
'isRejected',
'isPending',
'tap',
'then',
'wait',
].forEach(function(name) {
compare(typeof p[name], 'function');
});
}
}

View File

@ -0,0 +1,9 @@
TEMPLATE = app
CONFIG += qmltestcase
CONFIG += qtpromise-qml
DEFINES += QT_DEPRECATED_WARNINGS
QTPROMISE_IMPORTPATH = $$shadowed($$PWD/../../../qml)
DEFINES += QTPROMISE_IMPORTPATH=\"\\\"$$QTPROMISE_IMPORTPATH\\\"\"
include(../../../qtpromise.pri)

View File

@ -0,0 +1,4 @@
TEMPLATE = subdirs
SUBDIRS += \
extension \
requirements

View File

@ -0,0 +1,5 @@
TARGET = tst_qtqmlpromise_requirements
SOURCES += $$PWD/tst_requirements.cpp
OTHER_FILES += $$PWD/*.qml
include(../qtqmlpromise.pri)

View File

@ -0,0 +1,10 @@
#include <QtQuickTest/quicktest.h>
static void initialize()
{
qputenv("QML2_IMPORT_PATH", QTPROMISE_IMPORTPATH);
}
Q_COREAPP_STARTUP_FUNCTION(initialize)
QUICK_TEST_MAIN(requirements)

View File

@ -0,0 +1,384 @@
import QtQuick 2.3
import QtPromise 1.0
import QtTest 1.0
// https://promisesaplus.com/#requirements
TestCase {
name: "Requirements"
// 2.1. Promise States
// 2.1.1 When pending, a promise:
function test_pendingFulfilled() {
// 2.1.1.1. may transition to either the fulfilled state
var p = new Promise(function(resolve) {
Qt.callLater(function() {
resolve(42);
});
});
compare(p.isPending(), true);
compare(p.isFulfilled(), false);
compare(p.isRejected(), false);
p.wait();
compare(p.isPending(), false);
compare(p.isFulfilled(), true);
compare(p.isRejected(), false);
}
// 2.1.1 When pending, a promise:
function test_pendingRejected() {
// 2.1.1.1. ... or the rejected state
var p = new Promise(function(resolve, reject) {
Qt.callLater(function() {
reject(new Error(42));
});
});
compare(p.isPending(), true);
compare(p.isFulfilled(), false);
compare(p.isRejected(), false);
p.wait();
compare(p.isPending(), false);
compare(p.isFulfilled(), false);
compare(p.isRejected(), true);
}
// 2.1.2. When fulfilled, a promise:
function test_fulfilled() {
var p = new Promise(function(resolve, reject) {
Qt.callLater(function() {
// 2.1.2.2. must have a value, which must not change.
resolve(42);
resolve(43);
// 2.1.2.1. must not transition to any other state.
reject(new Error("foo"));
});
});
compare(p.isPending(), true);
var value = -1;
var error = null;
p.then(function(res) {
value = res;
}, function(err) {
error = err;
}).wait();
compare(p.isFulfilled(), true);
compare(p.isRejected(), false);
compare(error, null);
compare(value, 42);
}
// 2.1.3 When rejected, a promise:
function test_rejected() {
var p = new Promise(function(resolve, reject) {
Qt.callLater(function() {
// 2.1.3.2. must have a reason, which must not change.
reject(new Error("foo"));
reject(new Error("bar"));
// 2.1.3.1. must not transition to any other state.
resolve(42);
});
});
compare(p.isPending(), true);
var value = -1;
var error = null;
p.then(function(res) {
value = res;
}, function(err) {
error = err;
}).wait();
compare(p.isFulfilled(), false);
compare(p.isRejected(), true);
compare(error instanceof Error, true);
compare(error.message, 'foo');
compare(value, -1);
}
// 2.2. The then Method
// 2.2.1. Both onFulfilled and onRejected are given (resolve)
function test_thenArgsBothResolve() {
var error = null;
var value = -1;
Promise.resolve(42).then(
function(res) { value = res; },
function(err) { error = err; }
).wait();
compare(error, null);
compare(value, 42);
}
// 2.2.1. Both onFulfilled and onRejected are given (reject)
function test_thenArgsBothReject() {
var error = null;
var value = -1;
Promise.reject(new Error('foo')).then(
function(res) { value = res; },
function(err) { error = err; }
).wait();
compare(error instanceof Error, true);
compare(error.message, 'foo');
compare(value, -1);
}
// 2.2.1. onFulfilled is an optional arguments:
function test_thenArgsSecond() {
var error = null;
Promise.reject(new Error('foo')).then(
null,
function(err) { error = err; }
).wait();
compare(error instanceof Error, true);
compare(error.message, 'foo');
}
// 2.2.1. onRejected is an optional arguments:
function test_thenArgsFirst() {
var value = -1;
Promise.resolve(42).then(
function(res) { value = res; }
).wait();
compare(value, 42);
}
// 2.2.1.1. If onFulfilled is not a function, it must be ignored.
function test_thenArgsFirstInvalid() {
var value = -1;
Promise.resolve(42).then('invalid').then(
function(res) { value = res; }
).wait();
compare(value, 42);
}
// 2.2.1.2. If onRejected is not a function, it must be ignored.
function test_thenArgsSecondInvalid() {
var error = -1;
Promise.reject(new Error('foo')).then(
null,
'invalid'
).then(
null,
function(err) { error = err; }
).wait();
compare(error instanceof Error, true);
compare(error.message, 'foo');
}
// 2.2.2. If onFulfilled is a function:
function test_thenOnFulfilled() {
var p0 = new Promise(function(resolve) {
Qt.callLater(function() {
// 2.2.2.3. it must not be called more than once
resolve(42);
resolve(43);
});
});
var values = [];
var p1 = p0.then(function(res) {
values.push(res);
});
// 2.2.2.2. it must not be called before promise is fulfilled.
compare(p0.isPending(), true);
compare(p1.isPending(), true);
compare(values.length, 0);
p1.wait();
// 2.2.2.1. it must be called after promise is fulfilled,
// with promises value as its first argument.
compare(p0.isFulfilled(), true);
compare(p1.isFulfilled(), true);
compare(values, [42]);
}
// 2.2.3. If onRejected is a function:
function test_thenOnRejected() {
var p0 = new Promise(function(resolve, reject) {
Qt.callLater(function() {
// 2.2.2.3. it must not be called more than once
reject(new Error('foo'));
reject(new Error('bar'));
});
});
var errors = [];
var p1 = p0.then(null, function(res) {
errors.push(res);
});
// 2.2.3.2. it must not be called before promise is rejected.
compare(p0.isPending(), true);
compare(p1.isPending(), true);
compare(errors.length, 0);
p1.wait();
// 2.2.3.1. it must be called after promise is rejected,
// with promises reason as its first argument.
compare(p0.isRejected(), true);
compare(p1.isFulfilled(), true);
compare(errors.length, 1);
compare(errors[0] instanceof Error, true);
compare(errors[0].message, 'foo');
}
// 2.2.4. onFulfilled or onRejected must not be called until the execution context
// stack contains only platform code (ie. executed asynchronously, after the event
// loop turn in which then is called, and with a fresh stack).
function test_thenAsynchronous()
{
var value = -1;
var p0 = Promise.resolve(42);
var p1 = p0.then(function(res){ value = res; });
compare(p0.isFulfilled(), true);
compare(p1.isPending(), true);
compare(value, -1);
p1.wait();
compare(p1.isFulfilled(), true);
compare(value, 42);
}
// 2.2.5 onFulfilled and onRejected must be called as functions (i.e. with no this value).
function test_thenThisArg() {
var scopes = [];
Promise.resolve(42).then(function() { scopes.push(this); }).wait();
Promise.reject(new Error('foo')).then(null, function() { scopes.push(this); }).wait();
// Qt doesn't allow to call JS function with undefined "this"
// Let's adopt the sloppy mode (this === the global object).
var global = (function() { return this; })();
compare(scopes, [global, global]);
}
// 2.2.6. then may be called multiple times on the same promise:
// 2.2.6.1. If/when promise is fulfilled, all respective onFulfilled callbacks
// must execute in the order of their originating calls to then:
function test_thenMultipleCalls() {
var values = [];
var p = new Promise(function(resolve) {
Qt.callLater(function() {
resolve(42);
});
});
Promise.all([
p.then(function(res) { return res + 1; }),
p.then(function(res) { return res + 2; }),
p.then(function(res) { return res + 3; })
]).then(function(res) {
values = res;
}).wait();
compare(values, [43, 44, 45]);
}
}
/*
void tst_requirements::thenMultipleCalls()
{
// 2.2.6.2. If/when promise is rejected, all respective onRejected callbacks
// must execute in the order of their originating calls to then:
{
QVector<int> values;
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
qtpromise_defer([=]() {
reject(8);
});
});
qPromiseAll(QVector<QPromise<int> >{
p.then(nullptr, [&](int r) { values << r + 1; return r + 1; }),
p.then(nullptr, [&](int r) { values << r + 2; return r + 2; }),
p.then(nullptr, [&](int r) { values << r + 3; return r + 3; })
}).wait();
QCOMPARE(values, QVector<int>({9, 10, 11}));
}
}
void tst_requirements::thenHandlers()
{
// 2.2.7. then must return a promise: p2 = p1.then(onFulfilled, onRejected);
{
auto handler = [](){ return 42; };
auto p1 = QPromise<int>::resolve(42);
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, nullptr)), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(nullptr, handler)), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, handler)), QPromise<int> >::value));
}
// 2.2.7.1. If either onFulfilled or onRejected returns a value x, run the
// Promise Resolution Procedure [[Resolve]](p2, x) -> See 2.3.
// 2.2.7.2. If either onFulfilled or onRejected throws an exception e,
// p2 must be rejected with e as the reason.
{
QString reason;
auto p1 = QPromise<int>::resolve(42);
auto p2 = p1.then([](){ throw QString("foo"); });
p2.then(nullptr, [&](const QString& e) { reason = e; }).wait();
QVERIFY(p1.isFulfilled());
QVERIFY(p2.isRejected());
QCOMPARE(reason, QString("foo"));
}
{
QString reason;
auto p1 = QPromise<int>::reject(QString("foo"));
auto p2 = p1.then(nullptr, [](){ throw QString("bar"); return 42; });
p2.then(nullptr, [&](const QString& e) { reason = e; return 0; }).wait();
QVERIFY(p1.isRejected());
QVERIFY(p2.isRejected());
QCOMPARE(reason, QString("bar"));
}
// 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
// p2 must be fulfilled with the same value as promise1
{
QString value;
auto p1 = QPromise<QString>::resolve("42");
auto p2 = p1.then(nullptr, [](){ return QString(); });
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<QString> >::value));
p2.then([&](const QString& e) { value = e; }).wait();
QVERIFY(p1.isFulfilled());
QVERIFY(p2.isFulfilled());
QCOMPARE(value, QString("42"));
}
}
*/