mirror of
https://github.com/simonbrunel/qtpromise.git
synced 2025-04-02 22:45:08 +08:00
Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1a905cbd4f | ||
|
14031392ac | ||
|
f382ad25fc | ||
|
ac9b936959 | ||
|
21faa67b58 | ||
|
9f01c130cd | ||
|
461f09bef8 | ||
|
bac405febf | ||
|
0c3955cca5 | ||
|
60b36e7a70 | ||
|
f7639e921e | ||
|
1752255e7b | ||
|
13b4bb65d2 | ||
|
b7ecd95b04 | ||
|
88289a7635 | ||
|
6deec9f51f | ||
|
d43657fbd5 | ||
|
b99e468c84 | ||
|
be5455a8c8 | ||
|
0bfdddd887 | ||
|
2c1e631aed | ||
|
d5a82518f9 | ||
|
1ad99391a3 | ||
|
78417b5813 | ||
|
58738a5604 | ||
|
7ee51de987 | ||
|
6639ea52db | ||
|
7f9013a878 | ||
|
cc29ef3512 | ||
|
3c1461b8d0 | ||
|
815dc443b9 | ||
|
67837827b1 | ||
|
e3f0f054af | ||
|
cbf4cc7867 | ||
|
963ec621e1 | ||
|
1f30224578 | ||
|
47b90fb532 | ||
|
63acdfaab9 | ||
|
700098ef7b | ||
|
6110cd40d3 | ||
|
a6d883acbd | ||
|
9119cc72f6 | ||
|
fa5a4192ff | ||
|
16229fc2c9 | ||
|
22faef4ac3 | ||
|
eebcb4f364 | ||
|
051fed5fbc | ||
|
f610826ef0 | ||
|
4fa7a37750 | ||
|
69c07855f4 | ||
|
4cfe2e54f4 | ||
|
54d88f16a3 | ||
|
bdf3619469 | ||
|
8da467e9da | ||
|
efb6001b9d | ||
|
26a2110a14 | ||
|
fa987a5044 | ||
|
7b0cba5b9d | ||
|
2c8ed6e676 | ||
|
d128a5fa8d | ||
|
dcbb2ef860 | ||
|
50bae380be | ||
|
d279fb4391 | ||
|
313d3882d7 | ||
|
f794916be6 | ||
|
9d2a4ca00f | ||
|
4af2740d80 | ||
|
18739bd8e0 | ||
|
c34316243e | ||
|
d306423159 | ||
|
d3b69f1248 | ||
|
36a0eed12a | ||
|
931d5d5b13 |
34
.appveyor.yml
Normal file
34
.appveyor.yml
Normal file
@ -0,0 +1,34 @@
|
||||
# https://www.appveyor.com/docs/build-configuration/
|
||||
# https://www.appveyor.com/docs/lang/cpp/#visual-studio
|
||||
# https://www.appveyor.com/docs/windows-images-software/#qt
|
||||
|
||||
environment:
|
||||
PATH: '%PATH%;%QTDIR%\bin'
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
QTDIR: C:\Qt\5.6\msvc2013
|
||||
SETUP_CMD: C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat
|
||||
SETUP_ARG: x86
|
||||
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
QTDIR: C:\Qt\5.15\msvc2019_64
|
||||
SETUP_CMD: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat
|
||||
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
QTDIR: C:\Qt\6.2\msvc2019_64
|
||||
SETUP_CMD: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat
|
||||
|
||||
before_build:
|
||||
- call "%SETUP_CMD%" %SETUP_ARG%
|
||||
- cmake --version
|
||||
- qmake --version
|
||||
|
||||
build_script:
|
||||
- cmake -G "NMake Makefiles"
|
||||
- cmake --build .
|
||||
|
||||
test_script:
|
||||
- cmake --build . --target test
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
66
.clang-format
Normal file
66
.clang-format
Normal file
@ -0,0 +1,66 @@
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
BasedOnStyle: WebKit
|
||||
AlignAfterOpenBracket: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterStruct: true
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Custom
|
||||
BreakInheritanceList: BeforeComma
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: "^!|^:"
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- BOOST_FOREACH
|
||||
- foreach
|
||||
- forever
|
||||
- Q_FOREACH
|
||||
- Q_FOREVER
|
||||
- QBENCHMARK
|
||||
- QBENCHMARK_ONCE
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<Q.*>'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*>'
|
||||
Priority: 4
|
||||
SortPriority: 0
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
IndentPPDirectives: AfterHash
|
||||
NamespaceIndentation: None
|
||||
PenaltyReturnTypeOnItsOwnLine: 10
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
...
|
43
.github/workflows/ci.yaml
vendored
Normal file
43
.github/workflows/ci.yaml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
# https://docs.github.com/actions/reference/workflow-syntax-for-github-actions
|
||||
# https://doc.qt.io/qt-6/supported-platforms.html
|
||||
# https://ddalcino.github.io/aqt-list-server/
|
||||
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
qt:
|
||||
- 5.9.9
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt }}
|
||||
archives: qtbase icu
|
||||
tools: tools_cmake
|
||||
arch: gcc_64
|
||||
host: linux
|
||||
- run: |
|
||||
qmake --version
|
||||
cmake --version
|
||||
gcc --version
|
||||
g++ --version
|
||||
- run: |
|
||||
cmake -G "Unix Makefiles"
|
||||
cmake --build . -- -j12
|
||||
cmake --build . --target test
|
||||
- run: |
|
||||
sudo apt-get install lcov > /dev/null
|
||||
lcov --version
|
||||
lcov --capture --directory . -o coverage.info
|
||||
lcov -e coverage.info '**/src/**/*' -o coverage.info
|
||||
- uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
files: coverage.info
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -1,7 +1,15 @@
|
||||
/build
|
||||
/dist
|
||||
/node_modules
|
||||
*.autosave
|
||||
*.gcno
|
||||
*.gcda
|
||||
*.info
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
*.user
|
||||
Makefile
|
||||
coverage.info
|
||||
Makefile*
|
||||
moc_*.cpp
|
||||
moc_*.h
|
||||
|
6
.remarkrc.yml
Normal file
6
.remarkrc.yml
Normal file
@ -0,0 +1,6 @@
|
||||
plugins:
|
||||
- frontmatter
|
||||
- validate-links
|
||||
- preset-lint-recommended
|
||||
- preset-lint-markdown-style-guide
|
||||
- [lint-maximum-line-length, 100]
|
32
.travis.yml
32
.travis.yml
@ -1,32 +0,0 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: cpp
|
||||
compiler: gcc
|
||||
|
||||
before_install:
|
||||
- sudo add-apt-repository -y ppa:beineri/opt-qt542-trusty
|
||||
- sudo apt-get update -qq
|
||||
|
||||
install:
|
||||
- sudo apt-get install -qq qt54base
|
||||
- source /opt/qt54/bin/qt54-env.sh
|
||||
- wget http://archive.ubuntu.com/ubuntu/pool/universe/l/lcov/lcov_1.13.orig.tar.gz
|
||||
- tar xf lcov_1.13.orig.tar.gz
|
||||
- cd lcov-1.13/
|
||||
- sudo make install
|
||||
- cd ..
|
||||
|
||||
before_script:
|
||||
- qmake --version
|
||||
- lcov --version
|
||||
- gcc --version
|
||||
|
||||
script:
|
||||
- qmake qtpromise.pro CONFIG+=coverage
|
||||
- make -j4
|
||||
- make check --quiet
|
||||
- lcov -capture --directory . --o coverage.info
|
||||
- lcov -e coverage.info '**/src/**/*' -o coverage.info
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash) -f coverage.info
|
82
CMakeLists.txt
Normal file
82
CMakeLists.txt
Normal file
@ -0,0 +1,82 @@
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
|
||||
if(DEFINED PROJECT_NAME)
|
||||
set(SUBPROJECT ON)
|
||||
endif()
|
||||
|
||||
project(qtpromise VERSION 0.7.0 LANGUAGES CXX)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
|
||||
|
||||
find_package(QT 5.6.0 NAMES Qt6 Qt5 REQUIRED)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(qtpromise INTERFACE)
|
||||
add_library(qtpromise::qtpromise ALIAS qtpromise)
|
||||
|
||||
target_link_libraries(qtpromise INTERFACE Qt${QT_VERSION_MAJOR}::Core)
|
||||
target_include_directories(qtpromise INTERFACE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||
|
||||
add_definitions(
|
||||
-DQT_DEPRECATED_WARNINGS
|
||||
-DQT_NO_KEYWORDS
|
||||
)
|
||||
|
||||
# https://github.com/simonbrunel/qtpromise/issues/10
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
# https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
|
||||
add_compile_options(
|
||||
-Werror
|
||||
-Wpedantic
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wconversion
|
||||
-Wdouble-promotion
|
||||
-Wformat=2
|
||||
-Wlogical-op
|
||||
-Wmissing-noreturn
|
||||
-Wold-style-cast
|
||||
# -Wshadow # disabled due to many findings in the current code
|
||||
-Wsign-conversion
|
||||
-Wswitch-default
|
||||
-Wunused-local-typedefs
|
||||
-pedantic-errors
|
||||
)
|
||||
|
||||
# https://github.com/Barro/compiler-warnings/blob/master/gcc/warnings-gcc-6.txt
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6)
|
||||
add_compile_options(
|
||||
-Wduplicated-cond
|
||||
)
|
||||
endif()
|
||||
|
||||
# https://github.com/Barro/compiler-warnings/blob/master/gcc/warnings-gcc-7.txt
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 7)
|
||||
add_compile_options(
|
||||
-Wduplicated-branches
|
||||
)
|
||||
endif()
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
# https://clang.llvm.org/docs/DiagnosticsReference.html
|
||||
add_compile_options(
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
-Wsuggest-destructor-override
|
||||
-Wsuggest-override
|
||||
)
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
# https://docs.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level
|
||||
add_compile_options(
|
||||
/WX
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT SUBPROJECT)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
20
LICENSE
20
LICENSE
@ -1,9 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Simon Brunel
|
||||
Copyright (c) 2017-present Simon Brunel, https://github.com/simonbrunel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
441
README.md
441
README.md
@ -1,431 +1,28 @@
|
||||
<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>
|
||||
<p align="center">
|
||||
<img width="256" src="docs/.vuepress/public/hero.svg?sanitize=true">
|
||||
</p>
|
||||
|
||||
# QtPromise
|
||||
[](http://www.qpm.io/packages/com.github.simonbrunel.qtpromise/index.html) [](https://travis-ci.org/simonbrunel/qtpromise) [](https://codecov.io/gh/simonbrunel/qtpromise)
|
||||
<p align="center">
|
||||
<a href="https://www.qpm.io/packages/com.github.simonbrunel.qtpromise/index.html"><img src="https://img.shields.io/github/release/simonbrunel/qtpromise.svg?style=flat-square&label=qpm&colorB=4CAF50&maxAge=600" alt="Install"></a>
|
||||
<a href="https://travis-ci.com/simonbrunel/qtpromise"><img src="https://img.shields.io/travis/simonbrunel/qtpromise/master.svg?style=flat-square&maxAge=600" alt="Builds"></a>
|
||||
<a href="https://codecov.io/gh/simonbrunel/qtpromise"><img src="https://img.shields.io/codecov/c/github/simonbrunel/qtpromise.svg?style=flat-square&maxAge=600" alt="Coverage"></a>
|
||||
<a href="https://marketplace.qt.io/products/qtpromise"><img src="https://img.shields.io/static/v1?style=flat-square&label=Qt&message=Marketplace&colorB=40cd52&maxAge=600" alt="Marketplace"></a>
|
||||
</p>
|
||||
|
||||
## Overview
|
||||
|
||||
[Promises/A+](https://promisesaplus.com/) implementation for [Qt/C++](https://www.qt.io/).
|
||||
|
||||
Requires [Qt 5.4](https://www.qt.io/download/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects).
|
||||
Requires [Qt 5.6](https://doc.qt.io/qt-5/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects) or [Qt 6](https://doc.qt.io/qt-6/).
|
||||
|
||||
## 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-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(QVector<QPromise<T>>) -> QPromise<QVector<T>>`
|
||||
Returns a `QPromise<QVector<T>>` that fulfills when **all** `promises` of (the same) type `T` have been fulfilled. The `output` value is a vector containing **all** the values of `promises`, in the same order. If any of the given `promises` fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
|
||||
|
||||
```cpp
|
||||
QVector<QPromise<QByteArray> > promises{
|
||||
download(QUrl("http://a...")),
|
||||
download(QUrl("http://b...")),
|
||||
download(QUrl("http://c..."))
|
||||
};
|
||||
|
||||
auto output = QPromise<QByteArray>::all(promises);
|
||||
|
||||
// output type: QPromise<QVector<QByteArray>>
|
||||
output.then([](const QVector<QByteArray>& res) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
See also: [`qPromiseAll`](#helpers-qpromiseall)
|
||||
|
||||
## Helpers
|
||||
### <a name="helpers-qpromise"></a> `qPromise(T value) -> QPromise<R>`
|
||||
Similar to the `QPromise<T>::resolve` static method, creates a promise resolved from a given `value` without the extra typing:
|
||||
|
||||
```cpp
|
||||
auto promise = qPromise(); // QPromise<void>
|
||||
auto promise = qPromise(42); // QPromise<int>
|
||||
auto promise = qPromise(QString("foo")); // QPromise<QString>
|
||||
```
|
||||
|
||||
This method also allows to convert `QFuture<T>` to `QPromise<T>` delayed until the `QFuture` is finished ([read more](#qtconcurrent-convert)).
|
||||
|
||||
### <a name="helpers-qpromiseall"></a> `qPromiseAll(QVector<QPromise<T> promises) -> QPromise<QVector<T>>`
|
||||
This method simply calls the appropriated [`QPromise<T>::all`](#qpromise-all) static method based on the given `QVector` type. In some cases, this method is more convenient than the static one since it avoid some extra typing:
|
||||
|
||||
```cpp
|
||||
QVector<QPromise<QByteArray> > promises{...}
|
||||
|
||||
auto output = qPromiseAll(promises);
|
||||
// eq. QPromise<QByteArray>::all(promises)
|
||||
```
|
||||
- [Getting Started](https://qtpromise.netlify.com/qtpromise/getting-started.html)
|
||||
- [Qt Concurrent](https://qtpromise.netlify.com/qtpromise/qtconcurrent.html)
|
||||
- [Qt Signals](https://qtpromise.netlify.com/qtpromise/qtsignals.html)
|
||||
- [Thread-Safety](https://qtpromise.netlify.com/qtpromise/thread-safety.html)
|
||||
- [API Reference](https://qtpromise.netlify.com/qtpromise/api-reference.html)
|
||||
|
||||
## License
|
||||
|
||||
QtPromise is available under the [MIT license](LICENSE).
|
||||
|
44
cmake/QtPromiseAddTest.cmake
Normal file
44
cmake/QtPromiseAddTest.cmake
Normal file
@ -0,0 +1,44 @@
|
||||
function(qtpromise_add_test NAME)
|
||||
cmake_parse_arguments(_ARG "" "" "SOURCES;LIBRARIES" ${ARGN})
|
||||
|
||||
set(_TARGET qtpromise.tests.auto.${NAME})
|
||||
|
||||
add_executable(${_TARGET} ${_ARG_SOURCES})
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
target_link_libraries(${_TARGET} gcov)
|
||||
target_compile_options(${_TARGET}
|
||||
PRIVATE
|
||||
-fprofile-arcs
|
||||
-ftest-coverage
|
||||
-O0
|
||||
-g
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${_TARGET}
|
||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||
Qt${QT_VERSION_MAJOR}::Test
|
||||
qtpromise
|
||||
qtpromise.tests.utils
|
||||
${_ARG_LIBRARIES}
|
||||
)
|
||||
|
||||
add_test(NAME ${_TARGET}
|
||||
COMMAND ${_TARGET}
|
||||
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(qtpromise_add_tests GROUP)
|
||||
cmake_parse_arguments(_ARG "" "" "SOURCES" ${ARGN})
|
||||
|
||||
foreach(_FILE ${_ARG_SOURCES})
|
||||
get_filename_component(_FILE_NAME ${_FILE} NAME)
|
||||
if (_FILE_NAME MATCHES "^tst_(.+)\.cpp$")
|
||||
string(REGEX REPLACE "^tst_(.+)\.cpp$" "\\1" _TEST_NAME ${_FILE_NAME})
|
||||
qtpromise_add_test(${GROUP}.${_TEST_NAME} SOURCES ${_FILE} ${_ARG_UNPARSED_ARGUMENTS})
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
90
docs/.vuepress/config.js
Normal file
90
docs/.vuepress/config.js
Normal file
@ -0,0 +1,90 @@
|
||||
module.exports = {
|
||||
title: 'QtPromise',
|
||||
description: 'Promises/A+ implementation for Qt/C++',
|
||||
dest: 'dist/docs',
|
||||
head: [
|
||||
['link', { rel: 'icon', href: `/favicon.png` }],
|
||||
],
|
||||
plugins: [
|
||||
['@vuepress/google-analytics', {
|
||||
ga: 'UA-113899811-1'
|
||||
}]
|
||||
],
|
||||
themeConfig: {
|
||||
repo: 'simonbrunel/qtpromise',
|
||||
lastUpdated: 'Last Updated',
|
||||
smoothScroll: false,
|
||||
editLinks: true,
|
||||
sidebarDepth: 2,
|
||||
docsDir: 'docs',
|
||||
algolia: {
|
||||
apiKey: '0e6e9cccb8c2c360a5543e28c4e31cb8',
|
||||
indexName: 'qtpromise'
|
||||
},
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Guide', link: '/qtpromise/getting-started' },
|
||||
{ text: 'API Reference', link: '/qtpromise/api-reference' },
|
||||
],
|
||||
sidebar: [
|
||||
'/qtpromise/getting-started',
|
||||
'/qtpromise/qtconcurrent',
|
||||
'/qtpromise/qtsignals',
|
||||
'/qtpromise/thread-safety',
|
||||
{
|
||||
title: 'API Reference',
|
||||
path: '/qtpromise/api-reference',
|
||||
children: [
|
||||
//['/qtpromise/api-reference', 'Overview'],
|
||||
{
|
||||
title: 'QPromise',
|
||||
children: [
|
||||
'/qtpromise/qpromise/constructor',
|
||||
'/qtpromise/qpromise/convert',
|
||||
'/qtpromise/qpromise/delay',
|
||||
'/qtpromise/qpromise/each',
|
||||
'/qtpromise/qpromise/fail',
|
||||
'/qtpromise/qpromise/filter',
|
||||
'/qtpromise/qpromise/finally',
|
||||
'/qtpromise/qpromise/isfulfilled',
|
||||
'/qtpromise/qpromise/ispending',
|
||||
'/qtpromise/qpromise/isrejected',
|
||||
'/qtpromise/qpromise/map',
|
||||
'/qtpromise/qpromise/reduce',
|
||||
'/qtpromise/qpromise/tap',
|
||||
'/qtpromise/qpromise/tapfail',
|
||||
'/qtpromise/qpromise/then',
|
||||
'/qtpromise/qpromise/timeout',
|
||||
'/qtpromise/qpromise/wait',
|
||||
'/qtpromise/qpromise/reject',
|
||||
'/qtpromise/qpromise/resolve'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Helpers',
|
||||
children: [
|
||||
'/qtpromise/helpers/all',
|
||||
'/qtpromise/helpers/attempt',
|
||||
'/qtpromise/helpers/connect',
|
||||
'/qtpromise/helpers/each',
|
||||
'/qtpromise/helpers/filter',
|
||||
'/qtpromise/helpers/map',
|
||||
'/qtpromise/helpers/reduce',
|
||||
'/qtpromise/helpers/resolve'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Exceptions',
|
||||
children: [
|
||||
'/qtpromise/exceptions/canceled',
|
||||
'/qtpromise/exceptions/context',
|
||||
'/qtpromise/exceptions/conversion',
|
||||
'/qtpromise/exceptions/timeout',
|
||||
'/qtpromise/exceptions/undefined'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
BIN
docs/.vuepress/public/favicon.ico
Normal file
BIN
docs/.vuepress/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
docs/.vuepress/public/favicon.png
Normal file
BIN
docs/.vuepress/public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
47
docs/.vuepress/public/hero.svg
Normal file
47
docs/.vuepress/public/hero.svg
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
|
||||
<polygon fill="#F7E02F" points="440.999,446 0,446 0,135.382 69,66 511.999,66 511.999,374.906 "/>
|
||||
<g>
|
||||
<path d="M209.116,301.104c-27.241,0-46.231-7.334-56.971-22.002c-10.739-14.669-16.109-37.85-16.109-69.544
|
||||
c0-31.694,5.456-55.223,16.371-70.591c10.911-15.364,29.816-23.05,56.709-23.05c26.889,0,45.749,7.641,56.578,22.919
|
||||
c10.825,15.282,16.24,38.812,16.24,70.591c0,20.955-2.227,37.895-6.68,50.816c-4.452,12.925-11.746,22.788-21.871,29.599
|
||||
l22.002,35.361l-26.979,12.572l-23.312-38.242C221.775,300.581,216.45,301.104,209.116,301.104z M175.065,260.374
|
||||
c5.934,10.216,17.288,15.323,34.051,15.323s28.068-5.018,33.92-15.062c5.849-10.04,8.775-27.021,8.775-50.947
|
||||
c0-23.922-3.012-41.295-9.037-52.125c-6.024-10.825-17.247-16.24-33.659-16.24c-16.416,0-27.679,5.415-33.79,16.24
|
||||
c-6.115,10.83-9.167,28.117-9.167,51.863C166.159,233.177,169.126,250.158,175.065,260.374z"/>
|
||||
<path d="M382.776,191.616h-36.146v57.625c0,10.654,0.785,17.727,2.357,21.218c1.571,3.495,5.586,5.238,12.049,5.238l21.479-0.786
|
||||
l1.31,22.789c-11.701,2.267-20.606,3.404-26.718,3.404c-14.844,0-25.015-3.404-30.515-10.215
|
||||
c-5.501-6.811-8.251-19.646-8.251-38.505v-60.769h-16.764v-24.36h16.764v-37.98h28.289v37.98h36.146V191.616z"/>
|
||||
<path d="M152.877,382.938H142.83v15.285h-7.965v-49.157h18.012c11.147,0,16.721,5.502,16.721,16.505
|
||||
c0,5.646-1.399,9.952-4.198,12.918C162.601,381.456,158.426,382.938,152.877,382.938z M142.83,376.049h9.975
|
||||
c5.741,0,8.612-3.491,8.612-10.478c0-3.348-0.694-5.788-2.081-7.319c-1.388-1.531-3.565-2.297-6.531-2.297h-9.975V376.049z"/>
|
||||
<path d="M176.343,398.224v-35.882h7.75v4.306c4.066-2.63,8.133-4.329,12.2-5.095v7.822c-4.115,0.814-7.632,1.866-10.549,3.157
|
||||
l-1.579,0.646v25.045H176.343z"/>
|
||||
<path d="M203.936,366.218c2.463-3.109,6.566-4.665,12.307-4.665c5.741,0,9.843,1.556,12.308,4.665
|
||||
c2.463,3.11,3.696,7.774,3.696,13.993c0,6.22-1.197,10.908-3.588,14.065c-2.393,3.158-6.53,4.736-12.415,4.736
|
||||
c-5.884,0-10.023-1.578-12.415-4.736c-2.393-3.157-3.588-7.846-3.588-14.065C200.24,373.992,201.471,369.328,203.936,366.218z
|
||||
M209.784,389.54c1.1,1.914,3.253,2.871,6.458,2.871c3.205,0,5.357-0.957,6.459-2.871c1.1-1.913,1.65-5.047,1.65-9.4
|
||||
s-0.586-7.438-1.758-9.258c-1.173-1.817-3.29-2.727-6.351-2.727c-3.062,0-5.179,0.909-6.351,2.727
|
||||
c-1.173,1.819-1.758,4.904-1.758,9.258S208.683,387.627,209.784,389.54z"/>
|
||||
<path d="M247.818,398.224h-7.822v-35.882h7.75v2.225c3.396-2.009,6.506-3.014,9.329-3.014c4.162,0,7.199,1.173,9.113,3.517
|
||||
c4.354-2.344,8.684-3.517,12.99-3.517c4.305,0,7.344,1.328,9.113,3.982c1.77,2.655,2.656,7.141,2.656,13.456v19.232h-7.752v-19.018
|
||||
c0-3.875-0.395-6.625-1.184-8.252c-0.789-1.626-2.428-2.44-4.916-2.44c-2.152,0-4.473,0.479-6.961,1.436l-1.219,0.502
|
||||
c0.381,0.958,0.572,4.019,0.572,9.186v18.587h-7.75V379.78c0-4.257-0.383-7.199-1.148-8.826c-0.765-1.626-2.438-2.44-5.022-2.44
|
||||
c-2.393,0-4.618,0.479-6.674,1.436l-1.076,0.431V398.224z"/>
|
||||
<path d="M300.133,356.242v-8.253h7.822v8.253H300.133z M300.133,398.224v-35.882h7.822v35.882H300.133z"/>
|
||||
<path d="M342.33,369.733c-5.646-0.765-9.735-1.147-12.271-1.147s-4.294,0.299-5.274,0.896c-0.98,0.599-1.471,1.543-1.471,2.835
|
||||
s0.538,2.201,1.614,2.727c1.077,0.527,3.612,1.138,7.607,1.83c3.994,0.694,6.828,1.783,8.504,3.266
|
||||
c1.674,1.483,2.512,4.115,2.512,7.894c0,3.78-1.209,6.556-3.624,8.324c-2.417,1.771-5.945,2.655-10.585,2.655
|
||||
c-2.919,0-6.603-0.406-11.052-1.22l-2.225-0.358l0.287-6.53c5.741,0.766,9.878,1.147,12.415,1.147c2.535,0,4.342-0.311,5.418-0.933
|
||||
c1.076-0.621,1.614-1.65,1.614-3.086s-0.515-2.428-1.543-2.979c-1.029-0.549-3.492-1.147-7.392-1.794
|
||||
c-3.899-0.646-6.758-1.661-8.575-3.05c-1.819-1.387-2.728-3.922-2.728-7.606c0-3.684,1.256-6.435,3.768-8.253
|
||||
c2.512-1.817,5.729-2.727,9.652-2.727c3.062,0,6.817,0.383,11.267,1.147l2.225,0.431L342.33,369.733z"/>
|
||||
<path d="M376.632,391.765l2.009-0.215l0.144,5.813c-5.454,1.101-10.286,1.65-14.496,1.65c-5.311,0-9.126-1.458-11.446-4.377
|
||||
c-2.32-2.918-3.48-7.582-3.48-13.994c0-12.726,5.19-19.089,15.573-19.089c10.047,0,15.07,5.479,15.07,16.434l-0.503,5.598h-22.246
|
||||
c0.047,2.967,0.692,5.144,1.938,6.53c1.243,1.389,3.563,2.081,6.961,2.081C369.551,392.195,373.044,392.052,376.632,391.765z
|
||||
M372.327,377.556c0-3.54-0.563-6.016-1.687-7.427c-1.125-1.411-3.026-2.117-5.705-2.117c-2.681,0-4.629,0.742-5.849,2.225
|
||||
c-1.221,1.483-1.855,3.923-1.902,7.319H372.327z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
37
docs/.vuepress/styles/index.styl
Normal file
37
docs/.vuepress/styles/index.styl
Normal file
@ -0,0 +1,37 @@
|
||||
@import 'palette.styl'
|
||||
|
||||
a
|
||||
svg.icon.outbound
|
||||
margin-bottom -2px
|
||||
margin-left 2px
|
||||
|
||||
img + svg.icon.outbound
|
||||
display none
|
||||
|
||||
sup
|
||||
font-size 0.75em !important
|
||||
|
||||
.home .hero
|
||||
img
|
||||
margin 1.5rem auto !important
|
||||
.description
|
||||
display none !important
|
||||
#main-title
|
||||
display none !important
|
||||
|
||||
.page a code
|
||||
color $accentColor
|
||||
|
||||
.navbar
|
||||
-webkit-box-shadow 0 4px 4px -4px rgba(0,0,0,0.1)
|
||||
-moz-box-shadow 0 4px 4px -4px rgba(0,0,0,0.1)
|
||||
box-shadow 0 4px 4px -4px rgba(0,0,0,0.1)
|
||||
|
||||
.logo
|
||||
margin-right: 0.5rem;
|
||||
|
||||
.site-name
|
||||
font-size 1.25rem
|
||||
|
||||
.repo-link
|
||||
margin-left 1rem
|
2
docs/.vuepress/styles/palette.styl
Normal file
2
docs/.vuepress/styles/palette.styl
Normal file
@ -0,0 +1,2 @@
|
||||
$accentColor = #23b223
|
||||
$textColor = #404244
|
30
docs/README.md
Normal file
30
docs/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
home: true
|
||||
heroImage: /hero.svg
|
||||
footer: MIT Licensed | Copyright © Simon Brunel, https://github.com/simonbrunel
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.qpm.io/packages/com.github.simonbrunel.qtpromise/index.html"><img src="https://img.shields.io/github/release/simonbrunel/qtpromise.svg?style=flat-square&label=qpm&colorB=4CAF50&maxAge=600" alt="Install"></a>
|
||||
<a href="https://travis-ci.com/simonbrunel/qtpromise"><img src="https://img.shields.io/travis/simonbrunel/qtpromise/master.svg?style=flat-square&maxAge=600" alt="Builds"></a>
|
||||
<a href="https://codecov.io/gh/simonbrunel/qtpromise"><img src="https://img.shields.io/codecov/c/github/simonbrunel/qtpromise.svg?style=flat-square&maxAge=600" alt="Coverage"></a>
|
||||
<a href="https://marketplace.qt.io/products/qtpromise"><img src="https://img.shields.io/static/v1?style=flat-square&label=Qt&message=Marketplace&colorB=40cd52&maxAge=600" alt="Marketplace"></a>
|
||||
</p>
|
||||
|
||||
## Overview
|
||||
|
||||
[Promises/A+](https://promisesaplus.com/) implementation for [Qt/C++](https://www.qt.io/).
|
||||
|
||||
Requires [Qt 5.6](https://doc.qt.io/qt-5/) (or later) with [C++11 support enabled](https://wiki.qt.io/How_to_use_C++11_in_your_Qt_Projects) or [Qt 6](https://doc.qt.io/qt-6/).
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Getting Started](/qtpromise/getting-started.md)
|
||||
- [Qt Concurrent](/qtpromise/qtconcurrent.md)
|
||||
- [Qt Signals](/qtpromise/qtsignals.md)
|
||||
- [Thread-Safety](/qtpromise/thread-safety.md)
|
||||
- [API Reference](/qtpromise/api-reference.md)
|
||||
|
||||
## License
|
||||
|
||||
QtPromise is available under the [MIT license](https://github.com/simonbrunel/qtpromise/blob/master/LICENSE).
|
51
docs/qtpromise/api-reference.md
Normal file
51
docs/qtpromise/api-reference.md
Normal file
@ -0,0 +1,51 @@
|
||||
# API Reference
|
||||
|
||||
## Functions
|
||||
|
||||
- [`QPromise<T>::QPromise`](qpromise/constructor.md)
|
||||
- [`QPromise<T>::convert`](qpromise/convert.md)
|
||||
- [`QPromise<T>::delay`](qpromise/delay.md)
|
||||
- [`QPromise<T>::each`](qpromise/each.md)
|
||||
- [`QPromise<T>::fail`](qpromise/fail.md)
|
||||
- [`QPromise<T>::filter`](qpromise/filter.md)
|
||||
- [`QPromise<T>::finally`](qpromise/finally.md)
|
||||
- [`QPromise<T>::isFulfilled`](qpromise/isfulfilled.md)
|
||||
- [`QPromise<T>::isPending`](qpromise/ispending.md)
|
||||
- [`QPromise<T>::isRejected`](qpromise/isrejected.md)
|
||||
- [`QPromise<T>::map`](qpromise/map.md)
|
||||
- [`QPromise<T>::reduce`](qpromise/reduce.md)
|
||||
- [`QPromise<T>::tap`](qpromise/tap.md)
|
||||
- [`QPromise<T>::tapFail`](qpromise/tapfail.md)
|
||||
- [`QPromise<T>::then`](qpromise/then.md)
|
||||
- [`QPromise<T>::timeout`](qpromise/timeout.md)
|
||||
- [`QPromise<T>::wait`](qpromise/wait.md)
|
||||
|
||||
## Static Functions
|
||||
|
||||
- [`(static) QPromise<T>::reject`](qpromise/reject.md)
|
||||
- [`(static) QPromise<T>::resolve`](qpromise/resolve.md)
|
||||
|
||||
## Helpers
|
||||
|
||||
- [`QtPromise::all`](helpers/all.md)
|
||||
- [`QtPromise::attempt`](helpers/attempt.md)
|
||||
- [`QtPromise::connect`](helpers/connect.md)
|
||||
- [`QtPromise::each`](helpers/each.md)
|
||||
- [`QtPromise::filter`](helpers/filter.md)
|
||||
- [`QtPromise::map`](helpers/map.md)
|
||||
- [`QtPromise::reduce`](helpers/reduce.md)
|
||||
- [`QtPromise::resolve`](helpers/resolve.md)
|
||||
|
||||
## Exceptions
|
||||
|
||||
- [`QPromiseCanceledException`](exceptions/canceled.md)
|
||||
- [`QPromiseContextException`](exceptions/context.md)
|
||||
- [`QPromiseConversionException`](exceptions/conversion.md)
|
||||
- [`QPromiseTimeoutException`](exceptions/timeout.md)
|
||||
- [`QPromiseUndefinedException`](exceptions/undefined.md)
|
||||
|
||||
## Deprecations
|
||||
|
||||
- `(static) QPromise<T>::all`: use [`QtPromise::all`](helpers/all.md) instead (since 0.5.0)
|
||||
- `QtPromise::qPromise`: use [`QtPromise::resolve`](helpers/resolve.md) instead (since 0.5.0)
|
||||
- `QtPromise::qPromiseAll`: use [`QtPromise::all`](helpers/all.md) instead (since 0.5.0)
|
17
docs/qtpromise/exceptions/canceled.md
Normal file
17
docs/qtpromise/exceptions/canceled.md
Normal file
@ -0,0 +1,17 @@
|
||||
# QPromiseCanceledException
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
This exception is thrown for promise created from a [`QFuture`](../qtconcurrent.md) which has been
|
||||
canceled (e.g. using [`QFuture::cancel()`](http://doc.qt.io/qt-5/qfuture.html#cancel)), for example:
|
||||
|
||||
```cpp
|
||||
auto output = QtPromise::resolve(future)
|
||||
.fail([](const QPromiseCanceledException& error) {
|
||||
// `future` has been canceled!
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
QtPromise doesn't support promise cancelation (yet?)
|
||||
:::
|
14
docs/qtpromise/exceptions/context.md
Normal file
14
docs/qtpromise/exceptions/context.md
Normal file
@ -0,0 +1,14 @@
|
||||
# QPromiseContextException
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
When a promise is created using [`QtPromise::connect()`](../helpers/connect.md), this exception is
|
||||
thrown when the `sender` object is destroyed, for example:
|
||||
|
||||
```cpp
|
||||
auto promise = QtPromise::connect(sender, &Object::finished, &Object::error);
|
||||
|
||||
promise.fail([](const QPromiseContextException&) {
|
||||
// 'sender' has been destroyed.
|
||||
})
|
||||
```
|
13
docs/qtpromise/exceptions/conversion.md
Normal file
13
docs/qtpromise/exceptions/conversion.md
Normal file
@ -0,0 +1,13 @@
|
||||
# QPromiseConversionException
|
||||
|
||||
*Since: 0.7.0*
|
||||
|
||||
This exception is thrown whenever a promise result conversion fails, for example:
|
||||
|
||||
```cpp
|
||||
QPromise<QVariant> input = {...};
|
||||
auto output = input.convert<int>()
|
||||
.fail([](const QPromiseconversionException& e) {
|
||||
// conversion may fail because input could not be converted to number
|
||||
});
|
||||
```
|
14
docs/qtpromise/exceptions/timeout.md
Normal file
14
docs/qtpromise/exceptions/timeout.md
Normal file
@ -0,0 +1,14 @@
|
||||
# QPromiseTimeoutException
|
||||
|
||||
*Since: 0.2.0*
|
||||
|
||||
This is the default exception thrown when reaching the time limit when using the
|
||||
[`QPromise::timeout()`](../qpromise/timeout.md) method, for example:
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.timeout(2000)
|
||||
.fail([](const QPromiseTimeoutException& error) {
|
||||
// operation timed out after 2s!
|
||||
});
|
||||
```
|
21
docs/qtpromise/exceptions/undefined.md
Normal file
21
docs/qtpromise/exceptions/undefined.md
Normal file
@ -0,0 +1,21 @@
|
||||
# QPromiseUndefinedException
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
This exception is thrown when rejecting a promise with no explicit reason, for example:
|
||||
|
||||
```cpp
|
||||
QPromise<int> promise([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>& reject) {
|
||||
async_method([=](bool success, int result) {
|
||||
if (success) {
|
||||
resolve(result);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
promise.fail([](const QPromiseUndefinedException&) {
|
||||
// promise rejected without reason!
|
||||
})
|
||||
```
|
211
docs/qtpromise/getting-started.md
Normal file
211
docs/qtpromise/getting-started.md
Normal file
@ -0,0 +1,211 @@
|
||||
# Getting Started
|
||||
|
||||
## Installation
|
||||
|
||||
### Using CMake
|
||||
|
||||
*Since: 0.6.0*
|
||||
|
||||
If your project uses [CMake](https://cmake.org/) as build system, QtPromise can be installed using
|
||||
the `FetchContent` module. Please refer to the [CMake (FetchContent)](#cmake-fetchcontent) section
|
||||
for details of using this method.
|
||||
|
||||
### Using qpm
|
||||
|
||||
If your project is configured to use [qpm](https://www.qpm.io/), QtPromise can be installed using
|
||||
the following command:
|
||||
|
||||
```sh
|
||||
qpm install com.github.simonbrunel.qtpromise
|
||||
```
|
||||
|
||||
See also: [com.github.simonbrunel.qtpromise](https://www.qpm.io/packages/com.github.simonbrunel.qtpromise/)
|
||||
|
||||
### Using Git
|
||||
|
||||
If your project uses [Git](https://git-scm.com/) as version control system, QtPromise can be
|
||||
installed either as a [`subtree`](#subtree) or a [`submodule`](#submodule). Read more about these
|
||||
commands in ["Git: submodules vs subtrees"](https://nering.dev/2016/git-submodules-vs-subtrees/)
|
||||
which provides a good comparison between these two workflows.
|
||||
|
||||
The following examples install QtPromise version 0.7.0 under the `3rdparty/qtpromise` subdirectory.
|
||||
Note that the install directory is arbitrary and can be any empty directory under your repository.
|
||||
Once installed, refer to the [CMake](#cmake) or [qmake](#qmake) sections for details of integrating
|
||||
QtPromise into your project.
|
||||
|
||||
#### subtree
|
||||
|
||||
```sh
|
||||
cd <your/project/repository>
|
||||
git remote add qtpromise https://github.com/simonbrunel/qtpromise.git
|
||||
git subtree add -P 3rdparty/qtpromise qtpromise v0.7.0 --squash -m "Add QtPromise v0.7.0"
|
||||
```
|
||||
|
||||
#### submodule
|
||||
|
||||
```sh
|
||||
cd <your/project/repository>
|
||||
git submodule add https://github.com/simonbrunel/qtpromise.git 3rdparty/qtpromise
|
||||
cd 3rdparty/qtpromise
|
||||
git checkout v0.7.0
|
||||
cd ../..
|
||||
git add 3rdparty/qtpromise
|
||||
git commit -m "Add QtPromise v0.7.0"
|
||||
```
|
||||
|
||||
### Download
|
||||
|
||||
QtPromise can be downloaded from the [GitHub release page](https://github.com/simonbrunel/qtpromise/releases)
|
||||
as a `zip` or `tar.gz` archive. Under Linux, you can use the following commands:
|
||||
|
||||
```sh
|
||||
cd <your/project/repository>
|
||||
wget -q -O qtpromise.tar.gz https://github.com/simonbrunel/qtpromise/archive/v0.7.0.tar.gz
|
||||
tar xzf qtpromise.tar.gz --strip 1 --one-top-level=3rdparty/qtpromise
|
||||
rm qtpromise.tar.gz
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
QtPromise is a [header-only](https://en.wikipedia.org/wiki/Header-only) library. Integrating it
|
||||
within your project consists only in configuring the include path(s) to the library headers. To
|
||||
simplify this step, [qmake](#qmake) and [CMake](#cmake) integrations are provided.
|
||||
|
||||
### CMake
|
||||
|
||||
*Since: 0.6.0*
|
||||
|
||||
After installing QtPromise using [Git](#using-git) or the [download](#download) method, you can use
|
||||
the [`add_subdirectory`](https://cmake.org/cmake/help/latest/command/add_subdirectory.html) command
|
||||
to make its targets available to your CMake project:
|
||||
|
||||
```cmake
|
||||
add_subdirectory(<path/to/qtpromise>)
|
||||
|
||||
target_link_libraries(<target> qtpromise)
|
||||
```
|
||||
|
||||
### CMake (FetchContent)
|
||||
|
||||
*Since: 0.6.0*
|
||||
|
||||
Alternatively, the [`FetchContent`](https://cmake.org/cmake/help/latest/module/FetchContent.html)
|
||||
module (**CMake 3.11+**) allows to install QtPromise from your CMake project at configure time:
|
||||
|
||||
```cmake
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(qtpromise
|
||||
GIT_REPOSITORY https://github.com/simonbrunel/qtpromise.git
|
||||
GIT_TAG v0.7.0
|
||||
GIT_SHALLOW true
|
||||
)
|
||||
|
||||
# CMake v3.14+
|
||||
FetchContent_MakeAvailable(qtpromise)
|
||||
|
||||
target_link_libraries(<target> qtpromise)
|
||||
```
|
||||
|
||||
If your CMake version **is prior to v3.14**, you need to explicitly define the population steps:
|
||||
|
||||
```cmake
|
||||
# CMake v3.11+ (alternative for FetchContent_MakeAvailable)
|
||||
FetchContent_GetProperties(qtpromise)
|
||||
if(NOT qtpromise_POPULATED)
|
||||
FetchContent_Populate(qtpromise)
|
||||
add_subdirectory(
|
||||
${qtpromise_SOURCE_DIR}
|
||||
${qtpromise_BINARY_DIR}
|
||||
)
|
||||
endif()
|
||||
```
|
||||
|
||||
### qmake
|
||||
|
||||
After installing QtPromise using [Git](#using-git) or the [download](#download) method, you can
|
||||
[include](https://doc.qt.io/qt-5/qmake-test-function-reference.html#include-filename) `qtpromise.pri`
|
||||
file from the install directory:
|
||||
|
||||
```cmake
|
||||
include(<path/to/qtpromise>/qtpromise.pri)
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
To start using QtPromise in your code, you first need to include the single module header:
|
||||
|
||||
```cpp
|
||||
#include <QtPromise>
|
||||
```
|
||||
|
||||
The following `download` function creates a [promise from callbacks](qpromise/constructor.md) which
|
||||
will be resolved when the network request is finished:
|
||||
|
||||
```cpp
|
||||
QtPromise::QPromise<QByteArray> download(const QUrl& url)
|
||||
{
|
||||
return QtPromise::QPromise<QByteArray>{[&](
|
||||
const QtPromise::QPromiseResolve<QByteArray>& resolve,
|
||||
const QtPromise::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
|
||||
QtPromise::QPromise<Entries> uncompress(const QByteArray& data)
|
||||
{
|
||||
return QtPromise::resolve(QtConcurrent::run([](const QByteArray& data) {
|
||||
Entries entries;
|
||||
|
||||
// {...} uncompress data and parse content.
|
||||
|
||||
if (error) {
|
||||
throw MalformedException{};
|
||||
}
|
||||
|
||||
return entries;
|
||||
}, data));
|
||||
}
|
||||
```
|
||||
|
||||
It's then easy to chain the whole asynchronous process using promises:
|
||||
|
||||
- initiate the promise chain by downloading a specific URL,
|
||||
- [`then`](qpromise/then.md) *and only if download succeeded*, uncompress received data,
|
||||
- [`then`](qpromise/then.md) validate and process the uncompressed entries,
|
||||
- [`finally`](qpromise/finally.md) perform operations whatever the process succeeded or failed,
|
||||
- and handle specific errors using [`fail`](qpromise/fail.md).
|
||||
|
||||
```cpp
|
||||
download(url).then(&uncompress).then([](const Entries& entries) {
|
||||
if (entries.isEmpty()) {
|
||||
throw UpdateException{"No entries"};
|
||||
}
|
||||
// {...} process entries
|
||||
}).finally([]() {
|
||||
// {...} cleanup
|
||||
}).fail([](QNetworkReply::NetworkError error) {
|
||||
// {...} handle network error
|
||||
}).fail([](const UpdateException& error) {
|
||||
// {...} handle update error
|
||||
}).fail([]() {
|
||||
// {...} catch all
|
||||
});
|
||||
```
|
||||
|
||||
Note that `MalformedException` in the example above is thrown from a QtConcurrent thread and should
|
||||
meet [specific conditions](qtconcurrent.md#error).
|
37
docs/qtpromise/helpers/all.md
Normal file
37
docs/qtpromise/helpers/all.md
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
title: all
|
||||
---
|
||||
|
||||
# QtPromise::all
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
```cpp
|
||||
QtPromise::all(Sequence<QPromise<T>> promises) -> QPromise<QVector<T>>
|
||||
QtPromise::all(Sequence<QPromise<void>> promises) -> QPromise<void>
|
||||
```
|
||||
|
||||
Returns a `QPromise<QVector<T>>` (or `QPromise<void>`) that fulfills when **all** `promises` of
|
||||
(the same) type `T` have been fulfilled. The `output` value is a vector containing all the values
|
||||
of `promises`, in the same order, i.e., at the respective positions to the original sequence,
|
||||
regardless of completion order.
|
||||
|
||||
If any of the given `promises` fail, `output` immediately rejects with the error of the promise that
|
||||
rejected, whether or not the other promises are resolved.
|
||||
|
||||
`Sequence` is any STL compatible container (eg. `QVector`, `QList`, `std::vector`, etc.)
|
||||
|
||||
```cpp
|
||||
QVector<QPromise<QByteArray> > promises{
|
||||
download(QUrl("http://a...")),
|
||||
download(QUrl("http://b...")),
|
||||
download(QUrl("http://c..."))
|
||||
};
|
||||
|
||||
auto output = QtPromise::all(promises);
|
||||
|
||||
// output type: QPromise<QVector<QByteArray>>
|
||||
output.then([](const QVector<QByteArray>& res) {
|
||||
// {...}
|
||||
});
|
||||
```
|
47
docs/qtpromise/helpers/attempt.md
Normal file
47
docs/qtpromise/helpers/attempt.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: attempt
|
||||
---
|
||||
|
||||
# QtPromise::attempt
|
||||
|
||||
*Since: 0.4.0*
|
||||
|
||||
```cpp
|
||||
QtPromise::attempt(Functor functor, Args...) -> QPromise<R>
|
||||
|
||||
// With:
|
||||
// - Functor: Function(Args...) -> R | QPromise<R>
|
||||
```
|
||||
|
||||
Calls `functor` immediately and returns a promise fulfilled with the value returned by `functor`.
|
||||
Any synchronous exceptions will be turned into rejections on the returned promise. This is a
|
||||
convenient method that can be used instead of handling both synchronous and asynchronous exception
|
||||
flows.
|
||||
|
||||
The type `R` of the `output` promise depends on the type returned by the `functor` function. If
|
||||
`functor` returns a promise (or `QFuture`), the `output` promise is delayed and will be resolved
|
||||
by the returned promise.
|
||||
|
||||
```cpp
|
||||
QPromise<QByteArray> download(const QUrl& url);
|
||||
|
||||
QPromise<QByteArray> process(const QUrl& url)
|
||||
{
|
||||
return QtPromise::attempt([&]() {
|
||||
if (!url.isValid()) {
|
||||
throw InvalidUrlException{};
|
||||
}
|
||||
|
||||
return download(url);
|
||||
}
|
||||
}
|
||||
|
||||
auto output = process(url);
|
||||
|
||||
// 'output' type: QPromise<QByteArray>
|
||||
output.then([](const QByteArray& res) {
|
||||
// {...}
|
||||
}).fail([](const InvalidUrlException& error) {
|
||||
// {...}
|
||||
});
|
||||
```
|
53
docs/qtpromise/helpers/connect.md
Normal file
53
docs/qtpromise/helpers/connect.md
Normal file
@ -0,0 +1,53 @@
|
||||
---
|
||||
title: connect
|
||||
---
|
||||
|
||||
# QtPromise::connect
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
```cpp
|
||||
(1) QtPromise::connect(QObject* sender, Signal(T) resolver) -> QPromise<T>
|
||||
(2) QtPromise::connect(QObject* sender, Signal(T) resolver, Signal(R) rejecter) -> QPromise<T>
|
||||
(3) QtPromise::connect(QObject* sender, Signal(T) resolver, QObject* sender2, Signal(R) rejecter) -> QPromise<T>
|
||||
```
|
||||
|
||||
Creates a `QPromise<T>` that will be fulfilled with the `resolver` signal's first argument, or a
|
||||
`QPromise<void>` if `resolver` doesn't provide any argument.
|
||||
|
||||
The second `(2)` and third `(3)` variants of this method will reject the `output` promise when the
|
||||
`rejecter` signal is emitted. The rejection reason is the value of the `rejecter` signal's first
|
||||
argument or [`QPromiseUndefinedException`](../exceptions/undefined.md) if `rejected` doesn't provide
|
||||
any argument.
|
||||
|
||||
Additionally, the `output` promise will be automatically rejected with [`QPromiseContextException`](../exceptions/context.md)
|
||||
if `sender` is destroyed before the promise is resolved (that doesn't apply to `sender2`).
|
||||
|
||||
```cpp
|
||||
class Sender : public QObject
|
||||
{
|
||||
Q_SIGNALS:
|
||||
void finished(const QByteArray&);
|
||||
void error(ErrorCode);
|
||||
};
|
||||
|
||||
auto sender = new Sender{};
|
||||
auto output = QtPromise::connect(sender, &Sender::finished, &Sender::error);
|
||||
|
||||
// 'output' resolves as soon as one of the following events happens:
|
||||
// - the 'sender' object is destroyed, the promise is rejected
|
||||
// - the 'finished' signal is emitted, the promise is fulfilled
|
||||
// - the 'error' signal is emitted, the promise is rejected
|
||||
|
||||
// 'output' type: QPromise<QByteArray>
|
||||
output.then([](const QByteArray& res) {
|
||||
// 'res' is the first argument of the 'finished' signal.
|
||||
}).fail([](ErrorCode error) {
|
||||
// 'error' is the first argument of the 'error' signal.
|
||||
}).fail([](const QPromiseContextException& error) {
|
||||
// the 'sender' object has been destroyed before any of
|
||||
// the 'finished' or 'error' signals have been emitted.
|
||||
});
|
||||
```
|
||||
|
||||
See also the [`Qt Signals`](../qtsignals.md) section for more examples.
|
44
docs/qtpromise/helpers/each.md
Normal file
44
docs/qtpromise/helpers/each.md
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: each
|
||||
---
|
||||
|
||||
# QtPromise::each
|
||||
|
||||
*Since: 0.4.0*
|
||||
|
||||
```cpp
|
||||
QtPromise::each(Sequence<T> values, Functor functor) -> QPromise<Sequence<T>>
|
||||
|
||||
// With:
|
||||
// - Sequence: STL compatible container (e.g. QVector, etc.)
|
||||
// - Functor: Function(T value, int index) -> void | QPromise<void>
|
||||
```
|
||||
|
||||
Calls the given `functor` on each element in `values` then resolves to the original sequence
|
||||
unmodified. If `functor` throws, `output` is rejected with the new exception.
|
||||
|
||||
If `functor` returns a promise (or `QFuture`), the `output` promise is delayed until all the
|
||||
promises are resolved. If any of the promises fail, `output` immediately rejects with the error
|
||||
of the promise that rejected, whether or not the other promises are resolved.
|
||||
|
||||
```cpp
|
||||
auto output = QtPromise::each(QVector<QUrl>{
|
||||
QUrl("http://a..."),
|
||||
QUrl("http://b..."),
|
||||
QUrl("http://c...")
|
||||
}, [](const QUrl& url, ...) {
|
||||
return QPromise<void>{[&](auto resolve, auto reject) {
|
||||
// process url asynchronously ...
|
||||
}};
|
||||
});
|
||||
|
||||
// `output` resolves as soon as all promises returned by
|
||||
// `functor` are fulfilled or at least one is rejected.
|
||||
|
||||
// output type: QPromise<QVector<QUrl>>
|
||||
output.then([](const QVector<QUrl>& res) {
|
||||
// 'res' contains the original values
|
||||
});
|
||||
```
|
||||
|
||||
See also: [`QPromise<T>::each`](../qpromise/each.md)
|
52
docs/qtpromise/helpers/filter.md
Normal file
52
docs/qtpromise/helpers/filter.md
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: filter
|
||||
---
|
||||
|
||||
# QtPromise::filter
|
||||
|
||||
*Since: 0.4.0*
|
||||
|
||||
```cpp
|
||||
QtPromise::filter(Sequence<T> values, Filterer filterer) -> QPromise<Sequence<T>>
|
||||
|
||||
// With:
|
||||
// - Sequence: STL compatible container (e.g. QVector, etc.)
|
||||
// - Filterer: Function(T value, int index) -> bool
|
||||
```
|
||||
|
||||
Iterates over `values` and [filters the sequence](https://en.wikipedia.org/wiki/Filter_%28higher-order_function%29)
|
||||
to another using the given `filterer` function. If `filterer` returns `true`, a copy of the item
|
||||
is put in the `output` sequence, otherwise, the item will not appear in `output`. If `filterer`
|
||||
throws, `output` is rejected with the new exception.
|
||||
|
||||
If `filterer` returns a promise (or `QFuture`), the `output` promise is delayed until all the
|
||||
promises are resolved. If any of the promises fail, `output` immediately rejects with the error
|
||||
of the promise that rejected, whether or not the other promises are resolved.
|
||||
|
||||
```cpp
|
||||
auto output = QtPromise::filter(QVector{
|
||||
QUrl("http://a..."),
|
||||
QUrl("http://b..."),
|
||||
QUrl("http://c...")
|
||||
}, [](const QUrl& url, ...) {
|
||||
return QPromise<bool>{[&](auto resolve, auto reject) {
|
||||
// resolve(true) if 'url' is reachable, else resolve(false)
|
||||
// {...}
|
||||
}};
|
||||
});
|
||||
|
||||
// 'output' resolves as soon as all promises returned by
|
||||
// 'filterer' are fulfilled or at least one is rejected.
|
||||
|
||||
// 'output' type: QPromise<QVector<QUrl>>
|
||||
output.then([](const QVector<QUrl>& res) {
|
||||
// 'res' contains only reachable URLs
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
The order of the output sequence values is guarantee to be the same as the original sequence,
|
||||
regardless of completion order of the promises returned by `filterer`.
|
||||
:::
|
||||
|
||||
See also: [`QPromise<T>::filter`](../qpromise/filter.md)
|
51
docs/qtpromise/helpers/map.md
Normal file
51
docs/qtpromise/helpers/map.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: map
|
||||
---
|
||||
|
||||
# QtPromise::map
|
||||
|
||||
*Since: 0.4.0*
|
||||
|
||||
```cpp
|
||||
QtPromise::map(Sequence<T> values, Mapper mapper) -> QPromise<QVector<R>>
|
||||
|
||||
// With:
|
||||
// - Sequence: STL compatible container (e.g. QVector, etc.)
|
||||
// - Mapper: Function(T value, int index) -> R | QPromise<R>
|
||||
```
|
||||
|
||||
Iterates over `values` and [maps the sequence](https://en.wikipedia.org/wiki/Map_%28higher-order_function%29)
|
||||
to another using the given `mapper` function. The type returned by `mapper` determines the type of
|
||||
the `output` promise. If `mapper` throws, `output` is rejected with the new exception.
|
||||
|
||||
If `mapper` returns a promise (or `QFuture`), the `output` promise is delayed until all the promises
|
||||
are resolved. If any of the promises fails, `output` immediately rejects with the error of the
|
||||
promise that rejected, whether or not the other promises are resolved.
|
||||
|
||||
```cpp
|
||||
auto output = QtPromise::map(QVector{
|
||||
QUrl("http://a..."),
|
||||
QUrl("http://b..."),
|
||||
QUrl("http://c...")
|
||||
}, [](const QUrl& url, ...) {
|
||||
return QPromise<QByteArray>{[&](auto resolve, auto reject) {
|
||||
// download content at url and resolve
|
||||
// {...}
|
||||
}};
|
||||
});
|
||||
|
||||
// 'output' resolves as soon as all promises returned by
|
||||
// 'mapper' are fulfilled or at least one is rejected.
|
||||
|
||||
// 'output' type: QPromise<QVector<QByteArray>>
|
||||
output.then([](const QVector<QByteArray>& res) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
The order of the output sequence values is guarantee to be the same as the original sequence,
|
||||
regardless of completion order of the promises returned by `mapper`.
|
||||
:::
|
||||
|
||||
See also: [`QPromise<T>::map`](../qpromise/map.md)
|
55
docs/qtpromise/helpers/reduce.md
Normal file
55
docs/qtpromise/helpers/reduce.md
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: reduce
|
||||
---
|
||||
|
||||
# QtPromise::reduce
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
```cpp
|
||||
QPromise::reduce(Sequence<T|QPromise<T>> values, Reducer reducer) -> QPromise<T>
|
||||
QPromise::reduce(Sequence<T|QPromise<T>> values, Reducer reducer, R|QPromise<R> initialValue) -> QPromise<R>
|
||||
|
||||
// With:
|
||||
// - Sequence: STL compatible container (e.g. QVector, etc.)
|
||||
// - Reducer: Function(T|R accumulator, T item, int index) -> R|QPromise<R>
|
||||
```
|
||||
|
||||
Iterates over `values` and [reduces the sequence to a single value](https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29)
|
||||
using the given `reducer` function and an optional `initialValue`. The type returned by `reducer`
|
||||
determines the type of the `output` promise. If `reducer` throws, `output` is rejected with the
|
||||
new exception.
|
||||
|
||||
If `reducer` returns a promise (or `QFuture`), then the result of the promise is awaited, before
|
||||
continuing with next iteration. If any promise in the `values` sequence is rejected or a promise
|
||||
returned by the `reducer` function is rejected, `output` immediately rejects with the error of
|
||||
the promise that rejected.
|
||||
|
||||
```cpp
|
||||
// Concatenate the content of the given files, read asynchronously
|
||||
auto output = QtPromise::reduce(QList<QUrl>{
|
||||
"file:f0.txt", // contains "foo"
|
||||
"file:f1.txt", // contains "bar"
|
||||
"file:f2.txt" // contains "42"
|
||||
}, [](const QString& acc, const QString& cur, int idx) {
|
||||
return readAsync(cur).then([=](const QString& res) {
|
||||
return QString{"%1;%2:%3"}.arg(acc).arg(idx).arg(res);
|
||||
});
|
||||
}, QString{"index:text"});
|
||||
|
||||
// 'output' resolves as soon as all promises returned by
|
||||
// 'reducer' are fulfilled or at least one is rejected.
|
||||
|
||||
// 'output' type: QPromise<QString>
|
||||
output.then([](const QString& res) {
|
||||
// res == "index:text;0:foo;1:bar;2:42"
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
::: warning IMPORTANT
|
||||
The first time `reducer` is called, if no `initialValue` is provided, `accumulator` will be equal
|
||||
to the first value in the sequence, and `currentValue` to the second one (thus index will be `1`).
|
||||
:::
|
||||
|
||||
See also: [`QPromise<T>::reduce`](../qpromise/reduce.md)
|
23
docs/qtpromise/helpers/resolve.md
Normal file
23
docs/qtpromise/helpers/resolve.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: resolve
|
||||
---
|
||||
|
||||
# QtPromise::resolve
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
```cpp
|
||||
QtPromise::resolve(T value) -> QPromise<R>
|
||||
```
|
||||
|
||||
Similar to the [`QPromise<T>::resolve`](../qpromise/resolve.md) static method, creates a promise
|
||||
resolved from a given `value` but without the extra typing:
|
||||
|
||||
```cpp
|
||||
auto promise = QtPromise::resolve(); // QPromise<void>
|
||||
auto promise = QtPromise::resolve(42); // QPromise<int>
|
||||
auto promise = QtPromise::resolve(QString{"foo"}); // QPromise<QString>
|
||||
```
|
||||
|
||||
This method also allows to convert `QFuture<T>` to `QPromise<T>`, delayed until the `QFuture` is
|
||||
finished ([read more](../qtconcurrent.md#convert)).
|
70
docs/qtpromise/qpromise/constructor.md
Normal file
70
docs/qtpromise/qpromise/constructor.md
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
title: constructor
|
||||
---
|
||||
|
||||
# QPromise::QPromise
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::QPromise(Function resolver)
|
||||
```
|
||||
|
||||
Creates a new promise that will be fulfilled or rejected by the given `resolver` lambda:
|
||||
|
||||
```cpp
|
||||
QPromise<int> promise([](const QPromiseResolve<int>& resolve, const QPromiseReject<int>& reject) {
|
||||
async_method([=](bool success, int result) {
|
||||
if (success) {
|
||||
resolve(result);
|
||||
} else {
|
||||
reject(customException{});
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
`QPromise<void>` is specialized to not contain any value, meaning that the `resolve` callback takes
|
||||
no argument.
|
||||
:::
|
||||
|
||||
C++14 alternative:
|
||||
|
||||
```cpp
|
||||
QPromise<int> promise{[](const auto& resolve, const auto& reject) {
|
||||
// {...}
|
||||
}};
|
||||
```
|
||||
|
||||
## Undefined rejection reason
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
While not recommended because it makes tracking errors more difficult, it's also possible to reject
|
||||
a promise without explicit reason, in which case, a built-in [`QPromiseUndefinedException`](../exceptions/undefined.md)
|
||||
is thrown:
|
||||
|
||||
```cpp
|
||||
QPromise<int> promise{[](const QPromiseResolve<int>& resolve, const QPromiseReject<int>& reject) {
|
||||
async_method([=](bool success, int result) {
|
||||
if (success) {
|
||||
resolve(result);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}};
|
||||
```
|
||||
|
||||
```cpp
|
||||
// The exception can be caught explicitly
|
||||
promise.fail([](const QPromiseUndefinedException&) {
|
||||
// { ... }
|
||||
})
|
||||
|
||||
// ... or implicitly (since undefined)
|
||||
promise.fail([]() {
|
||||
// { ... }
|
||||
})
|
||||
```
|
124
docs/qtpromise/qpromise/convert.md
Normal file
124
docs/qtpromise/qpromise/convert.md
Normal file
@ -0,0 +1,124 @@
|
||||
---
|
||||
title: .convert
|
||||
---
|
||||
|
||||
# QPromise::convert
|
||||
|
||||
*Since: 0.7.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::convert<U>() -> QPromise<U>
|
||||
```
|
||||
|
||||
This method converts the resolved value of `QPromise<T>` to the type `U`. Depending on types `T`
|
||||
and `U`, it performs a [static cast](https://en.cppreference.com/w/cpp/language/static_cast),
|
||||
calls a [converting constructor](https://en.cppreference.com/w/cpp/language/converting_constructor),
|
||||
or tries to convert using [QVariant](https://doc.qt.io/qt-5/qvariant.html).
|
||||
|
||||
If `T` and `U` are [fundamental types](https://en.cppreference.com/w/cpp/language/types) or
|
||||
[enumerations](https://en.cppreference.com/w/cpp/language/enum), the result of the conversion is
|
||||
the same as calling `static_cast<U>` for type `T`:
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
|
||||
// output type: QPromise<double>
|
||||
auto output = input.convert<double>();
|
||||
|
||||
output.then([](double value) {
|
||||
// the value has been converted using static_cast
|
||||
});
|
||||
```
|
||||
|
||||
If `U` has a [converting constructor](https://en.cppreference.com/w/cpp/language/converting_constructor)
|
||||
from `T`, i.e., a non-explicit constructor with a single argument accepting `T`, it is used to
|
||||
convert the value:
|
||||
|
||||
```cpp
|
||||
QPromise<QByteArray> input = {...}
|
||||
|
||||
// output type: QPromise<QString>
|
||||
auto output = input.convert<QString>();
|
||||
|
||||
output.then([](const QString& value) {
|
||||
// the value has been converted using static_cast that effectively calls QString(QByteArray)
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
When using this method to convert to your own classes, make sure that the constructor meeting the
|
||||
converting constructor criteria actually performs conversion.
|
||||
:::
|
||||
|
||||
::: tip NOTE
|
||||
If `U` is `void`, the resolved value of `QPromise<T>` is dropped.
|
||||
:::
|
||||
|
||||
Calling this method for `QPromise<QVariant>` tries to convert the resolved `QVariant` to type `U`
|
||||
using the `QVariant` [conversion algorithm](https://doc.qt.io/qt-5/qvariant.html#using-canconvert-and-convert-consecutively).
|
||||
For example, this allows to convert a string contained in `QVariant` to number. If such a
|
||||
conversion fails, the promise is rejected with
|
||||
[`QPromiseConversionException`](../exceptions/conversion.md).
|
||||
|
||||
```cpp
|
||||
// resolves to QVariant(int, 42) or QVariant(string, "foo")
|
||||
QPromise<QVariant> input = {...};
|
||||
|
||||
auto output = input.convert<int>();
|
||||
|
||||
// output type: QPromise<int>
|
||||
output.then([](int value) {
|
||||
// input was QVariant(int, 42), value is 42
|
||||
})
|
||||
.fail(const QPromiseConversionException& e) {
|
||||
// input was QVariant(string, "foo")
|
||||
});
|
||||
```
|
||||
|
||||
Conversion of `T` to `QVariant` using this method effectively calls `QVariant::fromValue<T>()`.
|
||||
All custom types must be registered with
|
||||
[`Q_DECLARE_METATYPE`](https://doc.qt.io/qt-5/qmetatype.html#Q_DECLARE_METATYPE) for this
|
||||
conversion to work:
|
||||
|
||||
```cpp
|
||||
struct Foo {};
|
||||
Q_DECLARE_METATYPE(Foo);
|
||||
|
||||
QPromise<Foo> input = {...}
|
||||
|
||||
auto output = input.convert<QVariant>();
|
||||
|
||||
// output type: QPromise<QVariant>
|
||||
output.then([](const QVariant& value) {
|
||||
// value contains an instance of Foo
|
||||
});
|
||||
```
|
||||
|
||||
All other combinations of `T` and `U` are converted via `QVariant`. All non-Qt types should provide
|
||||
a [conversion function](https://doc.qt.io/qt-5/qmetatype.html#registerConverter), otherwise the
|
||||
promise is rejected with [`QPromiseConversionException`](../exceptions/conversion.md):
|
||||
|
||||
```cpp
|
||||
struct Foo {};
|
||||
Q_DECLARE_METATYPE(Foo);
|
||||
|
||||
QMetaType::registerConverter<Foo, QString>([](const Foo& foo) {
|
||||
return QString{...};
|
||||
});
|
||||
|
||||
QPromise<Foo> input = {...}
|
||||
|
||||
auto output = input.convert<QVariant>();
|
||||
|
||||
// output type: QPromise<QString>
|
||||
output.then([](const QString& value) {
|
||||
// value contains a result produced by the custom converter
|
||||
})
|
||||
.fail([](const QPromiseConversionException& e) {
|
||||
// QVariant was unable to convert Foo to QString
|
||||
})
|
||||
```
|
||||
|
||||
::: warning IMPORTANT
|
||||
Calling this method for `QPromise<void>` is not supported.
|
||||
:::
|
50
docs/qtpromise/qpromise/delay.md
Normal file
50
docs/qtpromise/qpromise/delay.md
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
title: .delay
|
||||
---
|
||||
|
||||
# QPromise::delay
|
||||
|
||||
*Since: 0.2.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::delay(int msec) -> QPromise<T>
|
||||
```
|
||||
|
||||
This method returns a promise that will be fulfilled with the same value as the `input` promise
|
||||
and after at least `msec` milliseconds. If the `input` promise is rejected, the `output` promise
|
||||
is immediately rejected with the same reason.
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.delay(2000).then([](int res) {
|
||||
// called 2 seconds after `input` is fulfilled
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Since: 0.6.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::delay(std::chrono::milliseconds msec) -> QPromise<T>
|
||||
```
|
||||
|
||||
This is a convenience overload accepting [durations from the C++ Standard Library](https://en.cppreference.com/w/cpp/chrono/duration).
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.delay(std::chrono::seconds{2}).then([](int res) {
|
||||
// called 2 seconds after `input` is fulfilled
|
||||
});
|
||||
```
|
||||
|
||||
C++14 alternative:
|
||||
|
||||
```cpp
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.delay(2s).then([](int res) {
|
||||
// called 2 seconds after `input` is fulfilled
|
||||
});
|
||||
```
|
60
docs/qtpromise/qpromise/each.md
Normal file
60
docs/qtpromise/qpromise/each.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: .each
|
||||
---
|
||||
|
||||
# QPromise::each
|
||||
|
||||
*Since: 0.4.0*
|
||||
|
||||
```cpp
|
||||
QPromise<Sequence<T>>::each(Functor functor) -> QPromise<Sequence<T>>
|
||||
|
||||
// With:
|
||||
// - Sequence: STL compatible container
|
||||
// - Functor: Function(T value, int index) -> any
|
||||
```
|
||||
|
||||
::: warning IMPORTANT
|
||||
This method only applies to promise with sequence value.
|
||||
:::
|
||||
|
||||
Calls the given `functor` on each element in the promise value (i.e. `Sequence<T>`), then resolves
|
||||
to the original sequence unmodified. If `functor` throws, `output` is rejected with the new
|
||||
exception.
|
||||
|
||||
```cpp
|
||||
QPromise<QList<QByteArray>> input = {...}
|
||||
|
||||
auto output = input.each([](const QByteArray& value, int index) {
|
||||
// process value ...
|
||||
});
|
||||
|
||||
// output type: QPromise<QList<QByteArray>>
|
||||
output.then([](const QList<QByteArray>& res) {
|
||||
// 'res' contains the original values
|
||||
});
|
||||
```
|
||||
|
||||
If `functor` returns a promise (or `QFuture`), the `output` promise is delayed until all the
|
||||
promises are resolved. If any of the promises fail, `output` immediately rejects with the error
|
||||
of the promise that rejected, whether or not the other promises are resolved.
|
||||
|
||||
```cpp
|
||||
QPromise<QList<QUrl>> input = {...}
|
||||
|
||||
auto output = input.each([](const QUrl& url, ...) {
|
||||
return QPromise<void>{[&](auto resolve, auto reject) {
|
||||
// process url asynchronously ...
|
||||
}};
|
||||
});
|
||||
|
||||
// `output` resolves as soon as all promises returned by
|
||||
// `functor` are fulfilled or at least one is rejected.
|
||||
|
||||
// output type: QPromise<QList<QUrl>>
|
||||
output.then([](const QList<QUrl>& res) {
|
||||
// 'res' contains the original values
|
||||
});
|
||||
```
|
||||
|
||||
See also: [`QtPromise::each`](../helpers/each.md)
|
28
docs/qtpromise/qpromise/fail.md
Normal file
28
docs/qtpromise/qpromise/fail.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: .fail
|
||||
---
|
||||
|
||||
# QPromise::fail
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::fail(Function onRejected) -> QPromise<T>
|
||||
```
|
||||
|
||||
Shorthand to [`promise.then(nullptr, onRejected)`](then.md) for handling errors in promise chains,
|
||||
similar to the native C++ [`catch` statement](http://en.cppreference.com/w/cpp/language/try_catch):
|
||||
|
||||
```cpp
|
||||
promise.fail([](const MyException& error) {
|
||||
// {...}
|
||||
}).fail([](const QException& error) {
|
||||
// {...}
|
||||
}).fail([](const std::exception& error) {
|
||||
// {...}
|
||||
}).fail([]() {
|
||||
// {...} catch-all
|
||||
});
|
||||
```
|
||||
|
||||
See also: [`QPromise::then`](then.md)
|
55
docs/qtpromise/qpromise/filter.md
Normal file
55
docs/qtpromise/qpromise/filter.md
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: .filter
|
||||
---
|
||||
|
||||
# QPromise::filter
|
||||
|
||||
*Since: 0.4.0*
|
||||
|
||||
```cpp
|
||||
QPromise<Sequence<T>>::filter(Filter filterer) -> QPromise<Sequence<T>>
|
||||
|
||||
// With:
|
||||
// - Sequence: STL compatible container (e.g. QVector, etc.)
|
||||
// - Filterer: Function(T value, int index) -> bool
|
||||
```
|
||||
|
||||
::: warning IMPORTANT
|
||||
This method only applies to promise with sequence value.
|
||||
:::
|
||||
|
||||
Iterates over all the promise values (i.e. `Sequence<T>`) and [filters the sequence](https://en.wikipedia.org/wiki/Filter_%28higher-order_function%29)
|
||||
to another using the given `filterer` function. If `filterer` returns `true`, a copy of the item
|
||||
is put in the `output` sequence, otherwise, the item will not appear in `output`. If `filterer`
|
||||
throws, `output` is rejected with the new exception.
|
||||
|
||||
If `filterer` returns a promise (or `QFuture`), the `output` promise is delayed until all the
|
||||
promises are resolved. If any of the promises fail, `output` immediately rejects with the error
|
||||
of the promise that rejected, whether or not the other promises are resolved.
|
||||
|
||||
```cpp
|
||||
QPromise<QList<QUrl>> input = {...}
|
||||
|
||||
auto output = input.filter([](const QUrl& url, ...) {
|
||||
return url.isValid(); // Keep only valid URLs
|
||||
}).filter([](const QUrl& url, ...) {
|
||||
return QPromise<bool>{[&](auto resolve, auto reject) {
|
||||
// resolve(true) if `url` is reachable, else resolve(false)
|
||||
}};
|
||||
});
|
||||
|
||||
// 'output' resolves as soon as all promises returned by
|
||||
// 'filterer' are fulfilled or at least one is rejected.
|
||||
|
||||
// 'output' type: QPromise<QList<QUrl>>
|
||||
output.then([](const QList<QUrl>& res) {
|
||||
// 'res' contains only reachable URLs
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
The order of the output sequence values is guarantee to be the same as the original sequence,
|
||||
regardless of completion order of the promises returned by `filterer`.
|
||||
:::
|
||||
|
||||
See also: [`QtPromise::filter`](../helpers/filter.md)
|
26
docs/qtpromise/qpromise/finally.md
Normal file
26
docs/qtpromise/qpromise/finally.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: .finally
|
||||
---
|
||||
|
||||
# QPromise::finally
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::finally(Function handler) -> QPromise<T>
|
||||
```
|
||||
|
||||
This `handler` is **always** called, without any argument and whatever the `input` promise state
|
||||
(fulfilled or rejected). The `output` promise has the same type as the `input` one but also the
|
||||
same value or error. The finally `handler` **can not modify the fulfilled value** (the returned
|
||||
value is ignored), however, if `handler` throws, `output` is rejected with the new exception.
|
||||
|
||||
```cpp
|
||||
auto output = input.finally([]() {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned
|
||||
promise is resolved and under the same conditions: the delayed value is ignored, the error
|
||||
transmitted to the `output` promise.
|
13
docs/qtpromise/qpromise/isfulfilled.md
Normal file
13
docs/qtpromise/qpromise/isfulfilled.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
title: .isFulfilled
|
||||
---
|
||||
|
||||
# QPromise::isFulfilled
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::isFulfilled() -> bool
|
||||
```
|
||||
|
||||
Returns `true` if the promise is fulfilled, otherwise returns `false`.
|
13
docs/qtpromise/qpromise/ispending.md
Normal file
13
docs/qtpromise/qpromise/ispending.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
title: .isPending
|
||||
---
|
||||
|
||||
# QPromise::isPending
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::isPending() -> bool
|
||||
```
|
||||
|
||||
Returns `true` if the promise is pending (not fulfilled or rejected), otherwise returns `false`.
|
13
docs/qtpromise/qpromise/isrejected.md
Normal file
13
docs/qtpromise/qpromise/isrejected.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
title: .isRejected
|
||||
---
|
||||
|
||||
# QPromise::isRejected
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::isRejected() -> bool
|
||||
```
|
||||
|
||||
Returns `true` if the promise is rejected, otherwise returns `false`.
|
67
docs/qtpromise/qpromise/map.md
Normal file
67
docs/qtpromise/qpromise/map.md
Normal file
@ -0,0 +1,67 @@
|
||||
---
|
||||
title: .map
|
||||
---
|
||||
|
||||
# QPromise::map
|
||||
|
||||
*Since: 0.4.0*
|
||||
|
||||
```cpp
|
||||
QPromise<Sequence<T>>::map(Mapper mapper) -> QPromise<QVector<R>>
|
||||
|
||||
// With:
|
||||
// - Sequence: STL compatible container (e.g. QVector, etc.)
|
||||
// - Mapper: Function(T value, int index) -> R | QPromise<R>
|
||||
```
|
||||
|
||||
::: warning IMPORTANT
|
||||
This method only applies to promise with sequence value.
|
||||
:::
|
||||
|
||||
Iterates over all the promise values (i.e. `Sequence<T>`) and [maps the sequence](https://en.wikipedia.org/wiki/Map_%28higher-order_function%29)
|
||||
to another using the given `mapper` function. The type returned by `mapper` determines the type of
|
||||
the `output` promise. If `mapper` throws, `output` is rejected with the new exception.
|
||||
|
||||
If `mapper` returns a promise (or `QFuture`), the `output` promise is delayed until all the promises
|
||||
are resolved. If any of the promises fails, `output` immediately rejects with the error of the
|
||||
promise that rejected, whether or not the other promises are resolved.
|
||||
|
||||
```cpp
|
||||
QPromise<QList<QUrl>> input = {...}
|
||||
|
||||
auto output = input.map([](const QUrl& url, int index) {
|
||||
return QPromise<QByteArray>{[&](auto resolve, auto reject) {
|
||||
// download content at 'url' and resolve
|
||||
// {...}
|
||||
}};
|
||||
}).map([](const QByteArray& value, ...) {
|
||||
// process the downloaded QByteArray
|
||||
// {...}
|
||||
return DownloadResult{value};
|
||||
});
|
||||
|
||||
// 'output' resolves as soon as all promises returned by
|
||||
// 'mapper' are fulfilled or at least one is rejected.
|
||||
|
||||
// 'output' type: QPromise<QVector<DownloadResult>>
|
||||
output.then([](const QVector<DownloadResult>& res) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
The order of the output sequence values is guarantee to be the same as the original sequence,
|
||||
regardless of completion order of the promises returned by `mapper`.
|
||||
:::
|
||||
|
||||
This function is provided for convenience and is similar to:
|
||||
|
||||
```cpp
|
||||
promise.then([](const Sequence<T>& values) {
|
||||
return QtPromise::map(values, [](const T& value, int index) {
|
||||
return // {...}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
See also: [`QtPromise::map`](../helpers/map.md)
|
61
docs/qtpromise/qpromise/reduce.md
Normal file
61
docs/qtpromise/qpromise/reduce.md
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
title: .reduce
|
||||
---
|
||||
|
||||
# QPromise::reduce
|
||||
|
||||
*Since: 0.5.0*
|
||||
|
||||
```cpp
|
||||
QPromise<Sequence<T>>::reduce(Reducer reducer) -> QPromise<T>
|
||||
QPromise<Sequence<T>>::reduce(Reducer reducer, R|QPromise<R> initialValue) -> QPromise<R>
|
||||
|
||||
// With:
|
||||
// - Sequence: STL compatible container (e.g. QVector, etc.)
|
||||
// - Reducer: Function(T|R accumulator, T item, int index) -> R|QPromise<R>
|
||||
```
|
||||
|
||||
::: warning IMPORTANT
|
||||
This method only applies to promise with sequence value.
|
||||
:::
|
||||
|
||||
Iterates over all the promise values (i.e. `Sequence<T>`) and [reduces the sequence](https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29)
|
||||
to a single value using the given `reducer` function and an optional `initialValue`. The type
|
||||
returned by `reducer` determines the type of the `output` promise.
|
||||
|
||||
See [`QtPromise::reduce`](../helpers/reduce.md) for details, this method is provided for convenience
|
||||
and is similar to:
|
||||
|
||||
```cpp
|
||||
promise.then([](const T& values) {
|
||||
return QtPromise::reduce(values, reducer, initialValue);
|
||||
})
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```cpp
|
||||
auto input = QtPromise::resolve(QList<QUrl>{
|
||||
"file:f0.txt", // contains "foo"
|
||||
"file:f1.txt", // contains "bar"
|
||||
"file:f2.txt" // contains "42"
|
||||
});
|
||||
|
||||
// Concatenate the content of the given files, read asynchronously
|
||||
auto output = input.reduce([](const QString& acc, const QString& cur, int idx) {
|
||||
return readAsync(cur).then([=](const QString& res) {
|
||||
return QString{"%1;%2:%3"}.arg(acc).arg(idx).arg(res);
|
||||
});
|
||||
}, QString{"index:text"});
|
||||
|
||||
// 'output' resolves as soon as all promises returned by
|
||||
// 'reducer' are fulfilled or at least one is rejected.
|
||||
|
||||
// 'output' type: QPromise<QString>
|
||||
output.then([](const QString& res) {
|
||||
// res == "index:text;0:foo;1:bar;2:42"
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
See also: [`QtPromise::reduce`](../helpers/reduce.md)
|
26
docs/qtpromise/qpromise/reject.md
Normal file
26
docs/qtpromise/qpromise/reject.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: ::reject (static)
|
||||
---
|
||||
|
||||
# QPromise::reject (static)
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
(static) QPromise<T>::reject(any reason) -> QPromise<T>
|
||||
```
|
||||
|
||||
Creates a `QPromise<T>` that is rejected with the given `reason` of *whatever type*:
|
||||
|
||||
```cpp
|
||||
QPromise<int> compute(const QString& type)
|
||||
{
|
||||
if (type == "foobar") {
|
||||
return QPromise<int>::reject(QString{"Unknown type: %1"}.arg(type));
|
||||
}
|
||||
|
||||
return QPromise<int>{[](const QPromiseResolve<int>& resolve) {
|
||||
// {...}
|
||||
}};
|
||||
}
|
||||
```
|
28
docs/qtpromise/qpromise/resolve.md
Normal file
28
docs/qtpromise/qpromise/resolve.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: ::resolve (static)
|
||||
---
|
||||
|
||||
# QPromise::resolve (static)
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
(static) QPromise<T>::resolve(T value) -> QPromise<T>
|
||||
```
|
||||
|
||||
Creates a `QPromise<T>` that is fulfilled with the given `value` of type `T`:
|
||||
|
||||
```cpp
|
||||
QPromise<int> compute(const QString& type)
|
||||
{
|
||||
if (type == "magic") {
|
||||
return QPromise<int>::resolve(42);
|
||||
}
|
||||
|
||||
return QPromise<int>{[](const QPromiseResolve<int>& resolve) {
|
||||
// {...}
|
||||
}};
|
||||
}
|
||||
```
|
||||
|
||||
See also: [`QtPromise::resolve`](../helpers/resolve.md)
|
29
docs/qtpromise/qpromise/tap.md
Normal file
29
docs/qtpromise/qpromise/tap.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
title: .tap
|
||||
---
|
||||
|
||||
# QPromise::tap
|
||||
|
||||
*Since: 0.2.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::tap(Function handler) -> QPromise<T>
|
||||
```
|
||||
|
||||
This `handler` allows to observe the value of the `input` promise, without changing the propagated
|
||||
value. The `output` promise will be resolved with the same value as the `input` promise (the `handler`
|
||||
returned value will be ignored). However, if `handler` throws, `output` is rejected with the new
|
||||
exception. Unlike [`finally`](finally.md), this handler is **not** called for rejections.
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.tap([](int res) {
|
||||
log(res);
|
||||
}).then([](int res) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned
|
||||
promise is resolved and under the same conditions: the delayed value is ignored, the error
|
||||
transmitted to the `output` promise.
|
32
docs/qtpromise/qpromise/tapfail.md
Normal file
32
docs/qtpromise/qpromise/tapfail.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: .tapFail
|
||||
---
|
||||
|
||||
# QPromise::tapFail
|
||||
|
||||
*Since: 0.4.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::tapFail(Function handler) -> QPromise<T>
|
||||
```
|
||||
|
||||
This `handler` allows to observe errors of the `input` promise without handling them - similar to
|
||||
[`finally`](finally.md) but **only** called on rejections. The `output` promise has the same type
|
||||
as the `input` one but also the same value or error. However, if `handler` throws, `output` is
|
||||
rejected with the new exception.
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.tapFail([](const Error& error) {
|
||||
log(error);
|
||||
}).then([](int res) {
|
||||
return process(res);
|
||||
}).fail([](const Error& error) {
|
||||
handle(error);
|
||||
return -1;
|
||||
});
|
||||
```
|
||||
|
||||
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned
|
||||
promise is resolved and under the same conditions: the delayed value is ignored, the error
|
||||
transmitted to the `output` promise.
|
99
docs/qtpromise/qpromise/then.md
Normal file
99
docs/qtpromise/qpromise/then.md
Normal file
@ -0,0 +1,99 @@
|
||||
---
|
||||
title: .then
|
||||
---
|
||||
|
||||
# QPromise::then
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::then(Function onFulfilled, Function onRejected) -> QPromise<R>
|
||||
QPromise<T>::then(Function onFulfilled) -> QPromise<R>
|
||||
```
|
||||
|
||||
See [Promises/A+ `.then`](https://promisesaplus.com/#the-then-method) for details.
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = ...
|
||||
auto output = input.then([](int res) {
|
||||
// called with the 'input' result if fulfilled
|
||||
}, [](const ReasonType& reason) {
|
||||
// called with the 'input' reason if rejected
|
||||
// see QPromise<T>::fail for details
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
`onRejected` handler is optional, in which case `output` will be rejected with the same reason as
|
||||
`input`. Also note that it's recommended to use the [`fail`](fail.md) shorthand to handle errors.
|
||||
:::
|
||||
|
||||
The type `<R>` of the `output` promise depends on the return type of the `onFulfilled` handler:
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.then([](int res) {
|
||||
return QString::number(res); // -> QPromise<QString>
|
||||
});
|
||||
|
||||
// output type: QPromise<QString>
|
||||
output.then([](const QString& res) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
Only `onFulfilled` can change the promise type, `onRejected` **must** return the same type as
|
||||
`onFulfilled`. That also means if `onFulfilled` is `nullptr`, `onRejected` must return the same
|
||||
type as the `input` promise.
|
||||
:::
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = ...
|
||||
auto output = input.then([](int res) {
|
||||
return res + 4;
|
||||
}, [](const ReasonType& reason) {
|
||||
return -1;
|
||||
});
|
||||
```
|
||||
|
||||
If `onFulfilled` doesn't return any value, the `output` type is `QPromise<void>`:
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = ...
|
||||
auto output = input.then([](int res) {
|
||||
// {...}
|
||||
});
|
||||
|
||||
// output type: QPromise<void>
|
||||
output.then([]() {
|
||||
// `QPromise<void>` `onFulfilled` handler has no argument
|
||||
});
|
||||
```
|
||||
|
||||
You can also decide to skip the promise result by omitting the handler argument:
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.then([]( /* skip int result */ ) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
The `output` promise can be *rejected* by throwing an exception in either `onFulfilled` or `onRejected`:
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.then([](int res) {
|
||||
if (res == -1) {
|
||||
throw ReasonType();
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
});
|
||||
|
||||
// output.isRejected() is true
|
||||
```
|
||||
|
||||
If a handler returns a promise (or QFuture), the `output` promise is delayed and will be resolved
|
||||
by the returned promise.
|
63
docs/qtpromise/qpromise/timeout.md
Normal file
63
docs/qtpromise/qpromise/timeout.md
Normal file
@ -0,0 +1,63 @@
|
||||
---
|
||||
title: .timeout
|
||||
---
|
||||
|
||||
# QPromise::timeout
|
||||
|
||||
*Since: 0.2.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::timeout(int msec, any error = QPromiseTimeoutException) -> QPromise<T>
|
||||
```
|
||||
|
||||
This method returns a promise that will be resolved with the `input` promise's fulfillment value
|
||||
or rejection reason. However, if the `input` promise is not fulfilled or rejected within `msec`
|
||||
milliseconds, the `output` promise is rejected with `error` as the reason ([`QPromiseTimeoutException`](../exceptions/timeout.md)
|
||||
by default).
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.timeout(2000)
|
||||
.then([](int res) {
|
||||
// operation succeeded within 2 seconds
|
||||
})
|
||||
.fail([](const QPromiseTimeoutException& error) {
|
||||
// operation timed out!
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Since: 0.6.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::timeout(std::chrono::milliseconds msec, any error = QPromiseTimeoutException) -> QPromise<T>
|
||||
```
|
||||
|
||||
This is a convenience overload accepting [durations from the C++ Standard Library](https://en.cppreference.com/w/cpp/chrono/duration).
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.timeout(std::chrono::seconds{2})
|
||||
.then([](int res) {
|
||||
// operation succeeded within 2 seconds
|
||||
})
|
||||
.fail([](const QPromiseTimeoutException& error) {
|
||||
// operation timed out!
|
||||
});
|
||||
```
|
||||
|
||||
C++14 alternative:
|
||||
|
||||
```cpp
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
QPromise<int> input = {...}
|
||||
auto output = input.timeout(2s)
|
||||
.then([](int res) {
|
||||
// operation succeeded within 2 seconds
|
||||
})
|
||||
.fail([](const QPromiseTimeoutException& error) {
|
||||
// operation timed out!
|
||||
});
|
||||
```
|
30
docs/qtpromise/qpromise/wait.md
Normal file
30
docs/qtpromise/qpromise/wait.md
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
title: .wait
|
||||
---
|
||||
|
||||
# QPromise::wait
|
||||
|
||||
*Since: 0.1.0*
|
||||
|
||||
```cpp
|
||||
QPromise<T>::wait() -> QPromise<T>
|
||||
```
|
||||
|
||||
This method holds the execution of the remaining code until the `input` promise is resolved (either
|
||||
fulfilled or rejected), **without** blocking the event loop of the current thread:
|
||||
|
||||
```cpp
|
||||
int result = -1;
|
||||
|
||||
QPromise<int> input = QtPromise::resolve(QtConcurrent::run([]() {
|
||||
return 42;
|
||||
})).tap([&](int res) {
|
||||
result = res;
|
||||
});
|
||||
|
||||
// input.isPending() is true && result is -1
|
||||
|
||||
input.wait();
|
||||
|
||||
// input.isPending() is false && result is 42
|
||||
```
|
88
docs/qtpromise/qtconcurrent.md
Normal file
88
docs/qtpromise/qtconcurrent.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Qt Concurrent
|
||||
|
||||
QtPromise integrates with [QtConcurrent](https://doc.qt.io/qt-5/qtconcurrent-index.html) to simplify
|
||||
chaining QFuture with QPromise.
|
||||
|
||||
## Convert
|
||||
|
||||
Converting `QFuture<T>` to `QPromise<T>` is done using the [`QtPromise::resolve`](helpers/resolve.md)
|
||||
helper:
|
||||
|
||||
```cpp
|
||||
QFuture<int> future = QtConcurrent::run([]() {
|
||||
// {...}
|
||||
return 42;
|
||||
});
|
||||
|
||||
QPromise<int> promise = QtPromise::resolve(future);
|
||||
```
|
||||
|
||||
or simply:
|
||||
|
||||
```cpp
|
||||
auto promise = QtPromise::resolve(QtConcurrent::run([]() {
|
||||
// {...}
|
||||
}));
|
||||
```
|
||||
|
||||
## Chain
|
||||
|
||||
Returning a `QFuture<T>` in [`then`](qpromise/then.md) or [`fail`](qpromise/fail.md) automatically
|
||||
translate to `QPromise<T>`:
|
||||
|
||||
```cpp
|
||||
QPromise<int> input = ...
|
||||
auto output = input.then([](int res) {
|
||||
return QtConcurrent::run([]() {
|
||||
// {...}
|
||||
return QString{"42"};
|
||||
});
|
||||
});
|
||||
|
||||
// output type: QPromise<QString>
|
||||
output.then([](const QString& res) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
The `output` promise is resolved when the `QFuture` is [finished](https://doc.qt.io/qt-5/qfuture.html#isFinished).
|
||||
|
||||
## Error
|
||||
|
||||
Exceptions thrown from a QtConcurrent thread reject the associated promise with the exception as the
|
||||
reason. For this to work, the exception should be a [`QException`](https://doc.qt.io/qt-5/qexception.html)
|
||||
or its subclass. Correct subclassing of [`QException`](https://doc.qt.io/qt-5/qexception.html)
|
||||
includes overriding its methods [`clone()`](https://doc.qt.io/qt-5/qexception.html#clone) and
|
||||
[`raise()`](https://doc.qt.io/qt-5/qexception.html#raise). Without these overrides the promise will
|
||||
be rejected with [`QException`](https://doc.qt.io/qt-5/qexception.html).
|
||||
|
||||
Note that if you throw an exception that is not a subclass of `QException`, the promise will
|
||||
be rejected with [`QUnhandledException`](https://doc.qt.io/qt-5/qunhandledexception.html#details)
|
||||
(this restriction only applies to exceptions thrown from a QtConcurrent thread,
|
||||
[read more](https://doc.qt.io/qt-5/qexception.html#details)).
|
||||
|
||||
```cpp
|
||||
class CustomException : public QException
|
||||
{
|
||||
public:
|
||||
void raise() const override { throw *this; }
|
||||
CustomException* clone() const override { return new CustomException{*this}; }
|
||||
};
|
||||
|
||||
// {...}
|
||||
|
||||
QPromise<int> promise = ...
|
||||
promise.then([](int res) {
|
||||
return QtConcurrent::run([]() {
|
||||
// {...}
|
||||
|
||||
if (!success) {
|
||||
throw CustomException{};
|
||||
}
|
||||
|
||||
return QString{"42"};
|
||||
});
|
||||
}).fail([](const CustomException& error) {
|
||||
// {...}
|
||||
});
|
||||
```
|
96
docs/qtpromise/qtsignals.md
Normal file
96
docs/qtpromise/qtsignals.md
Normal file
@ -0,0 +1,96 @@
|
||||
# Qt Signals
|
||||
|
||||
QtPromise supports creating promises that are resolved or rejected by regular [Qt signals](https://doc.qt.io/qt-5/signalsandslots.html).
|
||||
|
||||
::: warning IMPORTANT
|
||||
A promise connected to a signal will be resolved (fulfilled or rejected) **only one time**, no
|
||||
matter if the signals are emitted multiple times. Internally, the promise is disconnected from
|
||||
all signals as soon as one signal is emitted.
|
||||
:::
|
||||
|
||||
## Resolve Signal
|
||||
|
||||
The [`QtPromise::connect()`](helpers/connect.md) helper allows to create a promise resolved from
|
||||
a single signal:
|
||||
|
||||
```cpp
|
||||
// [signal] Object::finished(const QByteArray&)
|
||||
auto output = QtPromise::connect(obj, &Object::finished);
|
||||
|
||||
// output type: QPromise<QByteArray>
|
||||
output.then([](const QByteArray& data) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
If the signal doesn't provide any argument, a `QPromise<void>` is returned:
|
||||
|
||||
```cpp
|
||||
// [signal] Object::done()
|
||||
auto output = QtPromise::connect(obj, &Object::done);
|
||||
|
||||
// output type: QPromise<void>
|
||||
output.then([]() {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
::: tip NOTE
|
||||
QtPromise currently only supports single argument signals, which means that only the first argument
|
||||
is used to fulfill or reject the connected promise, other arguments being ignored.
|
||||
:::
|
||||
|
||||
## Reject Signal
|
||||
|
||||
The [`QtPromise::connect()`](helpers/connect.md) helper also allows to reject the promise from
|
||||
another signal:
|
||||
|
||||
```cpp
|
||||
// [signal] Object::finished(const QByteArray& data)
|
||||
// [signal] Object::error(ObjectError error)
|
||||
auto output = QtPromise::connect(obj, &Object::finished, &Object::error);
|
||||
|
||||
// output type: QPromise<QByteArray>
|
||||
output.then([](const QByteArray& data) {
|
||||
// {...}
|
||||
}).fail([](const ObjectError& error) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
If the rejection signal doesn't provide any argument, the promise will be rejected with
|
||||
[`QPromiseUndefinedException`](exceptions/undefined.md), for example:
|
||||
|
||||
```cpp
|
||||
// [signal] Object::finished()
|
||||
// [signal] Object::error()
|
||||
auto output = QtPromise::connect(obj, &Object::finished, &Object::error);
|
||||
|
||||
// output type: QPromise<QByteArray>
|
||||
output.then([]() {
|
||||
// {...}
|
||||
}).fail([](const QPromiseUndefinedException& error) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
A third variant allows to connect the resolve and reject signals from different objects:
|
||||
|
||||
```cpp
|
||||
// [signal] ObjectA::finished(const QByteArray& data)
|
||||
// [signal] ObjectB::error(ObjectBError error)
|
||||
auto output = QtPromise::connect(objA, &ObjectA::finished, objB, &ObjectB::error);
|
||||
|
||||
// output type: QPromise<QByteArray>
|
||||
output.then([](const QByteArray& data) {
|
||||
// {...}
|
||||
}).fail([](const ObjectBError& error) {
|
||||
// {...}
|
||||
});
|
||||
```
|
||||
|
||||
Additionally to the rejection signal, promises created using [`QtPromise::connect()`](helpers/connect.md)
|
||||
are automatically rejected with [`QPromiseContextException`](exceptions/context.md) if the sender is
|
||||
destroyed before fulfilling the promise.
|
||||
|
||||
See [`QtPromise::connect()`](helpers/connect.md) for more details.
|
12
docs/qtpromise/thread-safety.md
Normal file
12
docs/qtpromise/thread-safety.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Thread-Safety
|
||||
|
||||
QPromise is thread-safe and can be copied and accessed across different threads. QPromise relies on
|
||||
[explicitly data sharing](https://doc.qt.io/qt-5/qexplicitlyshareddatapointer.html#details) and thus
|
||||
`auto p2 = p1` represents the same promise: when `p1` resolves, handlers registered on `p1` and `p2`
|
||||
are called, the fulfilled value being shared between both instances.
|
||||
|
||||
::: warning IMPORTANT
|
||||
While it's safe to access the resolved value from different threads using [`then`](qpromise/then.md),
|
||||
QPromise provides no guarantee about the object being pointed to. Thread-safety and reentrancy rules
|
||||
for that object still apply.
|
||||
:::
|
@ -1,8 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_MODULE_H
|
||||
#define QTPROMISE_MODULE_H
|
||||
|
||||
#include "../src/qtpromise/qpromise.h"
|
||||
#include "../src/qtpromise/qpromiseconnections.h"
|
||||
#include "../src/qtpromise/qpromisefuture.h"
|
||||
#include "../src/qtpromise/qpromisehelpers.h"
|
||||
|
||||
#endif // ifndef QTPROMISE_MODULE_H
|
||||
#endif // QTPROMISE_MODULE_H
|
||||
|
34975
package-lock.json
generated
Normal file
34975
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"description": "qtpromise",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docs:build": "vuepress build docs",
|
||||
"docs:dev": "vuepress dev docs",
|
||||
"docs:lint": "remark --quiet --frail ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vuepress/plugin-google-analytics": "^1.9.9",
|
||||
"remark-cli": "^11.0.0",
|
||||
"remark-frontmatter": "^4.0.1",
|
||||
"remark-preset-lint-markdown-style-guide": "^5.1.2",
|
||||
"remark-preset-lint-recommended": "^6.1.2",
|
||||
"remark-validate-links": "^12.1.0",
|
||||
"vuepress": "^1.9.9"
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
include(../../qtpromise.pri)
|
4
qpm.json
4
qpm.json
@ -10,9 +10,9 @@
|
||||
"url": "https://github.com/simonbrunel/qtpromise.git"
|
||||
},
|
||||
"version": {
|
||||
"label": "0.2.0"
|
||||
"label": "0.7.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"pri_filename": "qtpromise.pri",
|
||||
"webpage": "https://github.com/simonbrunel/qtpromise"
|
||||
"webpage": "https://qtpromise.netlify.com"
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = \
|
||||
tests
|
||||
|
||||
_qt_creator_ {
|
||||
SUBDIRS += src
|
||||
}
|
||||
|
||||
OTHER_FILES = \
|
||||
package/features/*.prf \
|
||||
include/* \
|
||||
qtpromise.pri
|
@ -1,95 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_QPROMISE_H
|
||||
#define QTPROMISE_QPROMISE_H
|
||||
|
||||
// QtPromise
|
||||
#include "qpromiseexceptions.h"
|
||||
#include "qpromise_p.h"
|
||||
#include "qpromiseglobal.h"
|
||||
#include "qpromiseresolver.h"
|
||||
|
||||
// Qt
|
||||
#include <QExplicitlySharedDataPointer>
|
||||
#include <QtCore/QExplicitlySharedDataPointer>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
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>
|
||||
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>
|
||||
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 TRejected>
|
||||
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;
|
||||
|
||||
template <typename THandler>
|
||||
template<typename THandler>
|
||||
inline QPromise<T> finally(THandler handler) const;
|
||||
|
||||
template <typename THandler>
|
||||
template<typename THandler>
|
||||
inline QPromise<T> tap(THandler handler) const;
|
||||
|
||||
template <typename E = QPromiseTimeoutException>
|
||||
inline QPromise<T> timeout(int msec, E&& error = E()) const;
|
||||
template<typename THandler>
|
||||
inline QPromise<T> tapFail(THandler handler) const;
|
||||
|
||||
template<typename E = QPromiseTimeoutException>
|
||||
inline QPromise<T> timeout(int msec, E&& error = E{}) const;
|
||||
|
||||
template<typename E = QPromiseTimeoutException>
|
||||
inline QPromise<T> timeout(std::chrono::milliseconds msec, E&& error = E{}) const;
|
||||
|
||||
inline QPromise<T> delay(int msec) const;
|
||||
inline QPromise<T> delay(std::chrono::milliseconds msec) const;
|
||||
|
||||
inline QPromise<T> wait() const;
|
||||
|
||||
void swap(QPromiseBase<T>& other) { qSwap(m_d, other.m_d); }
|
||||
|
||||
public: // STATIC
|
||||
template <typename E>
|
||||
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>;
|
||||
friend struct QtPromisePrivate::PromiseFulfill<QPromise<T>>;
|
||||
friend class QtPromisePrivate::PromiseResolver<T>;
|
||||
friend struct QtPromisePrivate::PromiseInspect;
|
||||
|
||||
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T> > m_d;
|
||||
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T>> m_d;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class QPromise: public QPromiseBase<T>
|
||||
template<typename T>
|
||||
class QPromise : public QPromiseBase<T>
|
||||
{
|
||||
public:
|
||||
template <typename F>
|
||||
QPromise(F&& resolver): QPromiseBase<T>(std::forward<F>(resolver)) { }
|
||||
template<typename F>
|
||||
QPromise(F&& resolver) : QPromiseBase<T>(std::forward<F>(resolver))
|
||||
{ }
|
||||
|
||||
template<typename U>
|
||||
inline QPromise<U> convert() const;
|
||||
|
||||
template<typename Functor>
|
||||
inline QPromise<T> each(Functor fn);
|
||||
|
||||
template<typename Functor>
|
||||
inline QPromise<T> filter(Functor fn);
|
||||
|
||||
template<typename Functor>
|
||||
inline typename QtPromisePrivate::PromiseMapper<T, Functor>::PromiseType map(Functor fn);
|
||||
|
||||
template<typename Functor, typename Input>
|
||||
inline typename QtPromisePrivate::PromiseDeduce<Input>::Type reduce(Functor fn, Input initial);
|
||||
|
||||
template<typename Functor, typename U = T>
|
||||
inline typename QtPromisePrivate::PromiseDeduce<typename U::value_type>::Type
|
||||
reduce(Functor fn);
|
||||
|
||||
public: // STATIC
|
||||
inline static QPromise<QVector<T> > all(const QVector<QPromise<T> >& promises);
|
||||
// DEPRECATED (remove at version 1)
|
||||
template<template<typename, typename...> class Sequence = QVector, typename... Args>
|
||||
Q_DECL_DEPRECATED_X("Use QtPromise::all instead")
|
||||
static inline QPromise<QVector<T>> all(const Sequence<QPromise<T>, Args...>& promises);
|
||||
|
||||
inline static QPromise<T> resolve(const T& value);
|
||||
inline static QPromise<T> resolve(T&& value);
|
||||
|
||||
private:
|
||||
friend class QPromiseBase<T>;
|
||||
};
|
||||
|
||||
template <>
|
||||
class QPromise<void>: public QPromiseBase<void>
|
||||
template<>
|
||||
class QPromise<void> : public QPromiseBase<void>
|
||||
{
|
||||
public:
|
||||
template <typename F>
|
||||
QPromise(F&& resolver): QPromiseBase<void>(std::forward<F>(resolver)) { }
|
||||
template<typename F>
|
||||
QPromise(F&& resolver) : QPromiseBase<void>(std::forward<F>(resolver))
|
||||
{ }
|
||||
|
||||
public: // STATIC
|
||||
inline static QPromise<void> all(const QVector<QPromise<void> >& promises);
|
||||
// DEPRECATED (remove at version 1)
|
||||
template<template<typename, typename...> class Sequence = QVector, typename... Args>
|
||||
Q_DECL_DEPRECATED_X("Use QtPromise::all instead")
|
||||
static inline QPromise<void> all(const Sequence<QPromise<void>, Args...>& promises);
|
||||
|
||||
inline static QPromise<void> resolve();
|
||||
|
||||
private:
|
||||
@ -100,4 +167,4 @@ private:
|
||||
|
||||
#include "qpromise.inl"
|
||||
|
||||
#endif // ifndef QTPROMISE_QPROMISE_H
|
||||
#endif // QTPROMISE_QPROMISE_H
|
||||
|
@ -1,116 +1,62 @@
|
||||
// Qt
|
||||
#include <QCoreApplication>
|
||||
#include <QSharedPointer>
|
||||
#include <QTimer>
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "qpromise.h"
|
||||
#include "qpromisehelpers.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
template <class T>
|
||||
class QPromiseResolve
|
||||
template<typename T>
|
||||
template<typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type>
|
||||
inline QPromiseBase<T>::QPromiseBase(F callback) : m_d{new QtPromisePrivate::PromiseData<T>{}}
|
||||
{
|
||||
public:
|
||||
QPromiseResolve(QPromise<T> p)
|
||||
: m_promise(new QPromise<T>(std::move(p)))
|
||||
{ }
|
||||
|
||||
template <typename V>
|
||||
void operator()(V&& value) const
|
||||
{
|
||||
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 <>
|
||||
class QPromiseResolve<void>
|
||||
{
|
||||
public:
|
||||
QPromiseResolve(QPromise<void> p)
|
||||
: m_promise(new QPromise<void>(std::move(p)))
|
||||
{ }
|
||||
|
||||
void operator()() const
|
||||
{
|
||||
Q_ASSERT(!m_promise.isNull());
|
||||
if (m_promise->isPending()) {
|
||||
m_promise->m_d->resolve();
|
||||
m_promise->m_d->dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QSharedPointer<QPromise<void> > m_promise;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class QPromiseReject
|
||||
{
|
||||
public:
|
||||
QPromiseReject(QPromise<T> p)
|
||||
: m_promise(new QPromise<T>(std::move(p)))
|
||||
{ }
|
||||
|
||||
template <typename E>
|
||||
void operator()(E&& error) const
|
||||
{
|
||||
Q_ASSERT(!m_promise.isNull());
|
||||
if (m_promise->isPending()) {
|
||||
m_promise->m_d->reject(std::forward<E>(error));
|
||||
m_promise->m_d->dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QSharedPointer<QPromise<T> > m_promise;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type>
|
||||
inline QPromiseBase<T>::QPromiseBase(F resolver)
|
||||
: m_d(new QtPromisePrivate::PromiseData<T>())
|
||||
{
|
||||
QPromiseResolve<T> resolve(*this);
|
||||
QPromiseReject<T> reject(*this);
|
||||
QtPromisePrivate::PromiseResolver<T> resolver{*this};
|
||||
|
||||
try {
|
||||
resolver(resolve);
|
||||
callback(QPromiseResolve<T>(resolver));
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
resolver.reject(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type>
|
||||
inline QPromiseBase<T>::QPromiseBase(F resolver)
|
||||
: m_d(new QtPromisePrivate::PromiseData<T>())
|
||||
template<typename T>
|
||||
template<typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type>
|
||||
inline QPromiseBase<T>::QPromiseBase(F callback) : m_d{new QtPromisePrivate::PromiseData<T>{}}
|
||||
{
|
||||
QPromiseResolve<T> resolve(*this);
|
||||
QPromiseReject<T> reject(*this);
|
||||
// To prevent infinite recursion at runtime when resolving the QPromise template
|
||||
// constructor, we don't explicitly check for ArgsOf<F>::count == 2 so that this
|
||||
// method is called for ALL callbacks other than the ones with a single typed
|
||||
// argument. This includes valid callbacks such as with two args, variadic or
|
||||
// auto args (c++14) but also invalid callbacks which are not functions or with
|
||||
// 0 or more than 2 arguments, in which case this method MUST fail to compile.
|
||||
|
||||
QtPromisePrivate::PromiseResolver<T> resolver{*this};
|
||||
|
||||
try {
|
||||
resolver(resolve, reject);
|
||||
callback(QPromiseResolve<T>(resolver), QPromiseReject<T>(resolver));
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
resolver.reject(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename TFulfilled, typename TRejected>
|
||||
template<typename T>
|
||||
template<typename TFulfilled, typename TRejected>
|
||||
inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
|
||||
QPromiseBase<T>::then(const TFulfilled& fulfilled, const TRejected& rejected) const
|
||||
{
|
||||
using namespace QtPromisePrivate;
|
||||
using PromiseType = typename PromiseHandler<T, TFulfilled>::Promise;
|
||||
|
||||
PromiseType next([&](
|
||||
const QPromiseResolve<typename PromiseType::Type>& resolve,
|
||||
const QPromiseReject<typename PromiseType::Type>& reject) {
|
||||
PromiseType next([&](const QPromiseResolve<typename PromiseType::Type>& resolve,
|
||||
const QPromiseReject<typename PromiseType::Type>& reject) {
|
||||
m_d->addHandler(PromiseHandler<T, TFulfilled>::create(fulfilled, resolve, reject));
|
||||
m_d->addCatcher(PromiseCatcher<T, TRejected>::create(rejected, resolve, reject));
|
||||
});
|
||||
@ -122,16 +68,24 @@ QPromiseBase<T>::then(const TFulfilled& fulfilled, const TRejected& rejected) co
|
||||
return next;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename TRejected>
|
||||
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
|
||||
QPromiseBase<T>::fail(TRejected&& rejected) const
|
||||
{
|
||||
return then(nullptr, std::forward<TRejected>(rejected));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename THandler>
|
||||
template<typename T>
|
||||
template<typename THandler>
|
||||
inline QPromise<T> QPromiseBase<T>::finally(THandler handler) const
|
||||
{
|
||||
QPromise<T> p = *this;
|
||||
@ -140,8 +94,8 @@ inline QPromise<T> QPromiseBase<T>::finally(THandler handler) const
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename THandler>
|
||||
template<typename T>
|
||||
template<typename THandler>
|
||||
inline QPromise<T> QPromiseBase<T>::tap(THandler handler) const
|
||||
{
|
||||
QPromise<T> p = *this;
|
||||
@ -150,15 +104,22 @@ inline QPromise<T> QPromiseBase<T>::tap(THandler handler) const
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename E>
|
||||
template<typename T>
|
||||
template<typename THandler>
|
||||
inline QPromise<T> QPromiseBase<T>::tapFail(THandler handler) const
|
||||
{
|
||||
QPromise<T> p = *this;
|
||||
return p.then([]() {}, handler).then([=]() {
|
||||
return p;
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename E>
|
||||
inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const
|
||||
{
|
||||
QPromise<T> p = *this;
|
||||
return QPromise<T>([&](
|
||||
const QPromiseResolve<T>& resolve,
|
||||
const QPromiseReject<T>& reject) {
|
||||
|
||||
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,
|
||||
@ -166,21 +127,34 @@ inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const
|
||||
reject(std::move(error));
|
||||
});
|
||||
|
||||
QtPromisePrivate::PromiseFulfill<QPromise<T> >::call(p, resolve, reject);
|
||||
});
|
||||
QtPromisePrivate::PromiseFulfill<QPromise<T>>::call(p, resolve, reject);
|
||||
}};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
template<typename E>
|
||||
inline QPromise<T> QPromiseBase<T>::timeout(std::chrono::milliseconds msec, E&& error) const
|
||||
{
|
||||
return timeout(static_cast<int>(msec.count()), std::forward<E>(error));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline QPromise<T> QPromiseBase<T>::delay(int msec) const
|
||||
{
|
||||
return tap([=]() {
|
||||
return QPromise<void>([&](const QPromiseResolve<void>& resolve) {
|
||||
return QPromise<void>{[&](const QPromiseResolve<void>& resolve) {
|
||||
QTimer::singleShot(msec, resolve);
|
||||
});
|
||||
}};
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
inline QPromise<T> QPromiseBase<T>::delay(std::chrono::milliseconds msec) const
|
||||
{
|
||||
return delay(static_cast<int>(msec.count()));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline QPromise<T> QPromiseBase<T>::wait() const
|
||||
{
|
||||
// @TODO wait timeout + global timeout
|
||||
@ -192,87 +166,114 @@ inline QPromise<T> QPromiseBase<T>::wait() const
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename E>
|
||||
template<typename T>
|
||||
template<typename E>
|
||||
inline QPromise<T> QPromiseBase<T>::reject(E&& error)
|
||||
{
|
||||
return QPromise<T>([&](const QPromiseResolve<T>&, const QPromiseReject<T>& reject) {
|
||||
return QPromise<T>{[&](const QPromiseResolve<T>&, const QPromiseReject<T>& reject) {
|
||||
reject(std::forward<E>(error));
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises)
|
||||
template<typename T>
|
||||
template<typename Functor>
|
||||
inline QPromise<T> QPromise<T>::each(Functor fn)
|
||||
{
|
||||
const int count = promises.size();
|
||||
if (count == 0) {
|
||||
return QPromise<QVector<T> >::resolve({});
|
||||
}
|
||||
return this->tap([=](const T& values) {
|
||||
int i = 0;
|
||||
|
||||
return QPromise<QVector<T> >([=](
|
||||
const QPromiseResolve<QVector<T> >& resolve,
|
||||
const QPromiseReject<QVector<T> >& reject) {
|
||||
std::vector<QPromise<void>> promises;
|
||||
for (const auto& v : values) {
|
||||
promises.push_back(QtPromise::attempt(fn, v, i).then([]() {
|
||||
// Cast to void in case fn returns a non promise value.
|
||||
// TODO remove when implicit cast is implemented.
|
||||
}));
|
||||
|
||||
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 {
|
||||
(*results)[i] = res;
|
||||
if (--(*remaining) == 0) {
|
||||
resolve(*results);
|
||||
}
|
||||
}, [=]() mutable {
|
||||
if (*remaining != -1) {
|
||||
*remaining = -1;
|
||||
reject(std::current_exception());
|
||||
}
|
||||
});
|
||||
i++;
|
||||
}
|
||||
|
||||
return QtPromise::all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
template<typename Functor>
|
||||
inline QPromise<T> QPromise<T>::filter(Functor fn)
|
||||
{
|
||||
return this->then([=](const T& values) {
|
||||
return QtPromise::filter(values, fn);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename Functor>
|
||||
inline typename QtPromisePrivate::PromiseMapper<T, Functor>::PromiseType
|
||||
QPromise<T>::map(Functor fn)
|
||||
{
|
||||
return this->then([=](const T& values) {
|
||||
return QtPromise::map(values, fn);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename Functor, typename Input>
|
||||
inline typename QtPromisePrivate::PromiseDeduce<Input>::Type
|
||||
QPromise<T>::reduce(Functor fn, Input initial)
|
||||
{
|
||||
return this->then([=](const T& values) {
|
||||
return QtPromise::reduce(values, fn, initial);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename Functor, typename U>
|
||||
inline typename QtPromisePrivate::PromiseDeduce<typename U::value_type>::Type
|
||||
QPromise<T>::reduce(Functor fn)
|
||||
{
|
||||
return this->then([=](const T& values) {
|
||||
return QtPromise::reduce(values, fn);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<template<typename, typename...> class Sequence, typename... Args>
|
||||
inline QPromise<QVector<T>> QPromise<T>::all(const Sequence<QPromise<T>, Args...>& promises)
|
||||
{
|
||||
return QtPromise::all(promises);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
inline QPromise<U> QPromise<T>::convert() const
|
||||
{
|
||||
return QPromiseBase<T>::then(QtPromisePrivate::PromiseConverter<T, U>::create());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return QPromise<T>([&](const QPromiseResolve<T>& resolve) {
|
||||
resolve(std::forward<T>(value));
|
||||
});
|
||||
return QPromise<T>{[&](const QPromiseResolve<T>& resolve) {
|
||||
resolve(std::forward<T>(value));
|
||||
}};
|
||||
}
|
||||
|
||||
inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promises)
|
||||
template<template<typename, typename...> class Sequence, typename... Args>
|
||||
inline QPromise<void> QPromise<void>::all(const Sequence<QPromise<void>, Args...>& promises)
|
||||
{
|
||||
const int count = promises.size();
|
||||
if (count == 0) {
|
||||
return QPromise<void>::resolve();
|
||||
}
|
||||
|
||||
return QPromise<void>([=](
|
||||
const QPromiseResolve<void>& resolve,
|
||||
const QPromiseReject<void>& reject) {
|
||||
|
||||
QSharedPointer<int> remaining(new int(promises.size()));
|
||||
|
||||
for (const auto& promise: promises) {
|
||||
promise.then([=]() {
|
||||
if (--(*remaining) == 0) {
|
||||
resolve();
|
||||
}
|
||||
}, [=]() {
|
||||
if (*remaining != -1) {
|
||||
*remaining = -1;
|
||||
reject(std::current_exception());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return QtPromise::all(promises);
|
||||
}
|
||||
|
||||
inline QPromise<void> QPromise<void>::resolve()
|
||||
{
|
||||
return QPromise<void>([](const QPromiseResolve<void>& resolve) {
|
||||
resolve();
|
||||
});
|
||||
return QtPromise::resolve();
|
||||
}
|
||||
|
||||
} // namespace QtPromise
|
||||
|
@ -1,148 +1,239 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_QPROMISE_P_H
|
||||
#define QTPROMISE_QPROMISE_P_H
|
||||
|
||||
// QtPromise
|
||||
#include "qpromiseerror.h"
|
||||
#include "qpromiseglobal.h"
|
||||
|
||||
// Qt
|
||||
#include <QCoreApplication>
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
#include <QReadWriteLock>
|
||||
#include <QSharedPointer>
|
||||
#include <QSharedData>
|
||||
#include <QPointer>
|
||||
#include <QtCore/QAbstractEventDispatcher>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtCore/QReadWriteLock>
|
||||
#include <QtCore/QSharedData>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
class QPromise;
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
class QPromiseResolve;
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
class QPromiseReject;
|
||||
|
||||
class QPromiseConversionException;
|
||||
|
||||
} // namespace QtPromise
|
||||
|
||||
namespace QtPromisePrivate {
|
||||
|
||||
// Use std::invoke_result for C++17 and beyond
|
||||
#if (__cplusplus >= 201703L) || defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)
|
||||
using std::invoke_result;
|
||||
#else
|
||||
template<class F, class... ArgTypes>
|
||||
using invoke_result = std::result_of<F(ArgTypes...)>;
|
||||
#endif
|
||||
|
||||
// https://stackoverflow.com/a/21653558
|
||||
template <typename F>
|
||||
static void qtpromise_defer(F&& f, QThread* thread = nullptr)
|
||||
template<typename F>
|
||||
static void qtpromise_defer(F&& f, const QPointer<QThread>& thread)
|
||||
{
|
||||
using FType = typename std::decay<F>::type;
|
||||
|
||||
struct Event : public QEvent
|
||||
{
|
||||
using FType = typename std::decay<F>::type;
|
||||
Event(FType&& f) : QEvent(QEvent::None), m_f(std::move(f)) { }
|
||||
Event(const FType& f) : QEvent(QEvent::None), m_f(f) { }
|
||||
~Event() { m_f(); }
|
||||
Event(FType&& f) : QEvent{QEvent::None}, m_f{std::move(f)} { }
|
||||
Event(const FType& f) : QEvent{QEvent::None}, m_f{f} { }
|
||||
~Event() override { m_f(); }
|
||||
FType m_f;
|
||||
};
|
||||
|
||||
if (!thread || thread->isFinished()) {
|
||||
// Make sure to not call `f` if the captured thread doesn't exist anymore,
|
||||
// which would potentially result in dispatching to the wrong thread (ie.
|
||||
// nullptr == current thread). Since the target thread is gone, it should
|
||||
// be safe to simply skip that notification.
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* target = QAbstractEventDispatcher::instance(thread);
|
||||
if (!target && QCoreApplication::closingDown()) {
|
||||
// When the app is shutting down, the even loop is not anymore available
|
||||
// so we don't have any way to dispatch `f`. This case can happen when a
|
||||
// promise is resolved after the app is requested to close, in which case
|
||||
// we should not trigger any error and skip that notification.
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT_X(target, "postMetaCall", "Target thread must have an event loop");
|
||||
QCoreApplication::postEvent(target, new Event(std::forward<F>(f)));
|
||||
QCoreApplication::postEvent(target, new Event{std::forward<F>(f)});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct PromiseDeduce
|
||||
template<typename F>
|
||||
static void qtpromise_defer(F&& f)
|
||||
{
|
||||
using Type = QtPromise::QPromise<Unqualified<T> >;
|
||||
Q_ASSERT(QThread::currentThread());
|
||||
qtpromise_defer(std::forward<F>(f), QThread::currentThread());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class PromiseValue
|
||||
{
|
||||
public:
|
||||
PromiseValue() { }
|
||||
PromiseValue(const T& data) : m_data(std::make_shared<T>(data)) { }
|
||||
PromiseValue(T&& data) : m_data(std::make_shared<T>(std::forward<T>(data))) { }
|
||||
bool isNull() const { return m_data == nullptr; }
|
||||
const T& data() const { return *m_data; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<T> m_data;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct PromiseDeduce<QtPromise::QPromise<T> >
|
||||
: public PromiseDeduce<T>
|
||||
class PromiseError
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
PromiseError(const T& value)
|
||||
{
|
||||
try {
|
||||
throw value;
|
||||
} catch (...) {
|
||||
m_data = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
PromiseError() { }
|
||||
PromiseError(const std::exception_ptr& exception) : m_data{exception} { }
|
||||
Q_NORETURN void rethrow() const { std::rethrow_exception(m_data); }
|
||||
bool isNull() const { return m_data == nullptr; }
|
||||
|
||||
private:
|
||||
// NOTE(SB) std::exception_ptr is already a shared pointer
|
||||
std::exception_ptr m_data;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct PromiseDeduce
|
||||
{
|
||||
using Type = QtPromise::QPromise<T>;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct PromiseDeduce<T&> : public PromiseDeduce<T>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
struct PromiseDeduce<const T> : public PromiseDeduce<T>
|
||||
{ };
|
||||
|
||||
template<typename T>
|
||||
struct PromiseDeduce<const volatile T> : public PromiseDeduce<T>
|
||||
{ };
|
||||
|
||||
template<typename T>
|
||||
struct PromiseDeduce<QtPromise::QPromise<T>> : public PromiseDeduce<T>
|
||||
{ };
|
||||
|
||||
template<typename Functor, typename... Args>
|
||||
struct PromiseFunctor
|
||||
{
|
||||
using ResultType = typename invoke_result<Functor, Args...>::type;
|
||||
using PromiseType = typename PromiseDeduce<ResultType>::Type;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct PromiseFulfill
|
||||
{
|
||||
static void call(
|
||||
T&& value,
|
||||
const QtPromise::QPromiseResolve<T>& resolve,
|
||||
const QtPromise::QPromiseReject<T>&)
|
||||
template<typename V, typename TResolve, typename TReject>
|
||||
static void call(V&& value, const TResolve& resolve, const TReject&)
|
||||
{
|
||||
resolve(std::move(value));
|
||||
resolve(std::forward<V>(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct PromiseFulfill<QtPromise::QPromise<T> >
|
||||
template<typename T>
|
||||
struct PromiseFulfill<QtPromise::QPromise<T>>
|
||||
{
|
||||
static void call(
|
||||
const QtPromise::QPromise<T>& promise,
|
||||
const QtPromise::QPromiseResolve<T>& resolve,
|
||||
const QtPromise::QPromiseReject<T>& reject)
|
||||
template<typename TResolve, typename TReject>
|
||||
static void
|
||||
call(const QtPromise::QPromise<T>& promise, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
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());
|
||||
});
|
||||
promise.then(
|
||||
[=]() {
|
||||
resolve(promise.m_d->value());
|
||||
},
|
||||
[=]() { // catch all
|
||||
reject(promise.m_d->error());
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct PromiseFulfill<QtPromise::QPromise<void> >
|
||||
template<>
|
||||
struct PromiseFulfill<QtPromise::QPromise<void>>
|
||||
{
|
||||
template <typename TPromise, typename TResolve, typename TReject>
|
||||
static void call(
|
||||
const TPromise& promise,
|
||||
const TResolve& resolve,
|
||||
const TReject& reject)
|
||||
template<typename TPromise, typename TResolve, typename TReject>
|
||||
static void call(const TPromise& promise, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
if (promise.isFulfilled()) {
|
||||
resolve();
|
||||
} else if (promise.isRejected()) {
|
||||
reject(promise.m_d->error());
|
||||
} else {
|
||||
promise.then([=]() {
|
||||
resolve();
|
||||
}, [=]() { // catch all
|
||||
reject(promise.m_d->error());
|
||||
});
|
||||
promise.then(
|
||||
[=]() {
|
||||
resolve();
|
||||
},
|
||||
[=]() { // catch all
|
||||
reject(promise.m_d->error());
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename TRes>
|
||||
template<typename Result>
|
||||
struct PromiseDispatch
|
||||
{
|
||||
using Promise = typename PromiseDeduce<TRes>::Type;
|
||||
using ResType = Unqualified<TRes>;
|
||||
|
||||
template <typename THandler, typename TResolve, typename TReject>
|
||||
static void call(const T& value, THandler handler, const TResolve& resolve, const TReject& reject)
|
||||
template<typename Resolve, typename Reject, typename Functor, typename... Args>
|
||||
static void call(const Resolve& resolve, const Reject& reject, Functor fn, Args&&... args)
|
||||
{
|
||||
try {
|
||||
PromiseFulfill<ResType>::call(handler(value), resolve, reject);
|
||||
PromiseFulfill<Unqualified<Result>>::call(fn(std::forward<Args>(args)...),
|
||||
resolve,
|
||||
reject);
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct PromiseDispatch<T, void>
|
||||
template<>
|
||||
struct PromiseDispatch<void>
|
||||
{
|
||||
using Promise = QtPromise::QPromise<void>;
|
||||
|
||||
template <typename THandler, typename TResolve, typename TReject>
|
||||
static void call(const T& value, THandler handler, const TResolve& resolve, const TReject& reject)
|
||||
template<typename Resolve, typename Reject, typename Functor, typename... Args>
|
||||
static void call(const Resolve& resolve, const Reject& reject, Functor fn, Args&&... args)
|
||||
{
|
||||
try {
|
||||
handler(value);
|
||||
fn(std::forward<Args>(args)...);
|
||||
resolve();
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
@ -150,104 +241,62 @@ struct PromiseDispatch<T, void>
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TRes>
|
||||
struct PromiseDispatch<void, TRes>
|
||||
{
|
||||
using Promise = typename PromiseDeduce<TRes>::Type;
|
||||
using ResType = Unqualified<TRes>;
|
||||
|
||||
template <typename THandler, typename TResolve, typename TReject>
|
||||
static void call(THandler handler, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
try {
|
||||
PromiseFulfill<ResType>::call(handler(), resolve, reject);
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct PromiseDispatch<void, void>
|
||||
{
|
||||
using Promise = QtPromise::QPromise<void>;
|
||||
|
||||
template <typename THandler, typename TResolve, typename TReject>
|
||||
static void call(THandler handler, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
try {
|
||||
handler();
|
||||
resolve();
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
|
||||
template<typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
|
||||
struct PromiseHandler
|
||||
{
|
||||
using ResType = typename std::result_of<THandler(T)>::type;
|
||||
using Promise = typename PromiseDispatch<T, ResType>::Promise;
|
||||
using ResType = typename invoke_result<THandler, T>::type;
|
||||
using Promise = typename PromiseDeduce<ResType>::Type;
|
||||
|
||||
template <typename TResolve, typename TReject>
|
||||
static std::function<void(const T&)> create(
|
||||
const THandler& handler,
|
||||
const TResolve& resolve,
|
||||
const TReject& reject)
|
||||
template<typename TResolve, typename TReject>
|
||||
static std::function<void(const T&)>
|
||||
create(const THandler& handler, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
return [=](const T& value) {
|
||||
PromiseDispatch<T, ResType>::call(value, std::move(handler), resolve, reject);
|
||||
PromiseDispatch<ResType>::call(resolve, reject, handler, value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename THandler>
|
||||
template<typename T, typename THandler>
|
||||
struct PromiseHandler<T, THandler, void>
|
||||
{
|
||||
using ResType = typename std::result_of<THandler()>::type;
|
||||
using Promise = typename PromiseDispatch<T, ResType>::Promise;
|
||||
using ResType = typename invoke_result<THandler>::type;
|
||||
using Promise = typename PromiseDeduce<ResType>::Type;
|
||||
|
||||
template <typename TResolve, typename TReject>
|
||||
static std::function<void(const T&)> create(
|
||||
const THandler& handler,
|
||||
const TResolve& resolve,
|
||||
const TReject& reject)
|
||||
template<typename TResolve, typename TReject>
|
||||
static std::function<void(const T&)>
|
||||
create(const THandler& handler, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
return [=](const T&) {
|
||||
PromiseDispatch<void, ResType>::call(handler, resolve, reject);
|
||||
PromiseDispatch<ResType>::call(resolve, reject, handler);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename THandler>
|
||||
template<typename THandler>
|
||||
struct PromiseHandler<void, THandler, void>
|
||||
{
|
||||
using ResType = typename std::result_of<THandler()>::type;
|
||||
using Promise = typename PromiseDispatch<void, ResType>::Promise;
|
||||
using ResType = typename invoke_result<THandler>::type;
|
||||
using Promise = typename PromiseDeduce<ResType>::Type;
|
||||
|
||||
template <typename TResolve, typename TReject>
|
||||
static std::function<void()> create(
|
||||
const THandler& handler,
|
||||
const TResolve& resolve,
|
||||
const TReject& reject)
|
||||
template<typename TResolve, typename TReject>
|
||||
static std::function<void()>
|
||||
create(const THandler& handler, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
return [=]() {
|
||||
PromiseDispatch<void, ResType>::call(handler, resolve, reject);
|
||||
PromiseDispatch<ResType>::call(resolve, reject, handler);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
struct PromiseHandler<T, std::nullptr_t, void>
|
||||
{
|
||||
using Promise = QtPromise::QPromise<T>;
|
||||
|
||||
template <typename TResolve, typename TReject>
|
||||
static std::function<void(const T&)> create(
|
||||
std::nullptr_t,
|
||||
const TResolve& resolve,
|
||||
const TReject& reject)
|
||||
template<typename TResolve, typename TReject>
|
||||
static std::function<void(const T&)>
|
||||
create(std::nullptr_t, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
return [=](const T& value) {
|
||||
// 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
|
||||
@ -257,16 +306,13 @@ struct PromiseHandler<T, std::nullptr_t, void>
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
template<>
|
||||
struct PromiseHandler<void, std::nullptr_t, void>
|
||||
{
|
||||
using Promise = QtPromise::QPromise<void>;
|
||||
|
||||
template <typename TResolve, typename TReject>
|
||||
static std::function<void()> create(
|
||||
std::nullptr_t,
|
||||
const TResolve& resolve,
|
||||
const TReject&)
|
||||
template<typename TResolve, typename TReject>
|
||||
static std::function<void()> create(std::nullptr_t, const TResolve& resolve, const TReject&)
|
||||
{
|
||||
return [=]() {
|
||||
// 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
|
||||
@ -276,22 +322,20 @@ struct PromiseHandler<void, std::nullptr_t, void>
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
|
||||
template<typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
|
||||
struct PromiseCatcher
|
||||
{
|
||||
using ResType = typename std::result_of<THandler(TArg)>::type;
|
||||
using ResType = typename invoke_result<THandler, TArg>::type;
|
||||
|
||||
template <typename TResolve, typename TReject>
|
||||
static std::function<void(const QtPromise::QPromiseError&)> create(
|
||||
const THandler& handler,
|
||||
const TResolve& resolve,
|
||||
const TReject& reject)
|
||||
template<typename TResolve, typename TReject>
|
||||
static std::function<void(const PromiseError&)>
|
||||
create(const THandler& handler, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
return [=](const QtPromise::QPromiseError& error) {
|
||||
return [=](const PromiseError& error) {
|
||||
try {
|
||||
error.rethrow();
|
||||
} catch (const TArg& error) {
|
||||
PromiseDispatch<TArg, ResType>::call(error, handler, resolve, reject);
|
||||
} catch (const TArg& argError) {
|
||||
PromiseDispatch<ResType>::call(resolve, reject, handler, argError);
|
||||
} catch (...) {
|
||||
reject(std::current_exception());
|
||||
}
|
||||
@ -299,37 +343,33 @@ struct PromiseCatcher
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename THandler>
|
||||
template<typename T, typename THandler>
|
||||
struct PromiseCatcher<T, THandler, void>
|
||||
{
|
||||
using ResType = typename std::result_of<THandler()>::type;
|
||||
using ResType = typename invoke_result<THandler>::type;
|
||||
|
||||
template <typename TResolve, typename TReject>
|
||||
static std::function<void(const QtPromise::QPromiseError&)> create(
|
||||
const THandler& handler,
|
||||
const TResolve& resolve,
|
||||
const TReject& reject)
|
||||
template<typename TResolve, typename TReject>
|
||||
static std::function<void(const PromiseError&)>
|
||||
create(const THandler& handler, const TResolve& resolve, const TReject& reject)
|
||||
{
|
||||
return [=](const QtPromise::QPromiseError& error) {
|
||||
return [=](const PromiseError& error) {
|
||||
try {
|
||||
error.rethrow();
|
||||
} catch (...) {
|
||||
PromiseDispatch<void, ResType>::call(handler, resolve, reject);
|
||||
PromiseDispatch<ResType>::call(resolve, reject, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
struct PromiseCatcher<T, std::nullptr_t, void>
|
||||
{
|
||||
template <typename TResolve, typename TReject>
|
||||
static std::function<void(const QtPromise::QPromiseError&)> create(
|
||||
std::nullptr_t,
|
||||
const TResolve&,
|
||||
const TReject& reject)
|
||||
template<typename TResolve, typename TReject>
|
||||
static std::function<void(const PromiseError&)>
|
||||
create(std::nullptr_t, const TResolve&, const TReject& reject)
|
||||
{
|
||||
return [=](const QtPromise::QPromiseError& error) {
|
||||
return [=](const PromiseError& error) {
|
||||
// 2.2.7.4. If onRejected is not a function and promise1 is rejected,
|
||||
// promise2 must be rejected with the same reason as promise1
|
||||
reject(error);
|
||||
@ -337,63 +377,61 @@ struct PromiseCatcher<T, std::nullptr_t, void>
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> class PromiseData;
|
||||
template<typename T, typename F>
|
||||
struct PromiseMapper
|
||||
{ };
|
||||
|
||||
template <typename T, typename F>
|
||||
template<typename T, typename F, template<typename, typename...> class Sequence, typename... Args>
|
||||
struct PromiseMapper<Sequence<T, Args...>, F>
|
||||
{
|
||||
using ReturnType = typename invoke_result<F, T, int>::type;
|
||||
using ResultType = QVector<typename PromiseDeduce<ReturnType>::Type::Type>;
|
||||
using PromiseType = QtPromise::QPromise<ResultType>;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class PromiseData;
|
||||
|
||||
template<typename T, 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&)> >;
|
||||
using Handler = std::pair<QPointer<QThread>, std::function<F>>;
|
||||
using Catcher = std::pair<QPointer<QThread>, std::function<void(const PromiseError&)>>;
|
||||
|
||||
virtual ~PromiseDataBase() {}
|
||||
virtual ~PromiseDataBase() { }
|
||||
|
||||
bool isFulfilled() const
|
||||
{
|
||||
return !isPending() && m_error.isNull();
|
||||
}
|
||||
|
||||
bool isRejected() const
|
||||
{
|
||||
return !isPending() && !m_error.isNull();
|
||||
}
|
||||
bool isFulfilled() const { return !isPending() && m_error.isNull(); }
|
||||
bool isRejected() const { return !isPending() && !m_error.isNull(); }
|
||||
|
||||
bool isPending() const
|
||||
{
|
||||
QReadLocker lock(&m_lock);
|
||||
QReadLocker lock{&m_lock};
|
||||
return !m_settled;
|
||||
}
|
||||
|
||||
void addHandler(std::function<F> handler)
|
||||
{
|
||||
QWriteLocker lock(&m_lock);
|
||||
QWriteLocker lock{&m_lock};
|
||||
m_handlers.append({QThread::currentThread(), std::move(handler)});
|
||||
}
|
||||
|
||||
void addCatcher(std::function<void(const Error&)> catcher)
|
||||
void addCatcher(std::function<void(const PromiseError&)> catcher)
|
||||
{
|
||||
QWriteLocker lock(&m_lock);
|
||||
QWriteLocker lock{&m_lock};
|
||||
m_catchers.append({QThread::currentThread(), std::move(catcher)});
|
||||
}
|
||||
|
||||
void reject(Error error)
|
||||
template<typename E>
|
||||
void reject(E&& error)
|
||||
{
|
||||
Q_ASSERT(isPending());
|
||||
Q_ASSERT(m_error.isNull());
|
||||
m_error.reset(new Error(std::move(error)));
|
||||
m_error = PromiseError{std::forward<E>(error)};
|
||||
setSettled();
|
||||
}
|
||||
|
||||
void reject(const QSharedPointer<Error>& error)
|
||||
{
|
||||
Q_ASSERT(isPending());
|
||||
Q_ASSERT(m_error.isNull());
|
||||
m_error = error;
|
||||
this->setSettled();
|
||||
}
|
||||
|
||||
const QSharedPointer<Error>& error() const
|
||||
const PromiseError& error() const
|
||||
{
|
||||
Q_ASSERT(isRejected());
|
||||
return m_error;
|
||||
@ -413,8 +451,8 @@ public:
|
||||
// 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));
|
||||
QVector<Handler> handlers = std::move(m_handlers);
|
||||
QVector<Catcher> catchers = std::move(m_catchers);
|
||||
m_lock.unlock();
|
||||
|
||||
if (m_error.isNull()) {
|
||||
@ -422,14 +460,16 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
QSharedPointer<Error> error = m_error;
|
||||
PromiseError error = m_error;
|
||||
Q_ASSERT(!error.isNull());
|
||||
|
||||
for (const auto& catcher: catchers) {
|
||||
for (const auto& catcher : catchers) {
|
||||
const auto& fn = catcher.second;
|
||||
qtpromise_defer([=]() {
|
||||
fn(*error);
|
||||
}, catcher.first);
|
||||
qtpromise_defer(
|
||||
[=]() {
|
||||
fn(error);
|
||||
},
|
||||
catcher.first);
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,7 +478,7 @@ protected:
|
||||
|
||||
void setSettled()
|
||||
{
|
||||
QWriteLocker lock(&m_lock);
|
||||
QWriteLocker lock{&m_lock};
|
||||
Q_ASSERT(!m_settled);
|
||||
m_settled = true;
|
||||
}
|
||||
@ -449,40 +489,25 @@ private:
|
||||
bool m_settled = false;
|
||||
QVector<Handler> m_handlers;
|
||||
QVector<Catcher> m_catchers;
|
||||
QSharedPointer<Error> m_error;
|
||||
PromiseError m_error;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
class PromiseData : public PromiseDataBase<T, void(const T&)>
|
||||
{
|
||||
using Handler = typename PromiseDataBase<T, void(const T&)>::Handler;
|
||||
|
||||
public:
|
||||
void resolve(T&& value)
|
||||
template<typename V>
|
||||
void resolve(V&& value)
|
||||
{
|
||||
Q_ASSERT(this->isPending());
|
||||
Q_ASSERT(m_value.isNull());
|
||||
m_value.reset(new T(std::move(value)));
|
||||
m_value = PromiseValue<T>{std::forward<V>(value)};
|
||||
this->setSettled();
|
||||
}
|
||||
|
||||
void resolve(const T& value)
|
||||
{
|
||||
Q_ASSERT(this->isPending());
|
||||
Q_ASSERT(m_value.isNull());
|
||||
m_value.reset(new T(value));
|
||||
this->setSettled();
|
||||
}
|
||||
|
||||
void resolve(const QSharedPointer<T>& value)
|
||||
{
|
||||
Q_ASSERT(this->isPending());
|
||||
Q_ASSERT(m_value.isNull());
|
||||
m_value = value;
|
||||
this->setSettled();
|
||||
}
|
||||
|
||||
const QSharedPointer<T>& value() const
|
||||
const PromiseValue<T>& value() const
|
||||
{
|
||||
Q_ASSERT(this->isFulfilled());
|
||||
return m_value;
|
||||
@ -490,41 +515,113 @@ public:
|
||||
|
||||
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
|
||||
{
|
||||
QSharedPointer<T> value(m_value);
|
||||
PromiseValue<T> value = m_value;
|
||||
Q_ASSERT(!value.isNull());
|
||||
|
||||
for (const auto& handler: handlers) {
|
||||
for (const auto& handler : handlers) {
|
||||
const auto& fn = handler.second;
|
||||
qtpromise_defer([=]() {
|
||||
fn(*value);
|
||||
}, handler.first);
|
||||
qtpromise_defer(
|
||||
[=]() {
|
||||
fn(value.data());
|
||||
},
|
||||
handler.first);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QSharedPointer<T> m_value;
|
||||
PromiseValue<T> m_value;
|
||||
};
|
||||
|
||||
template <>
|
||||
template<>
|
||||
class PromiseData<void> : public PromiseDataBase<void, void()>
|
||||
{
|
||||
using Handler = typename PromiseDataBase<void, void()>::Handler;
|
||||
using Handler = PromiseDataBase<void, void()>::Handler;
|
||||
|
||||
public:
|
||||
void resolve()
|
||||
{
|
||||
setSettled();
|
||||
}
|
||||
void resolve() { setSettled(); }
|
||||
|
||||
protected:
|
||||
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
|
||||
{
|
||||
for (const auto& handler: handlers) {
|
||||
for (const auto& handler : handlers) {
|
||||
qtpromise_defer(handler.second, handler.first);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QtPromise
|
||||
struct PromiseInspect
|
||||
{
|
||||
template<typename T>
|
||||
static inline PromiseData<T>* get(const QtPromise::QPromise<T>& p)
|
||||
{
|
||||
return p.m_d.data();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ifndef QTPROMISE_QPROMISE_H
|
||||
template<typename T, typename U, bool IsConvertibleViaStaticCast>
|
||||
struct PromiseConverterBase;
|
||||
|
||||
template<typename T, typename U>
|
||||
struct PromiseConverterBase<T, U, true>
|
||||
{
|
||||
static std::function<U(const T&)> create()
|
||||
{
|
||||
return [](const T& value) {
|
||||
return static_cast<U>(value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename U>
|
||||
struct PromiseConverterBase<T, U, false>
|
||||
{
|
||||
static std::function<U(const T&)> create()
|
||||
{
|
||||
return [](const T& value) {
|
||||
auto tmp = QVariant::fromValue(value);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
// https://doc.qt.io/qt-6/qvariant.html#using-canconvert-and-convert-consecutively
|
||||
if (tmp.canConvert(QMetaType{qMetaTypeId<U>()})
|
||||
&& tmp.convert(QMetaType{qMetaTypeId<U>()})) {
|
||||
return qvariant_cast<U>(tmp);
|
||||
}
|
||||
#else
|
||||
// https://doc.qt.io/qt-5/qvariant.html#using-canconvert-and-convert-consecutively
|
||||
if (tmp.canConvert(qMetaTypeId<U>()) && tmp.convert(qMetaTypeId<U>())) {
|
||||
return qvariant_cast<U>(tmp);
|
||||
}
|
||||
#endif
|
||||
throw QtPromise::QPromiseConversionException{};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct PromiseConverterBase<T, QVariant, false>
|
||||
{
|
||||
static std::function<QVariant(const T&)> create()
|
||||
{
|
||||
return [](const T& value) {
|
||||
return QVariant::fromValue(value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename U>
|
||||
struct PromiseConverter
|
||||
: PromiseConverterBase<T,
|
||||
U,
|
||||
// Fundamental types and converting constructors.
|
||||
std::is_convertible<T, U>::value ||
|
||||
// Conversion to void.
|
||||
std::is_same<U, void>::value ||
|
||||
// Conversion between enums and arithmetic types.
|
||||
((std::is_enum<T>::value && std::is_arithmetic<U>::value)
|
||||
|| (std::is_arithmetic<T>::value && std::is_enum<U>::value)
|
||||
|| (std::is_enum<T>::value && std::is_enum<U>::value))>
|
||||
{ };
|
||||
|
||||
} // namespace QtPromisePrivate
|
||||
|
||||
#endif // QTPROMISE_QPROMISE_H
|
||||
|
58
src/qtpromise/qpromiseconnections.h
Normal file
58
src/qtpromise/qpromiseconnections.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_QPROMISECONNECTIONS_H
|
||||
#define QTPROMISE_QPROMISECONNECTIONS_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
class QPromiseConnections
|
||||
{
|
||||
public:
|
||||
QPromiseConnections() : m_d(std::make_shared<Data>()) { }
|
||||
|
||||
int count() const { return static_cast<int>(m_d->connections.count()); }
|
||||
|
||||
void disconnect() const { m_d->disconnect(); }
|
||||
|
||||
void operator<<(QMetaObject::Connection&& other) const
|
||||
{
|
||||
m_d->connections.append(std::move(other));
|
||||
}
|
||||
|
||||
private:
|
||||
struct Data
|
||||
{
|
||||
QVector<QMetaObject::Connection> connections;
|
||||
|
||||
~Data()
|
||||
{
|
||||
if (!connections.empty()) {
|
||||
qWarning("QPromiseConnections: destroyed with unhandled connections.");
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
for (const auto& connection : connections) {
|
||||
QObject::disconnect(connection);
|
||||
}
|
||||
connections.clear();
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<Data> m_d;
|
||||
};
|
||||
|
||||
} // namespace QtPromise
|
||||
|
||||
#endif // QTPROMISE_QPROMISECONNECTIONS_H
|
@ -1,71 +0,0 @@
|
||||
#ifndef QTPROMISE_QPROMISEERROR_H
|
||||
#define QTPROMISE_QPROMISEERROR_H
|
||||
|
||||
// QtPromise
|
||||
#include "qpromiseglobal.h"
|
||||
|
||||
// Qt
|
||||
#include <QException>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
class QPromiseError
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
QPromiseError(const T& value)
|
||||
{
|
||||
try {
|
||||
throw value;
|
||||
} catch (...) {
|
||||
m_exception = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
QPromiseError(const std::exception_ptr& exception)
|
||||
: m_exception(exception)
|
||||
{ }
|
||||
|
||||
QPromiseError(const QPromiseError& error)
|
||||
: m_exception(error.m_exception)
|
||||
{ }
|
||||
|
||||
QPromiseError(QPromiseError&& other)
|
||||
: m_exception(nullptr)
|
||||
{
|
||||
swap(other);
|
||||
}
|
||||
|
||||
QPromiseError& operator =(QPromiseError other)
|
||||
{
|
||||
swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(QPromiseError& other)
|
||||
{
|
||||
qSwap(m_exception, other.m_exception);
|
||||
}
|
||||
|
||||
void rethrow() const
|
||||
{
|
||||
std::rethrow_exception(m_exception);
|
||||
}
|
||||
|
||||
private:
|
||||
std::exception_ptr m_exception;
|
||||
};
|
||||
|
||||
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
|
69
src/qtpromise/qpromiseexceptions.h
Normal file
69
src/qtpromise/qpromiseexceptions.h
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_QPROMISEEXCEPTIONS_H
|
||||
#define QTPROMISE_QPROMISEEXCEPTIONS_H
|
||||
|
||||
#include "qpromiseglobal.h"
|
||||
|
||||
#include <QtCore/QException>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
class QPromiseCanceledException : public QException
|
||||
{
|
||||
public:
|
||||
void raise() const Q_DECL_OVERRIDE { throw *this; }
|
||||
QPromiseCanceledException* clone() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return new QPromiseCanceledException{*this};
|
||||
}
|
||||
};
|
||||
|
||||
class QPromiseContextException : public QException
|
||||
{
|
||||
public:
|
||||
void raise() const Q_DECL_OVERRIDE { throw *this; }
|
||||
QPromiseContextException* clone() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return new QPromiseContextException{*this};
|
||||
}
|
||||
};
|
||||
|
||||
class QPromiseConversionException : public QException
|
||||
{
|
||||
public:
|
||||
void raise() const Q_DECL_OVERRIDE { throw *this; }
|
||||
QPromiseConversionException* clone() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return new QPromiseConversionException{*this};
|
||||
}
|
||||
};
|
||||
|
||||
class QPromiseTimeoutException : public QException
|
||||
{
|
||||
public:
|
||||
void raise() const Q_DECL_OVERRIDE { throw *this; }
|
||||
QPromiseTimeoutException* clone() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return new QPromiseTimeoutException{*this};
|
||||
}
|
||||
};
|
||||
|
||||
class QPromiseUndefinedException : public QException
|
||||
{
|
||||
public:
|
||||
void raise() const Q_DECL_OVERRIDE { throw *this; }
|
||||
QPromiseUndefinedException* clone() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return new QPromiseUndefinedException{*this};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QtPromise
|
||||
|
||||
#endif // QTPROMISE_QPROMISEEXCEPTIONS_H
|
@ -1,42 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_QPROMISEFUTURE_P_H
|
||||
#define QTPROMISE_QPROMISEFUTURE_P_H
|
||||
|
||||
// Qt
|
||||
#include <QFutureWatcher>
|
||||
#include <QFuture>
|
||||
#include "qpromiseexceptions.h"
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
class QPromiseCanceledException : public QException
|
||||
{
|
||||
public:
|
||||
void raise() const Q_DECL_OVERRIDE { throw *this; }
|
||||
QPromiseCanceledException* clone() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return new QPromiseCanceledException(*this);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QtPromise
|
||||
#include <QtCore/QFuture>
|
||||
#include <QtCore/QFutureWatcher>
|
||||
|
||||
namespace QtPromisePrivate {
|
||||
|
||||
template <typename T>
|
||||
struct PromiseDeduce<QFuture<T> >
|
||||
: public PromiseDeduce<T>
|
||||
template<typename T>
|
||||
struct PromiseDeduce<QFuture<T>> : public PromiseDeduce<T>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
struct PromiseFulfill<QFuture<T> >
|
||||
template<typename T>
|
||||
struct PromiseFulfill<QFuture<T>>
|
||||
{
|
||||
static void call(
|
||||
const QFuture<T>& future,
|
||||
const QtPromise::QPromiseResolve<T>& resolve,
|
||||
const QtPromise::QPromiseReject<T>& reject)
|
||||
static void call(const QFuture<T>& future,
|
||||
const QtPromise::QPromiseResolve<T>& resolve,
|
||||
const QtPromise::QPromiseReject<T>& reject)
|
||||
{
|
||||
using Watcher = QFutureWatcher<T>;
|
||||
|
||||
Watcher* watcher = new Watcher();
|
||||
Watcher* watcher = new Watcher{};
|
||||
QObject::connect(watcher, &Watcher::finished, [=]() mutable {
|
||||
try {
|
||||
if (watcher->isCanceled()) {
|
||||
@ -46,7 +38,7 @@ struct PromiseFulfill<QFuture<T> >
|
||||
// rethrown potential exceptions using waitForFinished() and thus detect
|
||||
// if the future has been canceled by the user or an exception.
|
||||
watcher->waitForFinished();
|
||||
reject(QtPromise::QPromiseCanceledException());
|
||||
reject(QtPromise::QPromiseCanceledException{});
|
||||
} else {
|
||||
PromiseFulfill<T>::call(watcher->result(), resolve, reject);
|
||||
}
|
||||
@ -61,23 +53,22 @@ struct PromiseFulfill<QFuture<T> >
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct PromiseFulfill<QFuture<void> >
|
||||
template<>
|
||||
struct PromiseFulfill<QFuture<void>>
|
||||
{
|
||||
static void call(
|
||||
const QFuture<void>& future,
|
||||
const QtPromise::QPromiseResolve<void>& resolve,
|
||||
const QtPromise::QPromiseReject<void>& reject)
|
||||
static void call(const QFuture<void>& future,
|
||||
const QtPromise::QPromiseResolve<void>& resolve,
|
||||
const QtPromise::QPromiseReject<void>& reject)
|
||||
{
|
||||
using Watcher = QFutureWatcher<void>;
|
||||
|
||||
Watcher* watcher = new Watcher();
|
||||
Watcher* watcher = new Watcher{};
|
||||
QObject::connect(watcher, &Watcher::finished, [=]() mutable {
|
||||
try {
|
||||
if (watcher->isCanceled()) {
|
||||
// let's rethrown potential exception
|
||||
watcher->waitForFinished();
|
||||
reject(QtPromise::QPromiseCanceledException());
|
||||
reject(QtPromise::QPromiseCanceledException{});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
|
@ -1,33 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_QPROMISEGLOBAL_H
|
||||
#define QTPROMISE_QPROMISEGLOBAL_H
|
||||
|
||||
// QtCore
|
||||
#include <QtGlobal>
|
||||
|
||||
// STL
|
||||
#include <functional>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
namespace QtPromisePrivate {
|
||||
|
||||
namespace QtPromisePrivate
|
||||
{
|
||||
// https://rmf.io/cxx11/even-more-traits#unqualified_types
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
using Unqualified = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||
|
||||
/*!
|
||||
* \struct HasCallOperator
|
||||
* http://stackoverflow.com/a/5839442
|
||||
* http://stackoverflow.com/a/5117641
|
||||
*/
|
||||
template <typename T>
|
||||
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);
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -35,7 +40,7 @@ struct HasCallOperator
|
||||
* http://stackoverflow.com/a/7943765
|
||||
* http://stackoverflow.com/a/27885283
|
||||
*/
|
||||
template <typename... Args>
|
||||
template<typename... Args>
|
||||
struct ArgsTraits
|
||||
{
|
||||
using types = std::tuple<Args...>;
|
||||
@ -43,7 +48,7 @@ struct ArgsTraits
|
||||
static const size_t count = std::tuple_size<types>::value;
|
||||
};
|
||||
|
||||
template <>
|
||||
template<>
|
||||
struct ArgsTraits<>
|
||||
{
|
||||
using types = std::tuple<>;
|
||||
@ -51,75 +56,61 @@ struct ArgsTraits<>
|
||||
static const size_t count = 0;
|
||||
};
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
// Fallback implementation, including types (T) which are not functions but
|
||||
// also lambda with `auto` arguments, which are not covered but still valid
|
||||
// callbacks (see the QPromiseBase<T> template constructor).
|
||||
template<typename T, typename Enabled = void>
|
||||
struct ArgsOf : public ArgsTraits<>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
// Partial specialization for null function.
|
||||
template<>
|
||||
struct ArgsOf<std::nullptr_t> : public ArgsTraits<>
|
||||
{ };
|
||||
|
||||
// Partial specialization for type with a non-overloaded operator().
|
||||
// This applies to lambda, std::function but not to std::bind result.
|
||||
template<typename T>
|
||||
struct ArgsOf<T, typename std::enable_if<HasCallOperator<T>::value>::type>
|
||||
: public ArgsOf<decltype(&T::operator())>
|
||||
{ };
|
||||
|
||||
template <typename TReturn, typename... Args>
|
||||
struct ArgsOf<TReturn(Args...)> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template <typename TReturn, typename... Args>
|
||||
struct ArgsOf<TReturn(*)(Args...)> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template <typename T, typename TReturn, typename... Args>
|
||||
struct ArgsOf<TReturn(T::*)(Args...)> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template <typename T, typename TReturn, typename... Args>
|
||||
struct ArgsOf<TReturn(T::*)(Args...) const> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template <typename T, typename TReturn, typename... Args>
|
||||
struct ArgsOf<TReturn(T::*)(Args...) volatile> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template <typename T, typename TReturn, typename... Args>
|
||||
struct ArgsOf<TReturn(T::*)(Args...) const volatile> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
struct ArgsOf<std::function<T> > : public ArgsOf<T>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
// Partial specialization to remove reference and rvalue (e.g. lambda, std::function, etc.).
|
||||
template<typename T>
|
||||
struct ArgsOf<T&> : public ArgsOf<T>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
struct ArgsOf<const T&> : public ArgsOf<T>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
struct ArgsOf<volatile T&> : public ArgsOf<T>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
struct ArgsOf<const volatile T&> : public ArgsOf<T>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
struct ArgsOf<T&&> : public ArgsOf<T>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
struct ArgsOf<const T&&> : public ArgsOf<T>
|
||||
// Partial specialization for function type.
|
||||
template<typename R, typename... Args>
|
||||
struct ArgsOf<R(Args...)> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
struct ArgsOf<volatile T&&> : public ArgsOf<T>
|
||||
// Partial specialization for function pointer.
|
||||
template<typename R, typename... Args>
|
||||
struct ArgsOf<R (*)(Args...)> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template <typename T>
|
||||
struct ArgsOf<const volatile T&&> : public ArgsOf<T>
|
||||
// Partial specialization for pointer-to-member-function (i.e. operator()'s).
|
||||
template<typename R, typename T, typename... Args>
|
||||
struct ArgsOf<R (T::*)(Args...)> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template<typename R, typename T, typename... Args>
|
||||
struct ArgsOf<R (T::*)(Args...) const> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template<typename R, typename T, typename... Args>
|
||||
struct ArgsOf<R (T::*)(Args...) volatile> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
template<typename R, typename T, typename... Args>
|
||||
struct ArgsOf<R (T::*)(Args...) const volatile> : public ArgsTraits<Args...>
|
||||
{ };
|
||||
|
||||
} // namespace QtPromisePrivate
|
||||
|
||||
#endif // ifndef QTPROMISE_QPROMISEGLOBAL_H
|
||||
#endif // QTPROMISE_QPROMISEGLOBAL_H
|
||||
|
@ -1,40 +1,295 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_QPROMISEHELPERS_H
|
||||
#define QTPROMISE_QPROMISEHELPERS_H
|
||||
|
||||
// QtPromise
|
||||
#include "qpromise_p.h"
|
||||
#include "qpromisehelpers_p.h"
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
template <typename T>
|
||||
static inline typename QtPromisePrivate::PromiseDeduce<T>::Type qPromise(T&& value)
|
||||
template<typename T>
|
||||
static inline typename QtPromisePrivate::PromiseDeduce<T>::Type resolve(T&& value)
|
||||
{
|
||||
using namespace QtPromisePrivate;
|
||||
using Promise = typename PromiseDeduce<T>::Type;
|
||||
return Promise([&](
|
||||
const QPromiseResolve<typename Promise::Type>& resolve,
|
||||
const QPromiseReject<typename Promise::Type>& reject) {
|
||||
PromiseFulfill<T>::call(std::forward<T>(value), resolve, reject);
|
||||
});
|
||||
using PromiseType = typename PromiseDeduce<T>::Type;
|
||||
using ValueType = typename PromiseType::Type;
|
||||
using ResolveType = QPromiseResolve<ValueType>;
|
||||
using RejectType = QPromiseReject<ValueType>;
|
||||
|
||||
return PromiseType{[&](ResolveType&& resolve, RejectType&& reject) {
|
||||
PromiseFulfill<Unqualified<T>>::call(std::forward<T>(value),
|
||||
std::forward<ResolveType>(resolve),
|
||||
std::forward<RejectType>(reject));
|
||||
}};
|
||||
}
|
||||
|
||||
static inline QPromise<void> qPromise()
|
||||
template<typename T>
|
||||
static inline QPromise<T> resolve(QPromise<T> value)
|
||||
{
|
||||
return QPromise<void>([](
|
||||
const QPromiseResolve<void>& resolve) {
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline QPromise<void> resolve()
|
||||
{
|
||||
return QPromise<void>{[](const QPromiseResolve<void>& resolve) {
|
||||
resolve();
|
||||
}};
|
||||
}
|
||||
|
||||
template<typename T, template<typename, typename...> class Sequence = QVector, typename... Args>
|
||||
static inline QPromise<QVector<T>> all(const Sequence<QPromise<T>, Args...>& promises)
|
||||
{
|
||||
const int count = static_cast<int>(promises.size());
|
||||
if (count == 0) {
|
||||
return QtPromise::resolve(QVector<T>{});
|
||||
}
|
||||
|
||||
return QPromise<QVector<T>>{
|
||||
[=](const QPromiseResolve<QVector<T>>& resolve, const QPromiseReject<QVector<T>>& reject) {
|
||||
auto remaining = QSharedPointer<int>::create(count);
|
||||
auto results = QSharedPointer<QVector<T>>::create(count);
|
||||
|
||||
int i = 0;
|
||||
for (const auto& promise : promises) {
|
||||
promise.then(
|
||||
[=](const T& res) mutable {
|
||||
(*results)[i] = res;
|
||||
if (--(*remaining) == 0) {
|
||||
resolve(*results);
|
||||
}
|
||||
},
|
||||
[=]() mutable {
|
||||
if (*remaining != -1) {
|
||||
*remaining = -1;
|
||||
reject(std::current_exception());
|
||||
}
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
template<template<typename, typename...> class Sequence = QVector, typename... Args>
|
||||
static inline QPromise<void> all(const Sequence<QPromise<void>, Args...>& promises)
|
||||
{
|
||||
const int count = static_cast<int>(promises.size());
|
||||
if (count == 0) {
|
||||
return QtPromise::resolve();
|
||||
}
|
||||
|
||||
return QPromise<void>{
|
||||
[=](const QPromiseResolve<void>& resolve, const QPromiseReject<void>& reject) {
|
||||
auto remaining = QSharedPointer<int>::create(count);
|
||||
|
||||
for (const auto& promise : promises) {
|
||||
promise.then(
|
||||
[=]() {
|
||||
if (--(*remaining) == 0) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
[=]() {
|
||||
if (*remaining != -1) {
|
||||
*remaining = -1;
|
||||
reject(std::current_exception());
|
||||
}
|
||||
});
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
template<typename Functor, typename... Args>
|
||||
static inline typename QtPromisePrivate::PromiseFunctor<Functor, Args...>::PromiseType
|
||||
attempt(Functor&& fn, Args&&... args)
|
||||
{
|
||||
using namespace QtPromisePrivate;
|
||||
using FunctorType = PromiseFunctor<Functor, Args...>;
|
||||
using PromiseType = typename FunctorType::PromiseType;
|
||||
using ValueType = typename PromiseType::Type;
|
||||
|
||||
// NOTE: std::forward<T<U>>: MSVC 2013 fails when forwarding
|
||||
// template type (error: "expects 4 arguments - 0 provided").
|
||||
// However it succeeds with type alias.
|
||||
// TODO: should we expose QPromise::ResolveType & RejectType?
|
||||
using ResolveType = QPromiseResolve<ValueType>;
|
||||
using RejectType = QPromiseReject<ValueType>;
|
||||
|
||||
return PromiseType{[&](ResolveType&& resolve, RejectType&& reject) {
|
||||
PromiseDispatch<typename FunctorType::ResultType>::call(std::forward<ResolveType>(resolve),
|
||||
std::forward<RejectType>(reject),
|
||||
std::forward<Functor>(fn),
|
||||
std::forward<Args>(args)...);
|
||||
}};
|
||||
}
|
||||
|
||||
template<typename Sender, typename Signal>
|
||||
static inline typename QtPromisePrivate::PromiseFromSignal<Signal>
|
||||
connect(const Sender* sender, Signal signal)
|
||||
{
|
||||
using namespace QtPromisePrivate;
|
||||
using T = typename PromiseFromSignal<Signal>::Type;
|
||||
|
||||
return QPromise<T>{[&](const QPromiseResolve<T>& resolve, const QPromiseReject<T>& reject) {
|
||||
QPromiseConnections connections;
|
||||
connectSignalToResolver(connections, resolve, sender, signal);
|
||||
connectDestroyedToReject(connections, reject, sender);
|
||||
}};
|
||||
}
|
||||
|
||||
template<typename FSender, typename FSignal, typename RSender, typename RSignal>
|
||||
static inline typename QtPromisePrivate::PromiseFromSignal<FSignal>
|
||||
connect(const FSender* fsender, FSignal fsignal, const RSender* rsender, RSignal rsignal)
|
||||
{
|
||||
using namespace QtPromisePrivate;
|
||||
using T = typename PromiseFromSignal<FSignal>::Type;
|
||||
|
||||
return QPromise<T>{[&](const QPromiseResolve<T>& resolve, const QPromiseReject<T>& reject) {
|
||||
QPromiseConnections connections;
|
||||
connectSignalToResolver(connections, resolve, fsender, fsignal);
|
||||
connectSignalToResolver(connections, reject, rsender, rsignal);
|
||||
connectDestroyedToReject(connections, reject, fsender);
|
||||
}};
|
||||
}
|
||||
|
||||
template<typename Sender, typename FSignal, typename RSignal>
|
||||
static inline typename QtPromisePrivate::PromiseFromSignal<FSignal>
|
||||
connect(const Sender* sender, FSignal fsignal, RSignal rsignal)
|
||||
{
|
||||
return connect(sender, fsignal, sender, rsignal);
|
||||
}
|
||||
|
||||
template<typename Sequence, typename Functor>
|
||||
static inline QPromise<Sequence> each(const Sequence& values, Functor&& fn)
|
||||
{
|
||||
return QPromise<Sequence>::resolve(values).each(std::forward<Functor>(fn));
|
||||
}
|
||||
|
||||
template<typename Sequence, typename Functor>
|
||||
static inline typename QtPromisePrivate::PromiseMapper<Sequence, Functor>::PromiseType
|
||||
map(const Sequence& values, Functor fn)
|
||||
{
|
||||
using namespace QtPromisePrivate;
|
||||
using MapperType = PromiseMapper<Sequence, Functor>;
|
||||
using ResType = typename MapperType::ResultType::value_type;
|
||||
using RetType = typename MapperType::ReturnType;
|
||||
|
||||
int i = 0;
|
||||
|
||||
std::vector<QPromise<ResType>> promises;
|
||||
for (const auto& v : values) {
|
||||
promises.push_back(QPromise<ResType>{
|
||||
[&](const QPromiseResolve<ResType>& resolve, const QPromiseReject<ResType>& reject) {
|
||||
PromiseFulfill<RetType>::call(fn(v, i), resolve, reject);
|
||||
}});
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return QtPromise::all(promises);
|
||||
}
|
||||
|
||||
template<typename Sequence, typename Functor>
|
||||
static inline QPromise<Sequence> filter(const Sequence& values, Functor fn)
|
||||
{
|
||||
return QtPromise::map(values, fn).then([=](const QVector<bool>& filters) {
|
||||
Sequence filtered;
|
||||
|
||||
auto filter = filters.begin();
|
||||
for (auto& value : values) {
|
||||
if (*filter) {
|
||||
filtered.push_back(std::move(value));
|
||||
}
|
||||
|
||||
filter++;
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline QPromise<QVector<T> > qPromiseAll(const QVector<QPromise<T> >& promises)
|
||||
template<typename T,
|
||||
template<typename...> class Sequence = QVector,
|
||||
typename Reducer,
|
||||
typename Input,
|
||||
typename... Args>
|
||||
static inline typename QtPromisePrivate::PromiseDeduce<Input>::Type
|
||||
reduce(const Sequence<T, Args...>& values, Reducer fn, Input initial)
|
||||
{
|
||||
return QPromise<T>::all(promises);
|
||||
using namespace QtPromisePrivate;
|
||||
using PromiseType = typename PromiseDeduce<T>::Type;
|
||||
using ValueType = typename PromiseType::Type;
|
||||
|
||||
int idx = 0;
|
||||
|
||||
auto promise = QtPromise::resolve(std::move(initial));
|
||||
for (const auto& value : values) {
|
||||
auto input = QtPromise::resolve(value);
|
||||
promise = promise.then([=]() {
|
||||
return input.then([=](const ValueType& cur) {
|
||||
return fn(PromiseInspect::get(promise)->value().data(), cur, idx);
|
||||
});
|
||||
});
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
static inline QPromise<void> qPromiseAll(const QVector<QPromise<void> >& promises)
|
||||
template<typename T,
|
||||
template<typename...> class Sequence = QVector,
|
||||
typename Reducer,
|
||||
typename... Args>
|
||||
static inline typename QtPromisePrivate::PromiseDeduce<T>::Type
|
||||
reduce(const Sequence<T, Args...>& values, Reducer fn)
|
||||
{
|
||||
return QPromise<void>::all(promises);
|
||||
using namespace QtPromisePrivate;
|
||||
using PromiseType = typename PromiseDeduce<T>::Type;
|
||||
using ValueType = typename PromiseType::Type;
|
||||
|
||||
Q_ASSERT(values.size());
|
||||
|
||||
int idx = 1;
|
||||
|
||||
auto it = values.begin();
|
||||
auto promise = QtPromise::resolve(*it);
|
||||
for (++it; it != values.end(); ++it) {
|
||||
auto input = QtPromise::resolve(*it);
|
||||
promise = promise.then([=]() {
|
||||
return input.then([=](const ValueType& cur) {
|
||||
return fn(PromiseInspect::get(promise)->value().data(), cur, idx);
|
||||
});
|
||||
});
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// DEPRECATIONS (remove at version 1)
|
||||
|
||||
template<typename... Args>
|
||||
Q_DECL_DEPRECATED_X("Use QtPromise::resolve instead")
|
||||
static inline auto qPromise(Args&&... args)
|
||||
-> decltype(QtPromise::resolve(std::forward<Args>(args)...))
|
||||
{
|
||||
return QtPromise::resolve(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
Q_DECL_DEPRECATED_X("Use QtPromise::all instead")
|
||||
static inline auto qPromiseAll(Args&&... args)
|
||||
-> decltype(QtPromise::all(std::forward<Args>(args)...))
|
||||
{
|
||||
return QtPromise::all(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
} // namespace QtPromise
|
||||
|
93
src/qtpromise/qpromisehelpers_p.h
Normal file
93
src/qtpromise/qpromisehelpers_p.h
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_QPROMISEHELPERS_P_H
|
||||
#define QTPROMISE_QPROMISEHELPERS_P_H
|
||||
|
||||
#include "qpromiseconnections.h"
|
||||
#include "qpromiseexceptions.h"
|
||||
|
||||
namespace QtPromisePrivate {
|
||||
|
||||
// TODO: Suppress QPrivateSignal trailing private signal args
|
||||
// TODO: Support deducing tuple from args (might require MSVC2017)
|
||||
|
||||
template<typename Signal>
|
||||
using PromiseFromSignal = typename QtPromise::QPromise<Unqualified<typename ArgsOf<Signal>::first>>;
|
||||
|
||||
// Connect signal() to QPromiseResolve
|
||||
template<typename Sender, typename Signal>
|
||||
typename std::enable_if<(ArgsOf<Signal>::count == 0)>::type
|
||||
connectSignalToResolver(const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseResolve<void>& resolve,
|
||||
const Sender* sender,
|
||||
Signal signal)
|
||||
{
|
||||
connections << QObject::connect(sender, signal, [=]() {
|
||||
connections.disconnect();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
// Connect signal() to QPromiseReject
|
||||
template<typename T, typename Sender, typename Signal>
|
||||
typename std::enable_if<(ArgsOf<Signal>::count == 0)>::type
|
||||
connectSignalToResolver(const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseReject<T>& reject,
|
||||
const Sender* sender,
|
||||
Signal signal)
|
||||
{
|
||||
connections << QObject::connect(sender, signal, [=]() {
|
||||
connections.disconnect();
|
||||
reject(QtPromise::QPromiseUndefinedException{});
|
||||
});
|
||||
}
|
||||
|
||||
// Connect signal(args...) to QPromiseResolve
|
||||
template<typename T, typename Sender, typename Signal>
|
||||
typename std::enable_if<(ArgsOf<Signal>::count >= 1)>::type
|
||||
connectSignalToResolver(const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseResolve<T>& resolve,
|
||||
const Sender* sender,
|
||||
Signal signal)
|
||||
{
|
||||
connections << QObject::connect(sender, signal, [=](const T& value) {
|
||||
connections.disconnect();
|
||||
resolve(value);
|
||||
});
|
||||
}
|
||||
|
||||
// Connect signal(args...) to QPromiseReject
|
||||
template<typename T, typename Sender, typename Signal>
|
||||
typename std::enable_if<(ArgsOf<Signal>::count >= 1)>::type
|
||||
connectSignalToResolver(const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseReject<T>& reject,
|
||||
const Sender* sender,
|
||||
Signal signal)
|
||||
{
|
||||
using V = Unqualified<typename ArgsOf<Signal>::first>;
|
||||
connections << QObject::connect(sender, signal, [=](const V& value) {
|
||||
connections.disconnect();
|
||||
reject(value);
|
||||
});
|
||||
}
|
||||
|
||||
// Connect QObject::destroyed signal to QPromiseReject
|
||||
template<typename T, typename Sender>
|
||||
void connectDestroyedToReject(const QtPromise::QPromiseConnections& connections,
|
||||
const QtPromise::QPromiseReject<T>& reject,
|
||||
const Sender* sender)
|
||||
{
|
||||
connections << QObject::connect(sender, &QObject::destroyed, [=]() {
|
||||
connections.disconnect();
|
||||
reject(QtPromise::QPromiseContextException{});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace QtPromisePrivate
|
||||
|
||||
#endif // QTPROMISE_QPROMISEHELPERS_P_H
|
140
src/qtpromise/qpromiseresolver.h
Normal file
140
src/qtpromise/qpromiseresolver.h
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef QTPROMISE_QPROMISERESOLVER_H
|
||||
#define QTPROMISE_QPROMISERESOLVER_H
|
||||
|
||||
#include "qpromiseexceptions.h"
|
||||
|
||||
#include <QtCore/QExplicitlySharedDataPointer>
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
template<typename T>
|
||||
class QPromise;
|
||||
|
||||
} // namespace QtPromise
|
||||
|
||||
namespace QtPromisePrivate {
|
||||
|
||||
template<typename T>
|
||||
class PromiseResolver
|
||||
{
|
||||
public:
|
||||
PromiseResolver(QtPromise::QPromise<T> promise) : m_d{new Data{}}
|
||||
{
|
||||
m_d->promise = new QtPromise::QPromise<T>{std::move(promise)};
|
||||
}
|
||||
|
||||
template<typename E>
|
||||
void reject(E&& error)
|
||||
{
|
||||
auto promise = m_d->promise;
|
||||
if (promise) {
|
||||
Q_ASSERT(promise->isPending());
|
||||
promise->m_d->reject(std::forward<E>(error));
|
||||
promise->m_d->dispatch();
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
void reject()
|
||||
{
|
||||
auto promise = m_d->promise;
|
||||
if (promise) {
|
||||
Q_ASSERT(promise->isPending());
|
||||
promise->m_d->reject(QtPromise::QPromiseUndefinedException{});
|
||||
promise->m_d->dispatch();
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
void resolve(V&& value)
|
||||
{
|
||||
auto promise = m_d->promise;
|
||||
if (promise) {
|
||||
Q_ASSERT(promise->isPending());
|
||||
promise->m_d->resolve(std::forward<V>(value));
|
||||
promise->m_d->dispatch();
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
void resolve()
|
||||
{
|
||||
auto promise = m_d->promise;
|
||||
if (promise) {
|
||||
Q_ASSERT(promise->isPending());
|
||||
promise->m_d->resolve();
|
||||
promise->m_d->dispatch();
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Data : public QSharedData
|
||||
{
|
||||
QtPromise::QPromise<T>* promise = nullptr;
|
||||
};
|
||||
|
||||
QExplicitlySharedDataPointer<Data> m_d;
|
||||
|
||||
void release()
|
||||
{
|
||||
Q_ASSERT(m_d->promise);
|
||||
Q_ASSERT(!m_d->promise->isPending());
|
||||
delete m_d->promise;
|
||||
m_d->promise = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QtPromisePrivate
|
||||
|
||||
namespace QtPromise {
|
||||
|
||||
template<class T>
|
||||
class QPromiseResolve
|
||||
{
|
||||
public:
|
||||
QPromiseResolve(QtPromisePrivate::PromiseResolver<T> resolver) : m_resolver{std::move(resolver)}
|
||||
{ }
|
||||
|
||||
template<typename V>
|
||||
void operator()(V&& value) const
|
||||
{
|
||||
m_resolver.resolve(std::forward<V>(value));
|
||||
}
|
||||
|
||||
void operator()() const { m_resolver.resolve(); }
|
||||
|
||||
private:
|
||||
mutable QtPromisePrivate::PromiseResolver<T> m_resolver;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class QPromiseReject
|
||||
{
|
||||
public:
|
||||
QPromiseReject(QtPromisePrivate::PromiseResolver<T> resolver) : m_resolver{std::move(resolver)}
|
||||
{ }
|
||||
|
||||
template<typename E>
|
||||
void operator()(E&& error) const
|
||||
{
|
||||
m_resolver.reject(std::forward<E>(error));
|
||||
}
|
||||
|
||||
void operator()() const { m_resolver.reject(); }
|
||||
|
||||
private:
|
||||
mutable QtPromisePrivate::PromiseResolver<T> m_resolver;
|
||||
};
|
||||
|
||||
} // namespace QtPromise
|
||||
|
||||
#endif // QTPROMISE_QPROMISERESOLVER_H
|
@ -1,8 +0,0 @@
|
||||
HEADERS += \
|
||||
$$PWD/qpromise.h \
|
||||
$$PWD/qpromise.inl \
|
||||
$$PWD/qpromise_p.h \
|
||||
$$PWD/qpromiseerror.h \
|
||||
$$PWD/qpromisefuture.h \
|
||||
$$PWD/qpromiseglobal.h \
|
||||
$$PWD/qpromisehelpers.h
|
@ -1,5 +0,0 @@
|
||||
TEMPLATE = lib
|
||||
CONFIG += c++11 qt thread warn_on
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
include(qtpromise.pri)
|
@ -1,2 +0,0 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = qtpromise
|
9
tests/CMakeLists.txt
Normal file
9
tests/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED
|
||||
COMPONENTS
|
||||
Concurrent
|
||||
Test
|
||||
)
|
||||
|
||||
include(QtPromiseAddTest)
|
||||
|
||||
add_subdirectory(auto)
|
1
tests/auto/CMakeLists.txt
Normal file
1
tests/auto/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
add_subdirectory(qtpromise)
|
@ -1,8 +0,0 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS += \
|
||||
benchmark \
|
||||
future \
|
||||
helpers \
|
||||
qpromise \
|
||||
requirements \
|
||||
thread
|
@ -1,4 +0,0 @@
|
||||
TARGET = tst_benchmark
|
||||
SOURCES += $$PWD/tst_benchmark.cpp
|
||||
|
||||
include(../tests.pri)
|
@ -1,315 +0,0 @@
|
||||
// QtPromise
|
||||
#include <QtPromise>
|
||||
|
||||
// Qt
|
||||
#include <QtTest>
|
||||
|
||||
using namespace QtPromise;
|
||||
|
||||
class tst_benchmark : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void valueResolve();
|
||||
void valueReject();
|
||||
void valueThen();
|
||||
void valueFinally();
|
||||
void valueTap();
|
||||
void valueDelayed();
|
||||
void errorReject();
|
||||
void errorThen();
|
||||
|
||||
}; // class tst_benchmark
|
||||
|
||||
QTEST_MAIN(tst_benchmark)
|
||||
#include "tst_benchmark.moc"
|
||||
|
||||
struct Logs {
|
||||
int ctor = 0;
|
||||
int copy = 0;
|
||||
int move = 0;
|
||||
int refs = 0;
|
||||
|
||||
void reset() {
|
||||
ctor = 0;
|
||||
copy = 0;
|
||||
move = 0;
|
||||
refs = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct Logger
|
||||
{
|
||||
Logger() { logs().ctor++; logs().refs++; }
|
||||
Logger(const Logger&) { logs().copy++; logs().refs++; }
|
||||
Logger(Logger&&) { logs().move++; logs().refs++; }
|
||||
~Logger() { logs().refs--; }
|
||||
|
||||
Logger& operator=(const Logger&) { logs().copy++; return *this; }
|
||||
Logger& operator=(Logger&&) { logs().move++; return *this; }
|
||||
|
||||
public: // STATICS
|
||||
static Logs& logs() { static Logs logs; return logs; }
|
||||
};
|
||||
|
||||
struct Data : public Logger
|
||||
{
|
||||
Data(int v): Logger(), m_value(v) {}
|
||||
int value() const { return m_value; }
|
||||
|
||||
private:
|
||||
int m_value;
|
||||
};
|
||||
|
||||
void tst_benchmark::valueResolve()
|
||||
{
|
||||
{ // should move the value when resolved by rvalue
|
||||
Data::logs().reset();
|
||||
QPromise<Data>([&](const QPromiseResolve<Data>& resolve) {
|
||||
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();
|
||||
QPromise<Data>([&](const QPromiseResolve<Data>& resolve) {
|
||||
Data value(42);
|
||||
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
|
||||
Data::logs().reset();
|
||||
QPromise<Data>([&](const QPromiseResolve<Data>&, const QPromiseReject<Data>& reject) {
|
||||
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::valueThen()
|
||||
{
|
||||
{ // should not copy value on continutation if fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QPromise<Data>::resolve(Data(42)).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 promise data
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
{ // should not create value on continutation if rejected
|
||||
int value = -1;
|
||||
QString error;
|
||||
Data::logs().reset();
|
||||
QPromise<Data>::reject(QString("foo")).then([&](const Data& res) {
|
||||
value = res.value();
|
||||
}, [&](const QString& err) {
|
||||
error = err;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 0);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(error, QString("foo"));
|
||||
QCOMPARE(value, -1);
|
||||
}
|
||||
{ // should move the returned value when fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QPromise<int>::resolve(42).then([&](int res) {
|
||||
return Data(res+2);
|
||||
}).then([&](const Data& res) {
|
||||
value = res.value();
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 1); // move values to the next promise data
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 44);
|
||||
}
|
||||
{ // should not create any data if handler throws
|
||||
Data::logs().reset();
|
||||
QPromise<int>::resolve(42).then([&](int res) {
|
||||
throw QString("foo");
|
||||
return Data(res+2);
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 0);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Data::logs().reset();
|
||||
QPromise<int>([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
||||
reject(Data(42));
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 1); // copy value in std::exception_ptr
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
}
|
||||
{ // should create one copy of the error when rejected by lvalue (no extra copy)
|
||||
Data::logs().reset();
|
||||
QPromise<int>([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
||||
Data error(42);
|
||||
reject(error);
|
||||
}).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::errorThen()
|
||||
{
|
||||
{ // should not copy error on continutation if rejected
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QPromise<void>::reject(Data(42)).fail([&](const Data& res) {
|
||||
value = res.value();
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 1); // (initial) copy value in std::exception_ptr
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
{ // should not copy error on continutation if rethrown
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QPromise<void>::reject(Data(42)).fail([](const Data&) {
|
||||
throw;
|
||||
}).fail([&](const Data& res) {
|
||||
value = res.value();
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 1); // (initial) copy value in std::exception_ptr
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
QT += concurrent
|
||||
TARGET = tst_future
|
||||
SOURCES += $$PWD/tst_future.cpp
|
||||
|
||||
include(../tests.pri)
|
@ -1,346 +0,0 @@
|
||||
// QtPromise
|
||||
#include <QtPromise>
|
||||
|
||||
// Qt
|
||||
#include <QtConcurrent>
|
||||
#include <QtTest>
|
||||
|
||||
using namespace QtPromise;
|
||||
|
||||
class tst_future : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void fulfilled();
|
||||
void fulfilled_void();
|
||||
void rejected();
|
||||
void rejected_void();
|
||||
void unhandled();
|
||||
void unhandled_void();
|
||||
void canceled();
|
||||
void canceled_void();
|
||||
void canceledFromThread();
|
||||
void then();
|
||||
void then_void();
|
||||
void fail();
|
||||
void fail_void();
|
||||
void finally();
|
||||
void finallyRejected();
|
||||
|
||||
}; // class tst_future
|
||||
|
||||
class MyException : public QException
|
||||
{
|
||||
public:
|
||||
MyException(const QString& error)
|
||||
: m_error(error)
|
||||
{ }
|
||||
|
||||
const QString& error() const { return m_error; }
|
||||
|
||||
void raise() const { throw *this; }
|
||||
MyException* clone() const { return new MyException(*this); }
|
||||
|
||||
private:
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_future)
|
||||
#include "tst_future.moc"
|
||||
|
||||
void tst_future::fulfilled()
|
||||
{
|
||||
int result = -1;
|
||||
auto p = qPromise(QtConcurrent::run([]() {
|
||||
return 42;
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.then([&](int res) {
|
||||
result = res;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(result, 42);
|
||||
}
|
||||
|
||||
void tst_future::fulfilled_void()
|
||||
{
|
||||
int result = -1;
|
||||
auto p = qPromise(QtConcurrent::run([]() { }));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.then([&]() {
|
||||
result = 42;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(result, 42);
|
||||
}
|
||||
|
||||
void tst_future::rejected()
|
||||
{
|
||||
QString error;
|
||||
auto p = qPromise(QtConcurrent::run([]() {
|
||||
throw MyException("foo");
|
||||
return 42;
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const MyException& e) {
|
||||
error = e.error();
|
||||
return -1;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString("foo"));
|
||||
}
|
||||
|
||||
void tst_future::rejected_void()
|
||||
{
|
||||
QString error;
|
||||
auto p = qPromise(QtConcurrent::run([]() {
|
||||
throw MyException("foo");
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const MyException& e) {
|
||||
error = e.error();
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString("foo"));
|
||||
}
|
||||
|
||||
void tst_future::unhandled()
|
||||
{
|
||||
QString error;
|
||||
auto p = qPromise(QtConcurrent::run([]() {
|
||||
throw QString("foo");
|
||||
return 42;
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QString& err) {
|
||||
error += err;
|
||||
return -1;
|
||||
}).fail([&](const QUnhandledException&) {
|
||||
error += "bar";
|
||||
return -1;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString("bar"));
|
||||
}
|
||||
|
||||
void tst_future::unhandled_void()
|
||||
{
|
||||
QString error;
|
||||
auto p = qPromise(QtConcurrent::run([]() {
|
||||
throw QString("foo");
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QString& err) {
|
||||
error += err;
|
||||
}).fail([&](const QUnhandledException&) {
|
||||
error += "bar";
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString("bar"));
|
||||
}
|
||||
|
||||
void tst_future::canceled()
|
||||
{
|
||||
QString error;
|
||||
auto p = qPromise(QFuture<int>()); // Constructs an empty, canceled future.
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QPromiseCanceledException&) {
|
||||
error = "canceled";
|
||||
return -1;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString("canceled"));
|
||||
}
|
||||
|
||||
void tst_future::canceled_void()
|
||||
{
|
||||
QString error;
|
||||
auto p = qPromise(QFuture<void>()); // Constructs an empty, canceled future.
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QPromiseCanceledException&) {
|
||||
error = "canceled";
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString("canceled"));
|
||||
}
|
||||
|
||||
void tst_future::canceledFromThread()
|
||||
{
|
||||
QString error;
|
||||
auto p = qPromise(QtConcurrent::run([]() {
|
||||
throw QPromiseCanceledException();
|
||||
}));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QPromiseCanceledException&) {
|
||||
error = "bar";
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString("bar"));
|
||||
}
|
||||
|
||||
void tst_future::then()
|
||||
{
|
||||
QString result;
|
||||
auto input = qPromise(42);
|
||||
auto output = input.then([](int res) {
|
||||
return QtConcurrent::run([=]() {
|
||||
return QString("foo%1").arg(res);
|
||||
});
|
||||
});
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
output.then([&](const QString& res) {
|
||||
result = res;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(output.isFulfilled(), true);
|
||||
QCOMPARE(result, QString("foo42"));
|
||||
}
|
||||
|
||||
void tst_future::then_void()
|
||||
{
|
||||
QString result;
|
||||
auto input = qPromise();
|
||||
auto output = input.then([&]() {
|
||||
return QtConcurrent::run([&]() {
|
||||
result = "foo";
|
||||
});
|
||||
});
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
output.then([&]() {
|
||||
result += "bar";
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(result, QString("foobar"));
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
QCOMPARE(input.isRejected(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
output.then([&](const QString& res) {
|
||||
result = res;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(output.isFulfilled(), true);
|
||||
QCOMPARE(result, QString("foobar"));
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
QCOMPARE(input.isRejected(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
output.then([&]() {
|
||||
result = result.prepend("foo");
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(output.isFulfilled(), true);
|
||||
QCOMPARE(result, QString("foobar"));
|
||||
}
|
||||
|
||||
void tst_future::finally()
|
||||
{
|
||||
auto input = qPromise(42);
|
||||
auto output = input.finally([]() {
|
||||
return QtConcurrent::run([]() {
|
||||
return QString("foo");
|
||||
});
|
||||
});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int> >::value));
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
int value = -1;
|
||||
output.then([&](int res) {
|
||||
value = res;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(output.isFulfilled(), true);
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
|
||||
void tst_future::finallyRejected()
|
||||
{
|
||||
auto input = qPromise(42);
|
||||
auto output = input.finally([]() {
|
||||
return QtConcurrent::run([]() {
|
||||
throw MyException("foo");
|
||||
});
|
||||
});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(output), QPromise<int> >::value));
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
QString error;
|
||||
output.fail([&](const MyException& e) {
|
||||
error = e.error();
|
||||
return -1;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(output.isRejected(), true);
|
||||
QCOMPARE(error, QString("foo"));
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
TARGET = tst_helpers
|
||||
SOURCES += $$PWD/tst_helpers.cpp
|
||||
|
||||
include(../tests.pri)
|
@ -1,244 +0,0 @@
|
||||
// QtPromise
|
||||
#include <QtPromise>
|
||||
|
||||
// Qt
|
||||
#include <QtTest>
|
||||
|
||||
using namespace QtPromise;
|
||||
|
||||
class tst_helpers : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void resolve();
|
||||
void resolve_void();
|
||||
void resolve_promise();
|
||||
void resolve_promise_void();
|
||||
|
||||
void allFulfilled();
|
||||
void allFulfilled_void();
|
||||
void allRejected();
|
||||
void allRejected_void();
|
||||
void allEmpty();
|
||||
void allEmpty_void();
|
||||
|
||||
}; // class tst_helpers
|
||||
|
||||
QTEST_MAIN(tst_helpers)
|
||||
#include "tst_helpers.moc"
|
||||
|
||||
void tst_helpers::resolve()
|
||||
{
|
||||
int value = -1;
|
||||
auto p = QtPromise::qPromise(42);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<int> >::value));
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
|
||||
p.then([&](int res) {
|
||||
value = res;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
|
||||
void tst_helpers::resolve_void()
|
||||
{
|
||||
int value = -1;
|
||||
auto p = QtPromise::qPromise();
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
|
||||
p.then([&]() {
|
||||
value = 42;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
|
||||
void tst_helpers::resolve_promise()
|
||||
{
|
||||
QString value;
|
||||
auto p = QtPromise::qPromise(
|
||||
QPromise<QString>([](const QPromiseResolve<QString>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=](){
|
||||
resolve("foo");
|
||||
});
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QString> >::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.then([&](const QString& res) {
|
||||
value = res;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(value, QString("foo"));
|
||||
}
|
||||
|
||||
void tst_helpers::resolve_promise_void()
|
||||
{
|
||||
QList<int> values;
|
||||
auto p = QtPromise::qPromise(
|
||||
QPromise<void>([&](const QPromiseResolve<void>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=, &values](){
|
||||
values << 42;
|
||||
resolve();
|
||||
});
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.then([&]() {
|
||||
values << 43;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(values, QList<int>({42, 43}));
|
||||
}
|
||||
|
||||
void tst_helpers::allFulfilled()
|
||||
{
|
||||
auto p0 = QtPromise::qPromise(42);
|
||||
auto p1 = QtPromise::qPromise(44);
|
||||
auto p2 = QPromise<int>([](const QPromiseResolve<int>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=](){
|
||||
resolve(43);
|
||||
});
|
||||
});
|
||||
|
||||
auto p = qPromiseAll(QVector<QPromise<int> >{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
|
||||
QVector<int> values;
|
||||
p.then([&](const QVector<int>& res) {
|
||||
values = res;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(p2.isFulfilled(), true);
|
||||
QCOMPARE(values, QVector<int>({42, 43, 44}));
|
||||
}
|
||||
|
||||
void tst_helpers::allFulfilled_void()
|
||||
{
|
||||
auto p0 = QtPromise::qPromise();
|
||||
auto p1 = QtPromise::qPromise();
|
||||
auto p2 = QPromise<void>([](const QPromiseResolve<void>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=](){
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
auto p = qPromiseAll(QVector<QPromise<void> >{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
|
||||
p.wait();
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(p2.isFulfilled(), true);
|
||||
}
|
||||
|
||||
void tst_helpers::allRejected()
|
||||
{
|
||||
auto p0 = QtPromise::qPromise(42);
|
||||
auto p1 = QtPromise::qPromise(44);
|
||||
auto p2 = QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
||||
QtPromisePrivate::qtpromise_defer([=](){
|
||||
reject(QString("foo"));
|
||||
});
|
||||
});
|
||||
|
||||
auto p = qPromiseAll(QVector<QPromise<int> >{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
|
||||
QString error;
|
||||
p.fail([&](const QString& err) {
|
||||
error = err;
|
||||
return QVector<int>();
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(p2.isRejected(), true);
|
||||
QCOMPARE(error, QString("foo"));
|
||||
}
|
||||
|
||||
void tst_helpers::allRejected_void()
|
||||
{
|
||||
auto p0 = QtPromise::qPromise();
|
||||
auto p1 = QtPromise::qPromise();
|
||||
auto p2 = QPromise<void>([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
|
||||
QtPromisePrivate::qtpromise_defer([=](){
|
||||
reject(QString("foo"));
|
||||
});
|
||||
});
|
||||
|
||||
auto p = qPromiseAll(QVector<QPromise<void> >{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
|
||||
QString error;
|
||||
p.fail([&](const QString& err) {
|
||||
error = err;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(p2.isRejected(), true);
|
||||
QCOMPARE(error, QString("foo"));
|
||||
}
|
||||
|
||||
void tst_helpers::allEmpty()
|
||||
{
|
||||
auto p = qPromiseAll(QVector<QPromise<int> >());
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QVector<int> > >::value));
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
|
||||
QVector<int> values;
|
||||
p.then([&](const QVector<int>& res) {
|
||||
values = res;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(values, QVector<int>());
|
||||
}
|
||||
|
||||
void tst_helpers::allEmpty_void()
|
||||
{
|
||||
auto p = qPromiseAll(QVector<QPromise<void> >());
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<void> >::value));
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
TARGET = tst_qpromise
|
||||
SOURCES += $$PWD/tst_qpromise.cpp
|
||||
|
||||
include(../tests.pri)
|
@ -1,766 +0,0 @@
|
||||
// QtPromise
|
||||
#include <QtPromise>
|
||||
|
||||
// Qt
|
||||
#include <QtTest>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
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 finallyFulfilled();
|
||||
void finallyFulfilled_void();
|
||||
void finallyRejected();
|
||||
void finallyRejected_void();
|
||||
void finallyThrows();
|
||||
void finallyThrows_void();
|
||||
void finallyDelayedResolved();
|
||||
void finallyDelayedRejected();
|
||||
|
||||
void tapFulfilled();
|
||||
void tapFulfilled_void();
|
||||
void tapRejected();
|
||||
void tapRejected_void();
|
||||
void tapThrows();
|
||||
void tapThrows_void();
|
||||
void tapDelayedResolved();
|
||||
void tapDelayedRejected();
|
||||
|
||||
void timeoutFulfilled();
|
||||
void timeoutRejected();
|
||||
void timeoutReject();
|
||||
|
||||
void delayFulfilled();
|
||||
void delayRejected();
|
||||
|
||||
}; // 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::finallyFulfilled()
|
||||
{
|
||||
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::finallyFulfilled_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::finallyRejected()
|
||||
{
|
||||
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::finallyRejected_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::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);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_qpromise::tapFulfilled()
|
||||
{
|
||||
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::tapFulfilled_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::tapRejected()
|
||||
{
|
||||
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::tapRejected_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);
|
||||
}
|
||||
|
||||
void tst_qpromise::tapThrows()
|
||||
{
|
||||
auto p = QPromise<int>::resolve(42).tap([&](int) {
|
||||
throw QString("foo");
|
||||
});
|
||||
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
}
|
||||
|
||||
void tst_qpromise::tapThrows_void()
|
||||
{
|
||||
auto p = QPromise<void>::resolve().tap([&]() {
|
||||
throw QString("foo");
|
||||
});
|
||||
|
||||
QCOMPARE(waitForError(p, QString()), QString("foo"));
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
}
|
||||
|
||||
void tst_qpromise::tapDelayedResolved()
|
||||
{
|
||||
QVector<int> values;
|
||||
auto p = QPromise<int>::resolve(1).tap([&](int) {
|
||||
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
|
||||
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::tapDelayedRejected()
|
||||
{
|
||||
QVector<int> values;
|
||||
auto p = QPromise<int>::resolve(1).tap([&](int) {
|
||||
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
||||
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::timeoutFulfilled()
|
||||
{
|
||||
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::timeoutRejected()
|
||||
{
|
||||
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::timeoutReject()
|
||||
{
|
||||
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.
|
||||
}
|
||||
|
||||
void tst_qpromise::delayFulfilled()
|
||||
{
|
||||
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::delayRejected()
|
||||
{
|
||||
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);
|
||||
}
|
13
tests/auto/qtpromise/CMakeLists.txt
Normal file
13
tests/auto/qtpromise/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
add_subdirectory(shared)
|
||||
|
||||
add_subdirectory(benchmark)
|
||||
add_subdirectory(cpp14)
|
||||
add_subdirectory(deprecations)
|
||||
add_subdirectory(exceptions)
|
||||
add_subdirectory(future)
|
||||
add_subdirectory(helpers)
|
||||
add_subdirectory(internals)
|
||||
add_subdirectory(qpromise)
|
||||
add_subdirectory(qpromiseconnections)
|
||||
add_subdirectory(requirements)
|
||||
add_subdirectory(thread)
|
4
tests/auto/qtpromise/benchmark/CMakeLists.txt
Normal file
4
tests/auto/qtpromise/benchmark/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
qtpromise_add_test(benchmark
|
||||
SOURCES
|
||||
tst_benchmark.cpp
|
||||
)
|
324
tests/auto/qtpromise/benchmark/tst_benchmark.cpp
Normal file
324
tests/auto/qtpromise/benchmark/tst_benchmark.cpp
Normal file
@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "../shared/data.h"
|
||||
|
||||
#include <QtPromise>
|
||||
#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
|
||||
|
||||
class tst_benchmark : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void valueResolve();
|
||||
void valueReject();
|
||||
void valueThen();
|
||||
void valueFinally();
|
||||
void valueTap();
|
||||
void valueDelayed();
|
||||
void errorReject();
|
||||
void errorThen();
|
||||
|
||||
}; // class tst_benchmark
|
||||
|
||||
QTEST_MAIN(tst_benchmark)
|
||||
#include "tst_benchmark.moc"
|
||||
|
||||
void tst_benchmark::valueResolve()
|
||||
{
|
||||
{ // should move the value when resolved by rvalue
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<Data>{[&](const QtPromise::QPromiseResolve<Data>& resolve) {
|
||||
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();
|
||||
QtPromise::QPromise<Data>{[&](const QtPromise::QPromiseResolve<Data>& resolve) {
|
||||
Data value{42};
|
||||
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
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<Data>{[&](const QtPromise::QPromiseResolve<Data>&,
|
||||
const QtPromise::QPromiseReject<Data>& reject) {
|
||||
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::valueThen()
|
||||
{
|
||||
{ // should not copy value on continuation if fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<Data>::resolve(Data{42})
|
||||
.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 promise data
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
{ // should not create value on continuation if rejected
|
||||
int value = -1;
|
||||
QString error;
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<Data>::reject(QString{"foo"})
|
||||
.then(
|
||||
[&](const Data& res) {
|
||||
value = res.value();
|
||||
},
|
||||
[&](const QString& err) {
|
||||
error = err;
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 0);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(error, QString{"foo"});
|
||||
QCOMPARE(value, -1);
|
||||
}
|
||||
{ // should move the returned value when fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<int>::resolve(42)
|
||||
.then([&](int res) {
|
||||
return Data{res + 2};
|
||||
})
|
||||
.then([&](const Data& res) {
|
||||
value = res.value();
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 1); // move values to the next promise data
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
QCOMPARE(value, 44);
|
||||
}
|
||||
{ // should not create any data if handler throws
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<int>::resolve(42)
|
||||
.then([&](int res) {
|
||||
throw QString{"foo"};
|
||||
return Data{res + 2};
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 0);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_benchmark::valueDelayed()
|
||||
{
|
||||
{ // should not copy the value on continuation if fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<int>::resolve(42)
|
||||
.then([&](int res) {
|
||||
return QtPromise::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 continuation if rejected
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<int>::resolve(42)
|
||||
.then([&]() {
|
||||
return QtPromise::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 continuation if fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QtPromise::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 continuation if rejected
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QtPromise::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 continuation if fulfilled
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QtPromise::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 continuation if rejected
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QtPromise::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
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<int>{[&](const QtPromise::QPromiseResolve<int>&,
|
||||
const QtPromise::QPromiseReject<int>& reject) {
|
||||
reject(Data{42});
|
||||
}}.wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
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);
|
||||
}
|
||||
{ // should create one copy of the error when rejected by lvalue (no extra copy)
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<int>{[&](const QtPromise::QPromiseResolve<int>&,
|
||||
const QtPromise::QPromiseReject<int>& reject) {
|
||||
Data error{42};
|
||||
reject(error);
|
||||
}}.wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_benchmark::errorThen()
|
||||
{
|
||||
{ // should not copy error on continuation if rejected
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<void>::reject(Data{42})
|
||||
.fail([&](const Data& res) {
|
||||
value = res.value();
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
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);
|
||||
}
|
||||
{ // should not copy error on continuation if rethrown
|
||||
int value = -1;
|
||||
Data::logs().reset();
|
||||
QtPromise::QPromise<void>::reject(Data{42})
|
||||
.fail([](const Data&) {
|
||||
throw;
|
||||
})
|
||||
.fail([&](const Data& res) {
|
||||
value = res.value();
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
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);
|
||||
}
|
||||
}
|
10
tests/auto/qtpromise/cpp14/CMakeLists.txt
Normal file
10
tests/auto/qtpromise/cpp14/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
# https://cmake.org/cmake/help/latest/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html
|
||||
# https://gcc.gnu.org/projects/cxx-status.html#cxx14
|
||||
if ("cxx_generic_lambdas" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
qtpromise_add_tests(cpp14
|
||||
SOURCES
|
||||
tst_argsof_lambda_auto.cpp
|
||||
tst_resolver_lambda_auto.cpp
|
||||
)
|
||||
endif()
|
33
tests/auto/qtpromise/cpp14/tst_argsof_lambda_auto.cpp
Normal file
33
tests/auto/qtpromise/cpp14/tst_argsof_lambda_auto.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <QtPromise>
|
||||
#include <QtTest>
|
||||
|
||||
using namespace QtPromisePrivate;
|
||||
|
||||
class tst_cpp14_argsof_lambda_auto : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void lambdaAutoArgs();
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_cpp14_argsof_lambda_auto)
|
||||
#include "tst_argsof_lambda_auto.moc"
|
||||
|
||||
void tst_cpp14_argsof_lambda_auto::lambdaAutoArgs()
|
||||
{
|
||||
auto lOneArg = [](auto) {};
|
||||
auto lManyArgs = [](const auto&, auto, auto) {};
|
||||
auto lMutable = [](const auto&, auto) mutable {};
|
||||
|
||||
Q_STATIC_ASSERT((ArgsOf<decltype(lOneArg)>::count == 0));
|
||||
Q_STATIC_ASSERT((ArgsOf<decltype(lManyArgs)>::count == 0));
|
||||
Q_STATIC_ASSERT((ArgsOf<decltype(lMutable)>::count == 0));
|
||||
}
|
77
tests/auto/qtpromise/cpp14/tst_resolver_lambda_auto.cpp
Normal file
77
tests/auto/qtpromise/cpp14/tst_resolver_lambda_auto.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "../shared/utils.h"
|
||||
|
||||
#include <QtPromise>
|
||||
#include <QtTest>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class tst_cpp14_resolver_lambda_auto : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void resolverTwoAutoArgs();
|
||||
void resolverTwoAutoArgs_void();
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_cpp14_resolver_lambda_auto)
|
||||
#include "tst_resolver_lambda_auto.moc"
|
||||
|
||||
void tst_cpp14_resolver_lambda_auto::resolverTwoAutoArgs()
|
||||
{
|
||||
QtPromise::QPromise<int> p0{[](auto resolve, auto reject) {
|
||||
Q_UNUSED(reject)
|
||||
resolve(42);
|
||||
}};
|
||||
QtPromise::QPromise<int> p1{[](auto resolve, const auto& reject) {
|
||||
Q_UNUSED(reject)
|
||||
resolve(42);
|
||||
}};
|
||||
QtPromise::QPromise<int> p2{[](const auto& resolve, auto reject) {
|
||||
Q_UNUSED(reject)
|
||||
resolve(42);
|
||||
}};
|
||||
QtPromise::QPromise<int> p3{[](const auto& resolve, const auto& reject) {
|
||||
Q_UNUSED(reject)
|
||||
resolve(42);
|
||||
}};
|
||||
|
||||
for (const auto& p : {p0, p1, p2, p3}) {
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(waitForError(p, -1), -1);
|
||||
QCOMPARE(waitForValue(p, -1), 42);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_cpp14_resolver_lambda_auto::resolverTwoAutoArgs_void()
|
||||
{
|
||||
QtPromise::QPromise<void> p0{[](auto resolve, auto reject) {
|
||||
Q_UNUSED(reject)
|
||||
resolve();
|
||||
}};
|
||||
QtPromise::QPromise<void> p1{[](auto resolve, const auto& reject) {
|
||||
Q_UNUSED(reject)
|
||||
resolve();
|
||||
}};
|
||||
QtPromise::QPromise<void> p2{[](const auto& resolve, auto reject) {
|
||||
Q_UNUSED(reject)
|
||||
resolve();
|
||||
}};
|
||||
QtPromise::QPromise<void> p3{[](const auto& resolve, const auto& reject) {
|
||||
Q_UNUSED(reject)
|
||||
resolve();
|
||||
}};
|
||||
|
||||
for (const auto& p : {p0, p1, p2, p3}) {
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(waitForError(p, -1), -1);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
}
|
||||
}
|
13
tests/auto/qtpromise/deprecations/CMakeLists.txt
Normal file
13
tests/auto/qtpromise/deprecations/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
# Disable deprecation warnings since we are testing deprecated features.
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
add_compile_options(-Wno-deprecated-declarations)
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
add_compile_options(/wd4996)
|
||||
endif()
|
||||
|
||||
qtpromise_add_tests(deprecation
|
||||
SOURCES
|
||||
tst_helpers_qpromise.cpp
|
||||
tst_helpers_qpromiseall.cpp
|
||||
tst_qpromise_all.cpp
|
||||
)
|
275
tests/auto/qtpromise/deprecations/tst_helpers_qpromise.cpp
Normal file
275
tests/auto/qtpromise/deprecations/tst_helpers_qpromise.cpp
Normal file
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "../shared/data.h"
|
||||
#include "../shared/utils.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
#include <QtPromise>
|
||||
#include <QtTest>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class tst_deprecations_helpers_qpromise : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void value();
|
||||
void noValue();
|
||||
void moveRValue();
|
||||
void copyLValue();
|
||||
void qtSharedPtr();
|
||||
void stdSharedPtr();
|
||||
void typedPromise();
|
||||
void voidPromise();
|
||||
void typedFuture();
|
||||
void voidFuture();
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_deprecations_helpers_qpromise)
|
||||
#include "tst_helpers_qpromise.moc"
|
||||
|
||||
void tst_deprecations_helpers_qpromise::value()
|
||||
{
|
||||
int v0 = 42;
|
||||
const int v1 = 42;
|
||||
|
||||
auto p0 = QtPromise::qPromise(42);
|
||||
auto p1 = QtPromise::qPromise(v0);
|
||||
auto p2 = QtPromise::qPromise(v1);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p0), QtPromise::QPromise<int>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p1), QtPromise::QPromise<int>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p2), QtPromise::QPromise<int>>::value));
|
||||
|
||||
for (const auto& p : {p0, p1, p2}) {
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
}
|
||||
for (const auto& p : {p0, p1, p2}) {
|
||||
QCOMPARE(waitForValue(p, -1), 42);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromise::noValue()
|
||||
{
|
||||
auto p = QtPromise::qPromise();
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromise::moveRValue()
|
||||
{
|
||||
Data::logs().reset();
|
||||
|
||||
{
|
||||
auto p = QtPromise::qPromise(Data{42}).wait();
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<Data>>::value));
|
||||
}
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 1);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromise::copyLValue()
|
||||
{
|
||||
Data::logs().reset();
|
||||
|
||||
{
|
||||
Data value{42};
|
||||
auto p = QtPromise::qPromise(value).wait();
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<Data>>::value));
|
||||
}
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 1);
|
||||
QCOMPARE(Data::logs().copy, 1);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
}
|
||||
|
||||
// https://github.com/simonbrunel/qtpromise/issues/6
|
||||
void tst_deprecations_helpers_qpromise::qtSharedPtr()
|
||||
{
|
||||
using DataSPtr = QSharedPointer<Data>;
|
||||
|
||||
Data::logs().reset();
|
||||
|
||||
QWeakPointer<Data> wptr;
|
||||
|
||||
{
|
||||
auto sptr0 = DataSPtr::create(42);
|
||||
const DataSPtr sptr1 = sptr0;
|
||||
|
||||
auto p0 = QtPromise::qPromise(DataSPtr::create(42));
|
||||
auto p1 = QtPromise::qPromise(sptr0);
|
||||
auto p2 = QtPromise::qPromise(sptr1);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p0), QtPromise::QPromise<DataSPtr>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p1), QtPromise::QPromise<DataSPtr>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p2), QtPromise::QPromise<DataSPtr>>::value));
|
||||
|
||||
QCOMPARE(waitForValue(p1, DataSPtr{}), sptr0);
|
||||
QCOMPARE(waitForValue(p2, DataSPtr{}), sptr1);
|
||||
|
||||
wptr = sptr0;
|
||||
|
||||
QCOMPARE(wptr.isNull(), false);
|
||||
QCOMPARE(Data::logs().refs, 2);
|
||||
}
|
||||
|
||||
QCOMPARE(wptr.isNull(), true);
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 2);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
}
|
||||
|
||||
// https://github.com/simonbrunel/qtpromise/issues/6
|
||||
void tst_deprecations_helpers_qpromise::stdSharedPtr()
|
||||
{
|
||||
using DataSPtr = std::shared_ptr<Data>;
|
||||
|
||||
Data::logs().reset();
|
||||
|
||||
std::weak_ptr<Data> wptr;
|
||||
|
||||
{
|
||||
auto sptr0 = std::make_shared<Data>(42);
|
||||
const DataSPtr sptr1 = sptr0;
|
||||
|
||||
auto p0 = QtPromise::qPromise(std::make_shared<Data>(42));
|
||||
auto p1 = QtPromise::qPromise(sptr0);
|
||||
auto p2 = QtPromise::qPromise(sptr1);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p0), QtPromise::QPromise<DataSPtr>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p1), QtPromise::QPromise<DataSPtr>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p2), QtPromise::QPromise<DataSPtr>>::value));
|
||||
|
||||
QCOMPARE(waitForValue(p1, DataSPtr{}), sptr0);
|
||||
QCOMPARE(waitForValue(p2, DataSPtr{}), sptr1);
|
||||
|
||||
wptr = sptr0;
|
||||
|
||||
QCOMPARE(wptr.use_count(), 4l);
|
||||
QCOMPARE(Data::logs().refs, 2);
|
||||
}
|
||||
|
||||
QCOMPARE(wptr.use_count(), 0l);
|
||||
|
||||
QCOMPARE(Data::logs().ctor, 2);
|
||||
QCOMPARE(Data::logs().copy, 0);
|
||||
QCOMPARE(Data::logs().move, 0);
|
||||
QCOMPARE(Data::logs().refs, 0);
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromise::typedPromise()
|
||||
{
|
||||
auto resolver = [](const QtPromise::QPromiseResolve<int>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
resolve(42);
|
||||
});
|
||||
};
|
||||
|
||||
QtPromise::QPromise<int> v0{resolver};
|
||||
const QtPromise::QPromise<int> v1 = v0;
|
||||
|
||||
auto p0 = QtPromise::qPromise(QtPromise::QPromise<int>{resolver});
|
||||
auto p1 = QtPromise::qPromise(v0);
|
||||
auto p2 = QtPromise::qPromise(v1);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p0), QtPromise::QPromise<int>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p1), QtPromise::QPromise<int>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p2), QtPromise::QPromise<int>>::value));
|
||||
|
||||
for (const auto& promise : {p0, p1, p2}) {
|
||||
QCOMPARE(promise.isPending(), true);
|
||||
}
|
||||
for (const auto& promise : {p0, p1, p2}) {
|
||||
QCOMPARE(waitForValue(promise, -1), 42);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromise::voidPromise()
|
||||
{
|
||||
auto resolver = [](const QtPromise::QPromiseResolve<void>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
QtPromise::QPromise<void> v0{resolver};
|
||||
const QtPromise::QPromise<void> v1 = v0;
|
||||
|
||||
auto p0 = QtPromise::qPromise(QtPromise::QPromise<void>{resolver});
|
||||
auto p1 = QtPromise::qPromise(v0);
|
||||
auto p2 = QtPromise::qPromise(v1);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p0), QtPromise::QPromise<void>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p1), QtPromise::QPromise<void>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p2), QtPromise::QPromise<void>>::value));
|
||||
|
||||
for (const auto& promise : {p0, p1, p2}) {
|
||||
QCOMPARE(promise.isPending(), true);
|
||||
}
|
||||
for (const auto& promise : {p0, p1, p2}) {
|
||||
QCOMPARE(waitForValue(promise, -1, 42), 42);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromise::typedFuture()
|
||||
{
|
||||
auto fn = []() {
|
||||
return 42;
|
||||
};
|
||||
QFuture<int> v0 = QtConcurrent::run(fn);
|
||||
const QFuture<int> v1 = v0;
|
||||
|
||||
auto p0 = QtPromise::qPromise(QtConcurrent::run(fn));
|
||||
auto p1 = QtPromise::qPromise(v0);
|
||||
auto p2 = QtPromise::qPromise(v1);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p0), QtPromise::QPromise<int>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p1), QtPromise::QPromise<int>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p2), QtPromise::QPromise<int>>::value));
|
||||
|
||||
for (const auto& promise : {p0, p1, p2}) {
|
||||
QCOMPARE(promise.isPending(), true);
|
||||
}
|
||||
for (const auto& promise : {p0, p1, p2}) {
|
||||
QCOMPARE(waitForValue(promise, -1), 42);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromise::voidFuture()
|
||||
{
|
||||
auto fn = []() {};
|
||||
QFuture<void> v0 = QtConcurrent::run(fn);
|
||||
const QFuture<void> v1 = v0;
|
||||
|
||||
auto p0 = QtPromise::qPromise(QtConcurrent::run(fn));
|
||||
auto p1 = QtPromise::qPromise(v0);
|
||||
auto p2 = QtPromise::qPromise(v1);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p0), QtPromise::QPromise<void>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p1), QtPromise::QPromise<void>>::value));
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p2), QtPromise::QPromise<void>>::value));
|
||||
|
||||
for (const auto& promise : {p0, p1, p2}) {
|
||||
QCOMPARE(promise.isPending(), true);
|
||||
}
|
||||
for (const auto& promise : {p0, p1, p2}) {
|
||||
QCOMPARE(waitForValue(promise, -1, 42), 42);
|
||||
}
|
||||
}
|
218
tests/auto/qtpromise/deprecations/tst_helpers_qpromiseall.cpp
Normal file
218
tests/auto/qtpromise/deprecations/tst_helpers_qpromiseall.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "../shared/utils.h"
|
||||
|
||||
#include <QtPromise>
|
||||
#include <QtTest>
|
||||
|
||||
class tst_deprecations_helpers_qpromiseall : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void emptySequence();
|
||||
void emptySequence_void();
|
||||
void allPromisesSucceed();
|
||||
void allPromisesSucceed_void();
|
||||
void atLeastOnePromiseReject();
|
||||
void atLeastOnePromiseReject_void();
|
||||
void preserveOrder();
|
||||
void sequenceTypes();
|
||||
void sequenceTypes_void();
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_deprecations_helpers_qpromiseall)
|
||||
#include "tst_helpers_qpromiseall.moc"
|
||||
|
||||
namespace {
|
||||
|
||||
template<class Sequence>
|
||||
struct SequenceTester
|
||||
{
|
||||
Q_STATIC_ASSERT((std::is_same<typename Sequence::value_type, QtPromise::QPromise<int>>::value));
|
||||
|
||||
static void exec()
|
||||
{
|
||||
Sequence promises{QtPromise::resolve(42), QtPromise::resolve(43), QtPromise::resolve(44)};
|
||||
|
||||
promises.push_back(QtPromise::resolve(45));
|
||||
promises.insert(++promises.begin(), QtPromise::resolve(46));
|
||||
promises.pop_back();
|
||||
|
||||
auto p = qPromiseAll(promises);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, QVector<int>{}), (QVector<int>{42, 46, 43, 44}));
|
||||
}
|
||||
};
|
||||
|
||||
template<template<typename, typename...> class Sequence, typename... Args>
|
||||
struct SequenceTester<Sequence<QtPromise::QPromise<void>, Args...>>
|
||||
{
|
||||
static void exec()
|
||||
{
|
||||
Sequence<QtPromise::QPromise<void>, Args...> promises{QtPromise::resolve(),
|
||||
QtPromise::resolve(),
|
||||
QtPromise::resolve()};
|
||||
|
||||
promises.push_back(QtPromise::resolve());
|
||||
promises.insert(++promises.begin(), QtPromise::resolve());
|
||||
promises.pop_back();
|
||||
|
||||
auto p = qPromiseAll(promises);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void tst_deprecations_helpers_qpromiseall::emptySequence()
|
||||
{
|
||||
auto p = qPromiseAll(QVector<QtPromise::QPromise<int>>());
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(waitForValue(p, QVector<int>{}), QVector<int>{});
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromiseall::emptySequence_void()
|
||||
{
|
||||
auto p = qPromiseAll(QVector<QtPromise::QPromise<void>>());
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromiseall::allPromisesSucceed()
|
||||
{
|
||||
auto p0 = QtPromise::resolve(42);
|
||||
auto p1 = QtPromise::resolve(44);
|
||||
auto p2 = QtPromise::QPromise<int>{[](const QtPromise::QPromiseResolve<int>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
resolve(43);
|
||||
});
|
||||
}};
|
||||
|
||||
auto p = QtPromise::qPromiseAll(QVector<QtPromise::QPromise<int>>{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, QVector<int>{}), (QVector<int>{42, 43, 44}));
|
||||
QCOMPARE(p2.isFulfilled(), true);
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromiseall::allPromisesSucceed_void()
|
||||
{
|
||||
auto p0 = QtPromise::resolve();
|
||||
auto p1 = QtPromise::resolve();
|
||||
auto p2 = QtPromise::QPromise<void>{[](const QtPromise::QPromiseResolve<void>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
resolve();
|
||||
});
|
||||
}};
|
||||
|
||||
auto p = QtPromise::qPromiseAll(QVector<QtPromise::QPromise<void>>{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
QCOMPARE(p2.isFulfilled(), true);
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromiseall::atLeastOnePromiseReject()
|
||||
{
|
||||
auto p0 = QtPromise::resolve(42);
|
||||
auto p1 = QtPromise::resolve(44);
|
||||
auto p2 = QtPromise::QPromise<int>{
|
||||
[](const QtPromise::QPromiseResolve<int>&, const QtPromise::QPromiseReject<int>& reject) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
reject(QString{"foo"});
|
||||
});
|
||||
}};
|
||||
|
||||
auto p = QtPromise::qPromiseAll(QVector<QtPromise::QPromise<int>>{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForError(p, QString{}), QString{"foo"});
|
||||
QCOMPARE(p2.isRejected(), true);
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromiseall::atLeastOnePromiseReject_void()
|
||||
{
|
||||
auto p0 = QtPromise::resolve();
|
||||
auto p1 = QtPromise::resolve();
|
||||
auto p2 = QtPromise::QPromise<void>{
|
||||
[](const QtPromise::QPromiseResolve<void>&, const QtPromise::QPromiseReject<void>& reject) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
reject(QString{"foo"});
|
||||
});
|
||||
}};
|
||||
|
||||
auto p = QtPromise::qPromiseAll(QVector<QtPromise::QPromise<void>>{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForError(p, QString{}), QString{"foo"});
|
||||
QCOMPARE(p2.isRejected(), true);
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromiseall::preserveOrder()
|
||||
{
|
||||
auto p0 = QtPromise::resolve(42).delay(500);
|
||||
auto p1 = QtPromise::resolve(43).delay(100);
|
||||
auto p2 = QtPromise::resolve(44).delay(250);
|
||||
|
||||
auto p = QtPromise::qPromiseAll(QVector<QtPromise::QPromise<int>>{p0, p1, p2});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
QCOMPARE(p0.isPending(), true);
|
||||
QCOMPARE(p1.isPending(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, QVector<int>{}), (QVector<int>{42, 43, 44}));
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isFulfilled(), true);
|
||||
}
|
||||
|
||||
// QVector::push_back/append isn't supported since it requires a default
|
||||
// constructor (see https://github.com/simonbrunel/qtpromise/issues/3)
|
||||
|
||||
void tst_deprecations_helpers_qpromiseall::sequenceTypes()
|
||||
{
|
||||
SequenceTester<QList<QtPromise::QPromise<int>>>::exec();
|
||||
// SequenceTester<QVector<QtPromise::QPromise<int>>>::exec();
|
||||
SequenceTester<std::list<QtPromise::QPromise<int>>>::exec();
|
||||
SequenceTester<std::vector<QtPromise::QPromise<int>>>::exec();
|
||||
}
|
||||
|
||||
void tst_deprecations_helpers_qpromiseall::sequenceTypes_void()
|
||||
{
|
||||
SequenceTester<QList<QtPromise::QPromise<void>>>::exec();
|
||||
// SequenceTester<QVector<QtPromise::QPromise<void>>>::exec();
|
||||
SequenceTester<std::list<QtPromise::QPromise<void>>>::exec();
|
||||
SequenceTester<std::vector<QtPromise::QPromise<void>>>::exec();
|
||||
}
|
227
tests/auto/qtpromise/deprecations/tst_qpromise_all.cpp
Normal file
227
tests/auto/qtpromise/deprecations/tst_qpromise_all.cpp
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "../shared/utils.h"
|
||||
|
||||
#include <QtPromise>
|
||||
#include <QtTest>
|
||||
|
||||
class tst_deprecations_qpromise_all : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void emptySequence();
|
||||
void emptySequence_void();
|
||||
void allPromisesSucceed();
|
||||
void allPromisesSucceed_void();
|
||||
void atLeastOnePromiseReject();
|
||||
void atLeastOnePromiseReject_void();
|
||||
void preserveOrder();
|
||||
void sequenceTypes();
|
||||
void sequenceTypes_void();
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_deprecations_qpromise_all)
|
||||
#include "tst_qpromise_all.moc"
|
||||
|
||||
namespace {
|
||||
|
||||
template<class Sequence>
|
||||
struct SequenceTester
|
||||
{
|
||||
Q_STATIC_ASSERT((std::is_same<typename Sequence::value_type, QtPromise::QPromise<int>>::value));
|
||||
|
||||
static void exec()
|
||||
{
|
||||
Sequence promises{QtPromise::resolve(42), QtPromise::resolve(43), QtPromise::resolve(44)};
|
||||
|
||||
promises.push_back(QtPromise::resolve(45));
|
||||
promises.insert(++promises.begin(), QtPromise::resolve(46));
|
||||
promises.pop_back();
|
||||
|
||||
auto p = QtPromise::QPromise<int>::all(promises);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, QVector<int>{}), (QVector<int>{42, 46, 43, 44}));
|
||||
}
|
||||
};
|
||||
|
||||
template<template<typename, typename...> class Sequence, typename... Args>
|
||||
struct SequenceTester<Sequence<QtPromise::QPromise<void>, Args...>>
|
||||
{
|
||||
static void exec()
|
||||
{
|
||||
Sequence<QtPromise::QPromise<void>, Args...> promises{QtPromise::resolve(),
|
||||
QtPromise::resolve(),
|
||||
QtPromise::resolve()};
|
||||
|
||||
promises.push_back(QtPromise::resolve());
|
||||
promises.insert(++promises.begin(), QtPromise::resolve());
|
||||
promises.pop_back();
|
||||
|
||||
auto p = QtPromise::QPromise<void>::all(promises);
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void tst_deprecations_qpromise_all::emptySequence()
|
||||
{
|
||||
auto p = QtPromise::QPromise<int>::all(QVector<QtPromise::QPromise<int>>{});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(waitForValue(p, QVector<int>{}), QVector<int>{});
|
||||
}
|
||||
|
||||
void tst_deprecations_qpromise_all::emptySequence_void()
|
||||
{
|
||||
auto p = QtPromise::QPromise<void>::all(QVector<QtPromise::QPromise<void>>{});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
}
|
||||
|
||||
void tst_deprecations_qpromise_all::allPromisesSucceed()
|
||||
{
|
||||
auto p0 = QtPromise::resolve(42);
|
||||
auto p1 = QtPromise::resolve(44);
|
||||
auto p2 = QtPromise::QPromise<int>{[](const QtPromise::QPromiseResolve<int>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
resolve(43);
|
||||
});
|
||||
}};
|
||||
|
||||
auto p = QtPromise::QPromise<int>::all(QVector<QtPromise::QPromise<int>>{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, QVector<int>{}), (QVector<int>{42, 43, 44}));
|
||||
QCOMPARE(p2.isFulfilled(), true);
|
||||
}
|
||||
|
||||
void tst_deprecations_qpromise_all::allPromisesSucceed_void()
|
||||
{
|
||||
auto p0 = QtPromise::resolve();
|
||||
auto p1 = QtPromise::resolve();
|
||||
auto p2 = QtPromise::QPromise<void>{[](const QtPromise::QPromiseResolve<void>& resolve) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
resolve();
|
||||
});
|
||||
}};
|
||||
|
||||
auto p = QtPromise::QPromise<void>::all(QVector<QtPromise::QPromise<void>>{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, -1, 42), 42);
|
||||
QCOMPARE(p2.isFulfilled(), true);
|
||||
}
|
||||
|
||||
void tst_deprecations_qpromise_all::atLeastOnePromiseReject()
|
||||
{
|
||||
auto p0 = QtPromise::resolve(42);
|
||||
auto p1 = QtPromise::resolve(44);
|
||||
auto p2 = QtPromise::QPromise<int>{
|
||||
[](const QtPromise::QPromiseResolve<int>&, const QtPromise::QPromiseReject<int>& reject) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
reject(QString{"foo"});
|
||||
});
|
||||
}};
|
||||
|
||||
auto p = QtPromise::QPromise<int>::all(QVector<QtPromise::QPromise<int>>{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForError(p, QString{}), QString{"foo"});
|
||||
QCOMPARE(p2.isRejected(), true);
|
||||
}
|
||||
|
||||
void tst_deprecations_qpromise_all::atLeastOnePromiseReject_void()
|
||||
{
|
||||
auto p0 = QtPromise::resolve();
|
||||
auto p1 = QtPromise::resolve();
|
||||
auto p2 = QtPromise::QPromise<void>{
|
||||
[](const QtPromise::QPromiseResolve<void>&, const QtPromise::QPromiseReject<void>& reject) {
|
||||
QtPromisePrivate::qtpromise_defer([=]() {
|
||||
reject(QString{"foo"});
|
||||
});
|
||||
}};
|
||||
|
||||
auto p = QtPromise::QPromise<void>::all(QVector<QtPromise::QPromise<void>>{p0, p2, p1});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForError(p, QString{}), QString{"foo"});
|
||||
QCOMPARE(p2.isRejected(), true);
|
||||
}
|
||||
|
||||
void tst_deprecations_qpromise_all::preserveOrder()
|
||||
{
|
||||
auto p0 = QtPromise::resolve(42).delay(500);
|
||||
auto p1 = QtPromise::resolve(43).delay(100);
|
||||
auto p2 = QtPromise::resolve(44).delay(250);
|
||||
|
||||
auto p = QtPromise::QPromise<int>::all(QVector<QtPromise::QPromise<int>>{p0, p1, p2});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<QVector<int>>>::value));
|
||||
|
||||
QCOMPARE(p0.isPending(), true);
|
||||
QCOMPARE(p1.isPending(), true);
|
||||
QCOMPARE(p2.isPending(), true);
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForValue(p, QVector<int>{}), (QVector<int>{42, 43, 44}));
|
||||
QCOMPARE(p0.isFulfilled(), true);
|
||||
QCOMPARE(p1.isFulfilled(), true);
|
||||
QCOMPARE(p2.isFulfilled(), true);
|
||||
}
|
||||
|
||||
// QVector::push_back/append isn't supported since it requires a default
|
||||
// constructor (see https://github.com/simonbrunel/qtpromise/issues/3)
|
||||
|
||||
void tst_deprecations_qpromise_all::sequenceTypes()
|
||||
{
|
||||
SequenceTester<QList<QtPromise::QPromise<int>>>::exec();
|
||||
// SequenceTester<QVector<QtPromise::QPromise<int>>>::exec();
|
||||
SequenceTester<std::list<QtPromise::QPromise<int>>>::exec();
|
||||
SequenceTester<std::vector<QtPromise::QPromise<int>>>::exec();
|
||||
}
|
||||
|
||||
void tst_deprecations_qpromise_all::sequenceTypes_void()
|
||||
{
|
||||
SequenceTester<QList<QtPromise::QPromise<void>>>::exec();
|
||||
// SequenceTester<QVector<QtPromise::QPromise<void>>>::exec();
|
||||
SequenceTester<std::list<QtPromise::QPromise<void>>>::exec();
|
||||
SequenceTester<std::vector<QtPromise::QPromise<void>>>::exec();
|
||||
}
|
4
tests/auto/qtpromise/exceptions/CMakeLists.txt
Normal file
4
tests/auto/qtpromise/exceptions/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
qtpromise_add_test(exceptions
|
||||
SOURCES
|
||||
tst_exceptions.cpp
|
||||
)
|
68
tests/auto/qtpromise/exceptions/tst_exceptions.cpp
Normal file
68
tests/auto/qtpromise/exceptions/tst_exceptions.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "../shared/utils.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
#include <QtPromise>
|
||||
#include <QtTest>
|
||||
|
||||
class tst_exceptions : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void canceled();
|
||||
void context();
|
||||
void conversion();
|
||||
void timeout();
|
||||
void undefined();
|
||||
|
||||
}; // class tst_exceptions
|
||||
|
||||
QTEST_MAIN(tst_exceptions)
|
||||
#include "tst_exceptions.moc"
|
||||
|
||||
namespace {
|
||||
|
||||
template<class E>
|
||||
void verify()
|
||||
{
|
||||
auto p = QtPromise::resolve(QtConcurrent::run([]() {
|
||||
throw E();
|
||||
}));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
QCOMPARE(waitForRejected<E>(p), true);
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void tst_exceptions::canceled()
|
||||
{
|
||||
verify<QtPromise::QPromiseCanceledException>();
|
||||
}
|
||||
|
||||
void tst_exceptions::context()
|
||||
{
|
||||
verify<QtPromise::QPromiseContextException>();
|
||||
}
|
||||
|
||||
void tst_exceptions::conversion()
|
||||
{
|
||||
verify<QtPromise::QPromiseConversionException>();
|
||||
}
|
||||
|
||||
void tst_exceptions::timeout()
|
||||
{
|
||||
verify<QtPromise::QPromiseTimeoutException>();
|
||||
}
|
||||
|
||||
void tst_exceptions::undefined()
|
||||
{
|
||||
verify<QtPromise::QPromiseUndefinedException>();
|
||||
}
|
4
tests/auto/qtpromise/future/CMakeLists.txt
Normal file
4
tests/auto/qtpromise/future/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
qtpromise_add_test(future
|
||||
SOURCES
|
||||
tst_future.cpp
|
||||
)
|
366
tests/auto/qtpromise/future/tst_future.cpp
Normal file
366
tests/auto/qtpromise/future/tst_future.cpp
Normal file
@ -0,0 +1,366 @@
|
||||
/*
|
||||
* Copyright (c) Simon Brunel, https://github.com/simonbrunel
|
||||
*
|
||||
* This source code is licensed under the MIT license found in
|
||||
* the LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <QtConcurrent>
|
||||
#include <QtPromise>
|
||||
#include <QtTest>
|
||||
|
||||
class tst_future : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void fulfilled();
|
||||
void fulfilled_void();
|
||||
void rejected();
|
||||
void rejected_void();
|
||||
void unhandled();
|
||||
void unhandled_void();
|
||||
void canceled();
|
||||
void canceled_void();
|
||||
void canceledFromThread();
|
||||
void then();
|
||||
void then_void();
|
||||
void fail();
|
||||
void fail_void();
|
||||
void finally();
|
||||
void finallyRejected();
|
||||
|
||||
}; // class tst_future
|
||||
|
||||
class MyException : public QException
|
||||
{
|
||||
public:
|
||||
MyException(const QString& error) : m_error{error} { }
|
||||
|
||||
const QString& error() const { return m_error; }
|
||||
|
||||
void raise() const { throw *this; }
|
||||
MyException* clone() const { return new MyException{*this}; }
|
||||
|
||||
private:
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_future)
|
||||
#include "tst_future.moc"
|
||||
|
||||
void tst_future::fulfilled()
|
||||
{
|
||||
int result = -1;
|
||||
auto p = QtPromise::resolve(QtConcurrent::run([]() {
|
||||
return 42;
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<int>>::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.then([&](int res) {
|
||||
result = res;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(result, 42);
|
||||
}
|
||||
|
||||
void tst_future::fulfilled_void()
|
||||
{
|
||||
int result = -1;
|
||||
auto p = QtPromise::resolve(QtConcurrent::run([]() {}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.then([&]() {
|
||||
result = 42;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isFulfilled(), true);
|
||||
QCOMPARE(result, 42);
|
||||
}
|
||||
|
||||
void tst_future::rejected()
|
||||
{
|
||||
QString error;
|
||||
auto p = QtPromise::resolve(QtConcurrent::run([]() {
|
||||
throw MyException{"foo"};
|
||||
return 42;
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<int>>::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const MyException& e) {
|
||||
error = e.error();
|
||||
return -1;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString{"foo"});
|
||||
}
|
||||
|
||||
void tst_future::rejected_void()
|
||||
{
|
||||
QString error;
|
||||
auto p = QtPromise::resolve(QtConcurrent::run([]() {
|
||||
throw MyException{"foo"};
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const MyException& e) {
|
||||
error = e.error();
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString{"foo"});
|
||||
}
|
||||
|
||||
void tst_future::unhandled()
|
||||
{
|
||||
QString error;
|
||||
auto p = QtPromise::resolve(QtConcurrent::run([]() {
|
||||
throw QString{"foo"};
|
||||
return 42;
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<int>>::value));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QString& err) {
|
||||
error += err;
|
||||
return -1;
|
||||
})
|
||||
.fail([&](const QUnhandledException&) {
|
||||
error += "bar";
|
||||
return -1;
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString{"bar"});
|
||||
}
|
||||
|
||||
void tst_future::unhandled_void()
|
||||
{
|
||||
QString error;
|
||||
auto p = QtPromise::resolve(QtConcurrent::run([]() {
|
||||
throw QString{"foo"};
|
||||
}));
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(p), QtPromise::QPromise<void>>::value));
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QString& err) {
|
||||
error += err;
|
||||
})
|
||||
.fail([&](const QUnhandledException&) {
|
||||
error += "bar";
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString{"bar"});
|
||||
}
|
||||
|
||||
void tst_future::canceled()
|
||||
{
|
||||
QString error;
|
||||
auto p = QtPromise::resolve(QFuture<int>()); // Constructs an empty, canceled future.
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QtPromise::QPromiseCanceledException&) {
|
||||
error = "canceled";
|
||||
return -1;
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString{"canceled"});
|
||||
}
|
||||
|
||||
void tst_future::canceled_void()
|
||||
{
|
||||
QString error;
|
||||
auto p = QtPromise::resolve(QFuture<void>()); // Constructs an empty, canceled future.
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QtPromise::QPromiseCanceledException&) {
|
||||
error = "canceled";
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString{"canceled"});
|
||||
}
|
||||
|
||||
void tst_future::canceledFromThread()
|
||||
{
|
||||
QString error;
|
||||
auto p = QtPromise::resolve(QtConcurrent::run([]() {
|
||||
throw QtPromise::QPromiseCanceledException{};
|
||||
}));
|
||||
|
||||
QCOMPARE(p.isPending(), true);
|
||||
|
||||
p.fail([&](const QtPromise::QPromiseCanceledException&) {
|
||||
error = "bar";
|
||||
}).wait();
|
||||
|
||||
QCOMPARE(p.isRejected(), true);
|
||||
QCOMPARE(error, QString{"bar"});
|
||||
}
|
||||
|
||||
void tst_future::then()
|
||||
{
|
||||
QString result;
|
||||
auto input = QtPromise::resolve(42);
|
||||
auto output = input.then([](int res) {
|
||||
return QtConcurrent::run([=]() {
|
||||
return QString{"foo%1"}.arg(res);
|
||||
});
|
||||
});
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
output
|
||||
.then([&](const QString& res) {
|
||||
result = res;
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(output.isFulfilled(), true);
|
||||
QCOMPARE(result, QString{"foo42"});
|
||||
}
|
||||
|
||||
void tst_future::then_void()
|
||||
{
|
||||
QString result;
|
||||
auto input = QtPromise::resolve();
|
||||
auto output = input.then([&]() {
|
||||
return QtConcurrent::run([&]() {
|
||||
result = "foo";
|
||||
});
|
||||
});
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
output
|
||||
.then([&]() {
|
||||
result += "bar";
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(result, QString{"foobar"});
|
||||
}
|
||||
|
||||
void tst_future::fail()
|
||||
{
|
||||
QString result;
|
||||
auto input = QtPromise::QPromise<QString>::reject(MyException{"bar"});
|
||||
auto output = input.fail([](const MyException& e) {
|
||||
return QtConcurrent::run(
|
||||
[](const QString& error) {
|
||||
return QString{"foo%1"}.arg(error);
|
||||
},
|
||||
e.error());
|
||||
});
|
||||
|
||||
QCOMPARE(input.isRejected(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
output
|
||||
.then([&](const QString& res) {
|
||||
result = res;
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(output.isFulfilled(), true);
|
||||
QCOMPARE(result, QString{"foobar"});
|
||||
}
|
||||
|
||||
void tst_future::fail_void()
|
||||
{
|
||||
QString result;
|
||||
auto input = QtPromise::QPromise<void>::reject(MyException{"bar"});
|
||||
auto output = input.fail([&](const MyException& e) {
|
||||
return QtConcurrent::run(
|
||||
[&](const QString& error) {
|
||||
result = error;
|
||||
},
|
||||
e.error());
|
||||
});
|
||||
|
||||
QCOMPARE(input.isRejected(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
output
|
||||
.then([&]() {
|
||||
result = result.prepend("foo");
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(output.isFulfilled(), true);
|
||||
QCOMPARE(result, QString{"foobar"});
|
||||
}
|
||||
|
||||
void tst_future::finally()
|
||||
{
|
||||
auto input = QtPromise::resolve(42);
|
||||
auto output = input.finally([]() {
|
||||
return QtConcurrent::run([]() {
|
||||
return QString{"foo"};
|
||||
});
|
||||
});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(output), QtPromise::QPromise<int>>::value));
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
int value = -1;
|
||||
output
|
||||
.then([&](int res) {
|
||||
value = res;
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(output.isFulfilled(), true);
|
||||
QCOMPARE(value, 42);
|
||||
}
|
||||
|
||||
void tst_future::finallyRejected()
|
||||
{
|
||||
auto input = QtPromise::resolve(42);
|
||||
auto output = input.finally([]() {
|
||||
return QtConcurrent::run([]() {
|
||||
throw MyException{"foo"};
|
||||
});
|
||||
});
|
||||
|
||||
Q_STATIC_ASSERT((std::is_same<decltype(output), QtPromise::QPromise<int>>::value));
|
||||
|
||||
QCOMPARE(input.isFulfilled(), true);
|
||||
QCOMPARE(output.isPending(), true);
|
||||
|
||||
QString error;
|
||||
output
|
||||
.fail([&](const MyException& e) {
|
||||
error = e.error();
|
||||
return -1;
|
||||
})
|
||||
.wait();
|
||||
|
||||
QCOMPARE(output.isRejected(), true);
|
||||
QCOMPARE(error, QString{"foo"});
|
||||
}
|
12
tests/auto/qtpromise/helpers/CMakeLists.txt
Normal file
12
tests/auto/qtpromise/helpers/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
qtpromise_add_tests(helpers
|
||||
SOURCES
|
||||
tst_all.cpp
|
||||
tst_attempt.cpp
|
||||
tst_connect.cpp
|
||||
tst_each.cpp
|
||||
tst_filter.cpp
|
||||
tst_map.cpp
|
||||
tst_reduce.cpp
|
||||
tst_reject.cpp
|
||||
tst_resolve.cpp
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user