mirror of
https://github.com/simonbrunel/qtpromise.git
synced 2024-11-22 02:34:30 +08:00
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
This commit is contained in:
parent
c34316243e
commit
18739bd8e0
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
*.gcno
|
*.gcno
|
||||||
*.gcda
|
*.gcda
|
||||||
*.moc
|
*.moc
|
||||||
@ -9,4 +11,5 @@ Makefile*
|
|||||||
moc_*.cpp
|
moc_*.cpp
|
||||||
moc_*.h
|
moc_*.h
|
||||||
coverage.info
|
coverage.info
|
||||||
|
package-lock.json
|
||||||
target_wrapper.bat
|
target_wrapper.bat
|
||||||
|
@ -30,3 +30,7 @@ script:
|
|||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- bash <(curl -s https://codecov.io/bash) -f coverage.info
|
- bash <(curl -s https://codecov.io/bash) -f coverage.info
|
||||||
|
|
||||||
|
|
||||||
|
# gitbook install .
|
||||||
|
# gitbook build . dist/docs
|
||||||
|
429
README.md
429
README.md
@ -1,433 +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
|
# 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/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)
|
[![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/).
|
[Promises/A+](https://promisesaplus.com/) implementation for [Qt/C++](https://www.qt.io/).
|
||||||
|
|
||||||
Requires [Qt 5.4](https://www.qt.io/download/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects).
|
Requires [Qt 5.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
|
## Documentation
|
||||||
### 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
|
* [Getting Started](https://qtpromise.netlify.com/qtpromise/getting-started)
|
||||||
Alternatively and **only** if your project relies on [qpm](http://www.qpm.io/), you can install QtPromise as follow:
|
* [Thread-Safety](https://qtpromise.netlify.com/qtpromise/thread-safety)
|
||||||
|
* [QtConcurrent](https://qtpromise.netlify.com/qtpromise/qtconcurrent)
|
||||||
```bash
|
* [API Reference](https://qtpromise.netlify.com/qtpromise/api-reference)
|
||||||
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-tap"></a> `QPromise<T>::tap(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`](#qpromise-finally), 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.
|
|
||||||
|
|
||||||
### <a name="qpromise-delay"></a> `QPromise<T>::delay(handler) -> 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
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### <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(Sequence<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.
|
|
||||||
|
|
||||||
`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)
|
|
||||||
|
|
||||||
## 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(Sequence<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)
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
QtPromise is available under the [MIT license](LICENSE).
|
QtPromise is available under the [MIT license](LICENSE).
|
||||||
|
40
book.json
Normal file
40
book.json
Normal 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
15
docs/README.md
Normal 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).
|
20
docs/SUMMARY.md
Normal file
20
docs/SUMMARY.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
### 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)
|
||||||
|
* [.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
15
docs/assets/style.css
Normal 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;
|
||||||
|
}
|
25
docs/qtpromise/api-reference.md
Normal file
25
docs/qtpromise/api-reference.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
## 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>::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)
|
93
docs/qtpromise/getting-started.md
Normal file
93
docs/qtpromise/getting-started.md
Normal 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
|
||||||
|
});
|
||||||
|
```
|
15
docs/qtpromise/helpers/qpromise.md
Normal file
15
docs/qtpromise/helpers/qpromise.md
Normal 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)).
|
16
docs/qtpromise/helpers/qpromiseall.md
Normal file
16
docs/qtpromise/helpers/qpromiseall.md
Normal 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)
|
||||||
|
```
|
||||||
|
|
26
docs/qtpromise/qpromise/all.md
Normal file
26
docs/qtpromise/qpromise/all.md
Normal 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)
|
29
docs/qtpromise/qpromise/constructor.md
Normal file
29
docs/qtpromise/qpromise/constructor.md
Normal 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) {
|
||||||
|
// {...}
|
||||||
|
});
|
||||||
|
```
|
14
docs/qtpromise/qpromise/delay.md
Normal file
14
docs/qtpromise/qpromise/delay.md
Normal 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
|
||||||
|
});
|
||||||
|
```
|
19
docs/qtpromise/qpromise/fail.md
Normal file
19
docs/qtpromise/qpromise/fail.md
Normal 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
|
||||||
|
});
|
||||||
|
```
|
15
docs/qtpromise/qpromise/finally.md
Normal file
15
docs/qtpromise/qpromise/finally.md
Normal 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.
|
7
docs/qtpromise/qpromise/isfulfilled.md
Normal file
7
docs/qtpromise/qpromise/isfulfilled.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# `QPromise<T>::isFulfilled`
|
||||||
|
|
||||||
|
```
|
||||||
|
QPromise<T>::isFulfilled() -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns `true` if the promise is fulfilled, otherwise returns `false`.
|
7
docs/qtpromise/qpromise/ispending.md
Normal file
7
docs/qtpromise/qpromise/ispending.md
Normal 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`.
|
8
docs/qtpromise/qpromise/isrejected.md
Normal file
8
docs/qtpromise/qpromise/isrejected.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# `QPromise<T>::isRejected`
|
||||||
|
|
||||||
|
```
|
||||||
|
QPromise<T>::isRejected() -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns `true` if the promise is rejected, otherwise returns `false`.
|
||||||
|
|
20
docs/qtpromise/qpromise/reject.md
Normal file
20
docs/qtpromise/qpromise/reject.md
Normal 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) {
|
||||||
|
// {...}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
22
docs/qtpromise/qpromise/resolve.md
Normal file
22
docs/qtpromise/qpromise/resolve.md
Normal 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)
|
18
docs/qtpromise/qpromise/tap.md
Normal file
18
docs/qtpromise/qpromise/tap.md
Normal 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.
|
87
docs/qtpromise/qpromise/then.md
Normal file
87
docs/qtpromise/qpromise/then.md
Normal 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.
|
21
docs/qtpromise/qpromise/wait.md
Normal file
21
docs/qtpromise/qpromise/wait.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
## `QPromise<T>::wait`
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
```
|
66
docs/qtpromise/qtconcurrent.md
Normal file
66
docs/qtpromise/qtconcurrent.md
Normal 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) {
|
||||||
|
// {...}
|
||||||
|
});
|
||||||
|
```
|
5
docs/qtpromise/thread-safety.md
Normal file
5
docs/qtpromise/thread-safety.md
Normal 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.
|
Loading…
Reference in New Issue
Block a user