Compare commits

...

81 Commits

Author SHA1 Message Date
Egor Krugletsov
1a905cbd4f
Use std::shared_ptr for PromiseValue<T>::m_data instead of QSharedPoitner<T> (#60)
In optimized builds (such as -O2) GCC emits "maybe-uninitialized" warning for QSharedPointer<T>::d. This happens only for optimized builds because that is when GCC tracks lifetimes[1]. In a project with lots of QtPromise uses this produces a heap of bogus warnings. This warning has been previously reported to a Qt team, but they're confident it is a compiler bug[2][3]. However, this is a second time and issue raised with QSharedPointer and warnings to it. The first time it was addressed here: https://github.com/simonbrunel/qtpromise/pull/34.

[1] https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wmaybe-uninitialized
[2] https://bugreports.qt.io/browse/QTBUG-14637
[3] https://bugreports.qt.io/browse/QTBUG-77641
2024-08-24 09:01:08 +02:00
Simon Brunel
14031392ac Use GitHub actions instead of Travis 2024-08-20 20:59:47 +02:00
Simon Brunel
f382ad25fc Bump version to 0.7.0 2023-03-05 15:13:39 +01:00
Simon Brunel
ac9b936959 Upgrade npm dependencies 2023-03-05 15:13:18 +01:00
Simon Brunel
21faa67b58 Configure AppVeyor to run Qt 6.x tests 2022-06-30 23:17:02 +02:00
Simon Brunel
9f01c130cd Add support for Qt 6 2022-06-26 18:10:20 +02:00
Dmitriy Purgin
461f09bef8
Remove QPromiseError; fix include order to compile on GCC 9 (#45)
Co-authored-by: Ryan Henderson <the.unkle.george@gmail.com>
2021-03-20 09:25:41 +01:00
Dmitriy Purgin
bac405febf
Fix a typo in then.md (#43) 2021-01-23 13:41:38 +01:00
Dmitriy Purgin
0c3955cca5
Implement QPromise<T>::convert<U>() (#41)
Converts the resolved value of `QPromise<T>` to the type `U`. Depending on types `T` and `U`, it performs a static cast, calls a converting constructor or tries to convert using `QVariant`.
2020-11-22 17:26:06 +01:00
Sören Sprößig
60b36e7a70
Fix Clang/GCC warnings and update compile options (#39) 2020-10-26 21:51:52 +01:00
Simon Brunel
f7639e921e Bump version to 0.6.0 2020-10-01 23:00:38 +02:00
Simon Brunel
1752255e7b Upgrade npm dependencies for security fixes 2020-10-01 22:54:58 +02:00
Simon Brunel
13b4bb65d2 Fix CMake FetchContent documentation GIT_TAG 2020-10-01 22:48:20 +02:00
Peter Würtz
b7ecd95b04
Fix deprecations and compile warnings (#34)
- Use std::invoke_result, fallback to std::result_of on C++11/14.
- Use RVO instead of std::move (GCC warning: redundant-move).
- Replace QSharedPointer with std::shared_ptr (GCC warning: unititialized value).
- QLinkedList is deprecated in Qt 5.15.
2020-04-02 13:39:03 +02:00
Simon Brunel
88289a7635 Fix npm security warning (docs only) 2020-03-22 17:50:16 +01:00
Simon Brunel
6deec9f51f Fix support for auto args in constructor callbacks
Broken since 78417b5, this bug creates an infinite recursion at runtime while trying to resolve the QPromise<T> template constructor if the given callback is either an invalid function or a valid callback but using auto args (C++14).

Related warning: C4717: recursive on all control paths, function will cause runtime stack overflow.
2020-03-22 17:50:16 +01:00
Simon Brunel
d43657fbd5 Use clang-format for code style consistency
Based on the WebKit preset and following 'most' of the Qt guidelines, except a few rules that work better for promise continuation lambdas. Requires clang-format 11.
2020-03-22 17:50:16 +01:00
Dmitriy Purgin
b99e468c84
More verbose description of throwing an exception from a QtConcurrent thread (#31) 2020-02-18 22:31:41 +01:00
Simon Brunel
be5455a8c8 Use C++11 curly braces initialization 2020-02-17 20:02:26 +01:00
Simon Brunel
0bfdddd887 Use QSharedPointer::create and std::make_shared 2020-02-16 11:42:57 +01:00
Simon Brunel
2c1e631aed Include Qt classes by module and remove unnecessary comments 2020-02-11 22:25:08 +01:00
Simon Brunel
d5a82518f9 Simplify tests directory structure 2020-02-11 22:13:57 +01:00
Dmitriy Purgin
1ad99391a3
std::chrono overloads for .timeout() and .delay() (#30)
Add convenience overloads accepting durations from the C++ Standard Library.
2020-02-11 22:07:05 +01:00
Simon Brunel
78417b5813 Fix support for std::function as continuation handler 2020-02-06 22:52:07 +01:00
Simon Brunel
58738a5604 Simplify license notice in source files 2020-02-06 22:33:19 +01:00
Simon Brunel
7ee51de987
Fix Google Analytics plugin docs config 2020-01-24 04:58:32 +01:00
Simon Brunel
6639ea52db Migrate to CMake to build the entire project 2020-01-12 21:04:43 +01:00
Simon Brunel
7f9013a878 Add CMake support and enhance the getting started docs
- Add a root CMakeLists.txt defining an INTERFACE target.
- Add CMake, qpm, Git and download installation instructions.
- Add CMake and qmake integration instructions.
- Remove smooth scroll when navigating the docs.
- Add links validation to `npm run docs:link`.
- Reorganize the sidebar API Reference section.
2020-01-09 20:45:04 +01:00
Simon Brunel
cc29ef3512 Fix docs C++ snippet for the .fail() args
- Fix the wrong lambda format (missing `[]`).
- Make consistent argument naming (`error`).
- Use `const &` when appropriated.
2020-01-04 13:25:19 +01:00
Simon Brunel
3c1461b8d0 Enhance the documentation and add markdown lint
- Add npm package.json to make easier maintaining dependencies.
- Make markdown files consistent using remark.
- Wrap markdown lines to 100 characters.
- Better README.md layout and visual.
- Enable VuePress landing/home page.
- Upgrade to latest VuePress version.
- Add link to the new Qt Marketplace.
2019-12-21 10:27:26 +01:00
Simon Brunel
815dc443b9 Add MIT license header to all source files 2019-12-20 21:42:19 +01:00
Simon Brunel
67837827b1 Bump version to 0.5.0 2019-03-31 22:14:45 +02:00
Simon Brunel
e3f0f054af Implement QPromise<Sequence<T>>::reduce(reducer, initialValue)
Iterates over all the promise values (i.e. `Sequence<T>`) and reduces the sequence to a single value using the given `reducer` function and an optional `initialValue`. Also provide a static helper to directly reduce values (`QtPromise::reduce(values, reducer, initialValue)`).
2019-03-31 15:16:08 +02:00
Simon Brunel
cbf4cc7867 Rename qPromiseAll() helper to QtPromise::all()
For consistency with other helpers, deprecate `qPromiseAll()` in favor of `QtPromise::all()`.
2019-03-25 14:45:51 +01:00
Simon Brunel
963ec621e1 Rename qPromise() helper to QtPromise::resolve()
For consistency with other helpers, deprecate `qPromise()` in favor of `QtPromise::resolve()` but also add support for calling this helper with lvalue. Add extra unit tests to make sure that rvalue is not copied.
2019-03-25 14:45:41 +01:00
Simon Brunel
1f30224578 Add support for creating promises from Qt signals (#25)
Introduce a new `QtPromise::connect()` helper that allows to create a promise resolved from a single signal and optionally, rejected by another one (from a different object or not). The promise type is determined by the type of the first signal argument (other arguments are currently ignored). A `QPromise<void>` is returned if the resolve signal doesn't provide any argument.

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

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

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

34
.appveyor.yml Normal file
View 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
View 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
View 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
View File

@ -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
View File

@ -0,0 +1,6 @@
plugins:
- frontmatter
- validate-links
- preset-lint-recommended
- preset-lint-markdown-style-guide
- [lint-maximum-line-length, 100]

View File

@ -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
View 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
View File

@ -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.

417
README.md
View File

@ -1,407 +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
[![qpm](https://img.shields.io/github/release/simonbrunel/qtpromise.svg?style=flat-square&label=qpm&colorB=4CAF50)](http://www.qpm.io/packages/com.github.simonbrunel.qtpromise/index.html) [![Travis](https://img.shields.io/travis/simonbrunel/qtpromise.svg?style=flat-square)](https://travis-ci.org/simonbrunel/qtpromise) [![coverage](https://img.shields.io/codecov/c/github/simonbrunel/qtpromise.svg?style=flat-square)](https://codecov.io/gh/simonbrunel/qtpromise)
<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-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).

View 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
View 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'
]
}
]
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,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

View 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

View File

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

30
docs/README.md Normal file
View 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).

View 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)

View 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?)
:::

View 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.
})
```

View 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
});
```

View 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!
});
```

View 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!
})
```

View 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).

View 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) {
// {...}
});
```

View 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) {
// {...}
});
```

View 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.

View 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)

View 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)

View 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)

View 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)

View 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)).

View 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([]() {
// { ... }
})
```

View 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.
:::

View 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
});
```

View 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)

View 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)

View 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)

View 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.

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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)

View 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)

View File

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

View File

@ -0,0 +1,28 @@
---
title: ::resolve (static)
---
# QPromise::resolve (static)
*Since: 0.1.0*
```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)

View 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.

View 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.

View 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.

View 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!
});
```

View 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
```

View 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) {
// {...}
});
```

View 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.

View 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.
:::

View File

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

File diff suppressed because it is too large Load Diff

18
package.json Normal file
View 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"
}
}

View File

@ -1 +0,0 @@
include(../../qtpromise.pri)

View File

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

View File

@ -1,12 +0,0 @@
TEMPLATE = subdirs
SUBDIRS = \
tests
_qt_creator_ {
SUBDIRS += src
}
OTHER_FILES = \
package/features/*.prf \
include/* \
qtpromise.pri

View File

@ -1,90 +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>
inline QPromise<T> finally(THandler handler) const;
template<typename THandler>
inline QPromise<T> tap(THandler handler) const;
template<typename THandler>
inline QPromise<T> tapFail(THandler handler) const;
template<typename E = QPromiseTimeoutException>
inline QPromise<T> timeout(int msec, E&& error = E{}) const;
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 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 THandler>
inline QPromise<T> finally(THandler handler) const;
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 THandler>
inline QPromise<void> finally(THandler handler) const;
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:
@ -95,4 +167,4 @@ private:
#include "qpromise.inl"
#endif // ifndef QTPROMISE_QPROMISE_H
#endif // QTPROMISE_QPROMISE_H

View File

@ -1,125 +1,62 @@
// Qt
#include <QCoreApplication>
#include <QSharedPointer>
/*
* 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)))
{ }
void operator()(const T& value) const
{
resolve(value);
}
void operator()(T&& value) const
{
resolve(std::move(value));
}
private:
QSharedPointer<QPromise<T> > m_promise;
template <typename U>
void resolve(U&& value) const
{
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->resolve(std::forward<U>(value));
m_promise->m_d->dispatch();
}
}
};
template <>
class QPromiseResolve<void>
{
public:
QPromiseResolve(QPromise<void> p)
: m_promise(new QPromise<void>(std::move(p)))
{ }
void operator()() const
{
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->resolve();
m_promise->m_d->dispatch();
}
}
private:
QSharedPointer<QPromise<void> > m_promise;
};
template <class T>
class QPromiseReject
{
public:
QPromiseReject(QPromise<T> p)
: m_promise(new QPromise<T>(std::move(p)))
{ }
template <typename E>
void operator()(E&& error) const
{
Q_ASSERT(!m_promise.isNull());
if (m_promise->isPending()) {
m_promise->m_d->reject(std::forward<E>(error));
m_promise->m_d->dispatch();
}
}
private:
QSharedPointer<QPromise<T> > m_promise;
};
template <typename T>
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F resolver)
: 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));
});
@ -131,15 +68,93 @@ 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 T>
template<typename THandler>
inline QPromise<T> QPromiseBase<T>::finally(THandler handler) const
{
QPromise<T> p = *this;
return p.then(handler, handler).then([=]() {
return p;
});
}
template<typename T>
template<typename THandler>
inline QPromise<T> QPromiseBase<T>::tap(THandler handler) const
{
QPromise<T> p = *this;
return p.then(handler).then([=]() {
return p;
});
}
template<typename T>
template<typename THandler>
inline QPromise<T> QPromiseBase<T>::tapFail(THandler handler) const
{
QPromise<T> p = *this;
return p.then([]() {}, handler).then([=]() {
return p;
});
}
template<typename T>
template<typename E>
inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const
{
QPromise<T> p = *this;
return QPromise<T>{[&](const QPromiseResolve<T>& resolve, const QPromiseReject<T>& reject) {
QTimer::singleShot(msec, [=]() {
// we don't need to verify the current promise state, reject()
// takes care of checking if the promise is already resolved,
// and thus will ignore this rejection.
reject(std::move(error));
});
QtPromisePrivate::PromiseFulfill<QPromise<T>>::call(p, resolve, reject);
}};
}
template<typename T>
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) {
QTimer::singleShot(msec, resolve);
}};
});
}
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
@ -151,117 +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>
template <typename THandler>
inline QPromise<T> QPromise<T>::finally(THandler handler) const
template<typename T>
template<typename Functor>
inline QPromise<T> QPromise<T>::each(Functor fn)
{
return this->then([=](const T& res) {
return QPromise<void>::resolve().then(handler).then([=](){
return res;
});
}, [=]() {
const auto exception = std::current_exception();
return QPromise<void>::resolve().then(handler).then([=](){
std::rethrow_exception(exception);
return T();
});
});
}
return this->tap([=](const T& values) {
int i = 0;
template <typename T>
inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises)
{
const int count = promises.size();
if (count == 0) {
return QPromise<QVector<T> >::resolve({});
}
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.
}));
return QPromise<QVector<T> >([=](
const QPromiseResolve<QVector<T> >& resolve,
const QPromiseReject<QVector<T> >& reject) {
QSharedPointer<int> remaining(new int(count));
QSharedPointer<QVector<T> > results(new QVector<T>(count));
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));
}};
}
template <typename THandler>
inline QPromise<void> QPromise<void>::finally(THandler handler) const
template<template<typename, typename...> class Sequence, typename... Args>
inline QPromise<void> QPromise<void>::all(const Sequence<QPromise<void>, Args...>& promises)
{
return this->then([=]() {
return QPromise<void>::resolve().then(handler).then([](){});
}, [=]() {
const auto exception = std::current_exception();
return QPromise<void>::resolve().then(handler).then([=](){
std::rethrow_exception(exception);
});
});
}
inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promises)
{
const int count = promises.size();
if (count == 0) {
return QPromise<void>::resolve();
}
return QPromise<void>([=](
const QPromiseResolve<void>& resolve,
const QPromiseReject<void>& reject) {
QSharedPointer<int> remaining(new int(promises.size()));
for (const auto& promise: promises) {
promise.then([=]() {
if (--(*remaining) == 0) {
resolve();
}
}, [=]() {
if (*remaining != -1) {
*remaining = -1;
reject(std::current_exception());
}
});
}
});
return QtPromise::all(promises);
}
inline QPromise<void> QPromise<void>::resolve()
{
return QPromise<void>([](const QPromiseResolve<void>& resolve) {
resolve();
});
return QtPromise::resolve();
}
} // namespace QtPromise

View File

@ -1,140 +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>
template<typename F>
static void qtpromise_defer(F&& f)
{
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;
};
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<Unqualified<T> >;
using Type = QtPromise::QPromise<T>;
};
template <typename T>
struct PromiseDeduce<QtPromise::QPromise<T> >
: public PromiseDeduce<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)
{
promise.then(
[=](const T& value) {
resolve(value);
},
[=]() { // catch all
reject(std::current_exception());
});
if (promise.isFulfilled()) {
resolve(promise.m_d->value());
} else if (promise.isRejected()) {
reject(promise.m_d->error());
} else {
promise.then(
[=]() {
resolve(promise.m_d->value());
},
[=]() { // catch all
reject(promise.m_d->error());
});
}
}
};
template <>
struct PromiseFulfill<QtPromise::QPromise<void> >
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)
{
promise.then(
[=]() {
resolve();
},
[=]() { // catch all
reject(std::current_exception());
});
if (promise.isFulfilled()) {
resolve();
} else if (promise.isRejected()) {
reject(promise.m_d->error());
} else {
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());
@ -142,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,
@ -249,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,
@ -268,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());
}
@ -291,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);
@ -329,70 +377,99 @@ struct PromiseCatcher<T, std::nullptr_t, void>
}
};
template <typename T> class PromiseData;
template<typename T, typename F>
struct PromiseMapper
{ };
template <typename T>
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 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 addCatcher(std::function<void(const Error&)> catcher)
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 PromiseError&)> catcher)
{
QWriteLocker lock{&m_lock};
m_catchers.append({QThread::currentThread(), std::move(catcher)});
}
void reject(Error error)
template<typename E>
void reject(E&& error)
{
Q_ASSERT(isPending());
Q_ASSERT(m_error.isNull());
m_error.reset(new Error(std::move(error)));
m_error = PromiseError{std::forward<E>(error)};
setSettled();
}
const PromiseError& error() const
{
Q_ASSERT(isRejected());
return m_error;
}
void dispatch()
{
if (isPending()) {
return;
}
// A promise can't be resolved multiple times so once settled, its state can't
// change. When fulfilled, handlers must be called (a single time) and catchers
// ignored indefinitely (or vice-versa when the promise is rejected), so make
// sure to clear both handlers AND catchers when dispatching. This also prevents
// shared pointer circular reference memory leaks when the owning promise is
// captured in the handler and/or catcher lambdas.
m_lock.lockForWrite();
QVector<Handler> handlers = std::move(m_handlers);
QVector<Catcher> catchers = std::move(m_catchers);
m_lock.unlock();
if (m_error.isNull()) {
notify();
notify(handlers);
return;
}
m_lock.lockForWrite();
QVector<Catcher> catchers(std::move(m_catchers));
m_lock.unlock();
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);
}
}
@ -401,97 +478,150 @@ protected:
void setSettled()
{
QWriteLocker lock(&m_lock);
QWriteLocker lock{&m_lock};
Q_ASSERT(!m_settled);
m_settled = true;
}
virtual void notify() = 0;
virtual void notify(const QVector<Handler>&) = 0;
private:
bool m_settled = false;
QVector<Handler> m_handlers;
QVector<Catcher> m_catchers;
QSharedPointer<Error> m_error;
PromiseError m_error;
};
template <typename T>
class PromiseData : public PromiseDataBase<T>
template<typename T>
class PromiseData : public PromiseDataBase<T, void(const T&)>
{
using Handler = std::pair<QPointer<QThread>, std::function<void(const T&)> >;
using Handler = typename PromiseDataBase<T, void(const T&)>::Handler;
public:
void addHandler(std::function<void(const T&)> handler)
{
QWriteLocker lock(&this->m_lock);
m_handlers.append({QThread::currentThread(), std::move(handler)});
}
void resolve(T&& value)
template<typename V>
void resolve(V&& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value.reset(new T(std::move(value)));
m_value = PromiseValue<T>{std::forward<V>(value)};
this->setSettled();
}
void resolve(const T& value)
const PromiseValue<T>& value() const
{
Q_ASSERT(m_value.isNull());
m_value.reset(new T(value));
this->setSettled();
Q_ASSERT(this->isFulfilled());
return m_value;
}
void notify() Q_DECL_OVERRIDE
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
{
this->m_lock.lockForWrite();
QVector<Handler> handlers(std::move(m_handlers));
this->m_lock.unlock();
QSharedPointer<T> value(m_value);
PromiseValue<T> value = m_value;
Q_ASSERT(!value.isNull());
for (const auto& handler: handlers) {
for (const auto& handler : handlers) {
const auto& fn = handler.second;
qtpromise_defer([=]() {
fn(*value);
}, handler.first);
qtpromise_defer(
[=]() {
fn(value.data());
},
handler.first);
}
}
private:
QVector<Handler> m_handlers;
QSharedPointer<T> m_value;
PromiseValue<T> m_value;
};
template <>
class PromiseData<void> : public PromiseDataBase<void>
template<>
class PromiseData<void> : public PromiseDataBase<void, void()>
{
using Handler = std::pair<QPointer<QThread>, std::function<void()> >;
using Handler = PromiseDataBase<void, void()>::Handler;
public:
void addHandler(std::function<void()> handler)
{
QWriteLocker lock(&m_lock);
m_handlers.append({QThread::currentThread(), std::move(handler)});
}
void resolve() { setSettled(); }
protected:
void notify() Q_DECL_OVERRIDE
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
{
this->m_lock.lockForWrite();
QVector<Handler> handlers(std::move(m_handlers));
this->m_lock.unlock();
for (const auto& handler: handlers) {
for (const auto& handler : handlers) {
qtpromise_defer(handler.second, handler.first);
}
}
private:
QVector<Handler> m_handlers;
};
} // namespace QtPromise
struct PromiseInspect
{
template<typename T>
static inline PromiseData<T>* get(const QtPromise::QPromise<T>& p)
{
return p.m_d.data();
}
};
#endif // ifndef QTPROMISE_QPROMISE_H
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

View 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

View File

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

View File

@ -0,0 +1,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

View File

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

View File

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

View File

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

View 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

View 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

View File

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

View File

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

View File

@ -1,2 +0,0 @@
TEMPLATE = subdirs
SUBDIRS = qtpromise

9
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,9 @@
find_package(Qt${QT_VERSION_MAJOR} REQUIRED
COMPONENTS
Concurrent
Test
)
include(QtPromiseAddTest)
add_subdirectory(auto)

View File

@ -0,0 +1 @@
add_subdirectory(qtpromise)

View File

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

View File

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

View File

@ -1,222 +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 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); // move value to the promise data
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::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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
qtpromise_add_test(benchmark
SOURCES
tst_benchmark.cpp
)

View 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);
}
}

View 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()

View 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));
}

View 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);
}
}

View 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
)

View 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);
}
}

View 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();
}

View 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();
}

View File

@ -0,0 +1,4 @@
qtpromise_add_test(exceptions
SOURCES
tst_exceptions.cpp
)

View 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>();
}

View File

@ -0,0 +1,4 @@
qtpromise_add_test(future
SOURCES
tst_future.cpp
)

View 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"});
}

View 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