8 Commits

Author SHA1 Message Date
5523597e7c - wip -
- wip -
2018-05-10 11:51:54 +02:00
26a2110a14 Fix GCC -Wold-style-cast warnings
Explicitly enable -Wold-style-cast and make all warnings into errors in tests (-Werror on GCC, -WX on MSVC).
2018-05-10 11:51:17 +02:00
fa987a5044 Cleanup promise captured in resolve/reject
Make sure that QPromiseResolve and QPromiseReject release their associated promise as soon as one of them is resolved or rejected, ensuring that the promise data is cleaned up once resolved (that fixes cases where one or both of them are captured in a signal/slot connection lambda)
2018-05-10 11:51:17 +02:00
7b0cba5b9d Allow QSharedPointer as rejection reason
Embed the promise fulfillment value and rejection reason in respectively PromiseValue and PromiseError private wrappers, both storing the data in a shared pointer (QPromiseError is now deprecated).
2018-05-10 11:51:14 +02:00
2c8ed6e676 Remove extra space between closing angle brackets
The extra space between template closing angle brackets is not anymore required in C++11 (https://en.wikipedia.org/wiki/C%2B%2B11#Right_angle_bracket)
2018-05-10 09:21:21 +02:00
d128a5fa8d Clarify QPromise::all fulfillment values order 2018-04-30 19:05:21 +02:00
dcbb2ef860 Fix clang "unused type alias 'FType'" warning 2018-04-26 16:06:10 +02:00
50bae380be Implement QPromise::tapFail(handler) 2018-03-29 09:08:19 +02:00
56 changed files with 937 additions and 1230 deletions

1
.gitignore vendored
View File

@ -8,7 +8,6 @@ node_modules
*.obj *.obj
*.exe *.exe
*.user *.user
*.qmlc
Makefile* Makefile*
moc_*.cpp moc_*.cpp
moc_*.h moc_*.h

View File

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

View File

@ -11,6 +11,7 @@
* [.isPending](qtpromise/qpromise/ispending.md) * [.isPending](qtpromise/qpromise/ispending.md)
* [.isRejected](qtpromise/qpromise/isrejected.md) * [.isRejected](qtpromise/qpromise/isrejected.md)
* [.tap](qtpromise/qpromise/tap.md) * [.tap](qtpromise/qpromise/tap.md)
* [.tapFail](qtpromise/qpromise/tapfail.md)
* [.then](qtpromise/qpromise/then.md) * [.then](qtpromise/qpromise/then.md)
* [.timeout](qtpromise/qpromise/timeout.md) * [.timeout](qtpromise/qpromise/timeout.md)
* [.wait](qtpromise/qpromise/wait.md) * [.wait](qtpromise/qpromise/wait.md)

View File

@ -10,6 +10,7 @@
* [`QPromise<T>::isPending`](qpromise/ispending.md) * [`QPromise<T>::isPending`](qpromise/ispending.md)
* [`QPromise<T>::isRejected`](qpromise/isrejected.md) * [`QPromise<T>::isRejected`](qpromise/isrejected.md)
* [`QPromise<T>::tap`](qpromise/tap.md) * [`QPromise<T>::tap`](qpromise/tap.md)
* [`QPromise<T>::tapFail`](qpromise/tapfail.md)
* [`QPromise<T>::then`](qpromise/then.md) * [`QPromise<T>::then`](qpromise/then.md)
* [`QPromise<T>::timeout`](qpromise/timeout.md) * [`QPromise<T>::timeout`](qpromise/timeout.md)
* [`QPromise<T>::wait`](qpromise/wait.md) * [`QPromise<T>::wait`](qpromise/wait.md)

View File

@ -4,7 +4,9 @@
[static] QPromise<T>::all(Sequence<QPromise<T>> promises) -> QPromise<QVector<T>> [static] QPromise<T>::all(Sequence<QPromise<T>> promises) -> QPromise<QVector<T>>
``` ```
Returns a `QPromise<QVector<T>>` that fulfills when **all** `promises` of (the same) type `T` have been fulfilled. The `output` value is a vector containing **all** the values of `promises`, in the same order. If any of the given `promises` fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved. Returns a `QPromise<QVector<T>>` that fulfills when **all** `promises` of (the same) type `T` have been fulfilled. The `output` value is a vector containing all the values of `promises`, in the same order, i.e., at the respective positions to the original sequence, regardless of completion order.
If any of the given `promises` fail, `output` immediately rejects with the error of the promise that rejected, whether or not the other promises are resolved.
`Sequence` is any STL compatible container (eg. `QVector`, `QList`, `std::vector`, etc.) `Sequence` is any STL compatible container (eg. `QVector`, `QList`, `std::vector`, etc.)

View File

@ -0,0 +1,21 @@
## `QPromise<T>::tapFail`
```
QPromise<T>::tapFail(Function handler) -> QPromise<T>
```
This `handler` allows to observe errors of the `input` promise without handling them - similar to [`finally`](finally.md) but **only** called on rejections. The `output` promise has the same type as the `input` one but also the same value or error. However, if `handler` throws, `output` is rejected with the new exception.
```cpp
QPromise<int> input = {...}
auto output = input.tapFail([](Error err) {
log(err);
}).then([](int res) {
return process(res);
}).fail([](Error err) {
handle(err);
return -1;
});
```
If `handler` returns a promise (or QFuture), the `output` promise is delayed until the returned promise is resolved and under the same conditions: the delayed value is ignored, the error transmitted to the `output` promise.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
// QtPromise // QtPromise
#include "qpromise_p.h" #include "qpromise_p.h"
#include "qpromiseerror.h"
#include "qpromiseglobal.h" #include "qpromiseglobal.h"
// Qt // Qt
@ -22,6 +23,9 @@ public:
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); inline QPromiseBase(F resolver);
template <typename U>
inline QPromiseBase(const QPromise<U>& other);
QPromiseBase(const QPromiseBase<T>& other): m_d(other.m_d) {} QPromiseBase(const QPromiseBase<T>& other): m_d(other.m_d) {}
QPromiseBase(const QPromise<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); } QPromiseBase(QPromiseBase<T>&& other) Q_DECL_NOEXCEPT { swap(other); }
@ -59,6 +63,9 @@ public:
template <typename THandler> template <typename THandler>
inline QPromise<T> tap(THandler handler) const; inline QPromise<T> tap(THandler handler) const;
template <typename THandler>
inline QPromise<T> tapFail(THandler handler) const;
template <typename E = QPromiseTimeoutException> template <typename E = QPromiseTimeoutException>
inline QPromise<T> timeout(int msec, E&& error = E()) const; inline QPromise<T> timeout(int msec, E&& error = E()) const;
@ -71,8 +78,7 @@ public: // STATIC
protected: protected:
friend struct QtPromisePrivate::PromiseFulfill<QPromise<T>>; friend struct QtPromisePrivate::PromiseFulfill<QPromise<T>>;
friend class QPromiseResolve<T>; friend class QtPromisePrivate::PromiseResolver<T>;
friend class QPromiseReject<T>;
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T>> m_d; QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T>> m_d;
}; };
@ -91,6 +97,9 @@ public: // STATIC
inline static QPromise<T> resolve(const T& value); inline static QPromise<T> resolve(const T& value);
inline static QPromise<T> resolve(T&& value); inline static QPromise<T> resolve(T&& value);
template <typename U>
operator QPromise<U>();
private: private:
friend class QPromiseBase<T>; friend class QPromiseBase<T>;
}; };
@ -102,6 +111,9 @@ public:
template <typename F> template <typename F>
QPromise(F&& resolver): QPromiseBase<void>(std::forward<F>(resolver)) { } QPromise(F&& resolver): QPromiseBase<void>(std::forward<F>(resolver)) { }
template <typename T>
QPromise(const QPromise<T>& other);
public: // STATIC public: // STATIC
template <template <typename, typename...> class Sequence = QVector, typename ...Args> template <template <typename, typename...> class Sequence = QVector, typename ...Args>
inline static QPromise<void> all(const Sequence<QPromise<void>, Args...>& promises); inline static QPromise<void> all(const Sequence<QPromise<void>, Args...>& promises);

View File

@ -9,95 +9,82 @@ template <class T>
class QPromiseResolve class QPromiseResolve
{ {
public: public:
QPromiseResolve(QPromise<T> p) QPromiseResolve(QtPromisePrivate::PromiseResolver<T> resolver)
: m_promise(new QPromise<T>(std::move(p))) : m_resolver(std::move(resolver))
{ } { }
template <typename V> template <typename V>
void operator()(V&& value) const void operator()(V&& value) const
{ {
Q_ASSERT(!m_promise.isNull()); m_resolver.resolve(std::forward<V>(value));
if (m_promise->isPending()) {
m_promise->m_d->resolve(std::forward<V>(value));
m_promise->m_d->dispatch();
} }
}
private:
QSharedPointer<QPromise<T> > m_promise;
};
template <>
class QPromiseResolve<void>
{
public:
QPromiseResolve(QPromise<void> p)
: m_promise(new QPromise<void>(std::move(p)))
{ }
void operator()() const void operator()() const
{ {
Q_ASSERT(!m_promise.isNull()); m_resolver.resolve();
if (m_promise->isPending()) {
m_promise->m_d->resolve();
m_promise->m_d->dispatch();
}
} }
private: private:
QSharedPointer<QPromise<void> > m_promise; mutable QtPromisePrivate::PromiseResolver<T> m_resolver;
}; };
template <class T> template <class T>
class QPromiseReject class QPromiseReject
{ {
public: public:
QPromiseReject(QPromise<T> p) QPromiseReject(QtPromisePrivate::PromiseResolver<T> resolver)
: m_promise(new QPromise<T>(std::move(p))) : m_resolver(std::move(resolver))
{ } { }
template <typename E> template <typename E>
void operator()(E&& error) const void operator()(E&& error) const
{ {
Q_ASSERT(!m_promise.isNull()); m_resolver.reject(std::forward<E>(error));
if (m_promise->isPending()) {
m_promise->m_d->reject(std::forward<E>(error));
m_promise->m_d->dispatch();
}
} }
private: private:
QSharedPointer<QPromise<T> > m_promise; mutable QtPromisePrivate::PromiseResolver<T> m_resolver;
}; };
template <typename T> template <typename T>
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type> template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F resolver) inline QPromiseBase<T>::QPromiseBase(F callback)
: m_d(new QtPromisePrivate::PromiseData<T>()) : m_d(new QtPromisePrivate::PromiseData<T>())
{ {
QPromiseResolve<T> resolve(*this); QtPromisePrivate::PromiseResolver<T> resolver(*this);
QPromiseReject<T> reject(*this);
try { try {
resolver(resolve); callback(QPromiseResolve<T>(resolver));
} catch (...) { } catch (...) {
reject(std::current_exception()); resolver.reject(std::current_exception());
} }
} }
template <typename T> template <typename T>
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type> template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F resolver) inline QPromiseBase<T>::QPromiseBase(F callback)
: m_d(new QtPromisePrivate::PromiseData<T>()) : m_d(new QtPromisePrivate::PromiseData<T>())
{ {
QtPromisePrivate::PromiseResolver<T> resolver(*this);
try {
callback(QPromiseResolve<T>(resolver), QPromiseReject<T>(resolver));
} catch (...) {
resolver.reject(std::current_exception());
}
}
template <typename T>
template <typename U>
inline QPromiseBase<T>::QPromiseBase(const QPromise<U>& other)
: m_d(new QtPromisePrivate::PromiseData<T>())
{
using namespace QtPromisePrivate;
QPromiseResolve<T> resolve(*this); QPromiseResolve<T> resolve(*this);
QPromiseReject<T> reject(*this); QPromiseReject<T> reject(*this);
try { PromiseFulfill<QPromise<U> >::call(other, PromiseCast<U, T>::apply(resolve), reject);
resolver(resolve, reject);
} catch (...) {
reject(std::current_exception());
}
} }
template <typename T> template <typename T>
@ -158,6 +145,16 @@ inline QPromise<T> QPromiseBase<T>::tap(THandler handler) const
}); });
} }
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 T>
template <typename E> template <typename E>
inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const
@ -213,7 +210,7 @@ template <typename T>
template <template <typename, typename...> class Sequence, typename ...Args> template <template <typename, typename...> class Sequence, typename ...Args>
inline QPromise<QVector<T>> QPromise<T>::all(const Sequence<QPromise<T>, Args...>& promises) inline QPromise<QVector<T>> QPromise<T>::all(const Sequence<QPromise<T>, Args...>& promises)
{ {
const int count = (int)promises.size(); const int count = static_cast<int>(promises.size());
if (count == 0) { if (count == 0) {
return QPromise<QVector<T>>::resolve({}); return QPromise<QVector<T>>::resolve({});
} }
@ -260,10 +257,25 @@ inline QPromise<T> QPromise<T>::resolve(T&& value)
}); });
} }
template <typename T>
template <typename U>
QPromise<T>::operator QPromise<U>()
{
return QPromise<U>::resolve(U());
}
template <typename T>
QPromise<void>::QPromise(const QPromise<T>& other)
: QPromiseBase<void>([&](const QPromiseResolve<void>& resolve, const QPromiseReject<void>& reject) {
QtPromisePrivate::PromiseFulfill<QPromise<T> >::call(other, resolve, reject);
})
{
}
template <template <typename, typename...> class Sequence, typename ...Args> template <template <typename, typename...> class Sequence, typename ...Args>
inline QPromise<void> QPromise<void>::all(const Sequence<QPromise<void>, Args...>& promises) inline QPromise<void> QPromise<void>::all(const Sequence<QPromise<void>, Args...>& promises)
{ {
const int count = (int)promises.size(); const int count = static_cast<int>(promises.size());
if (count == 0) { if (count == 0) {
return QPromise<void>::resolve(); return QPromise<void>::resolve();
} }

View File

@ -2,7 +2,6 @@
#define QTPROMISE_QPROMISE_P_H #define QTPROMISE_QPROMISE_P_H
// QtPromise // QtPromise
#include "qpromiseerror.h"
#include "qpromiseglobal.h" #include "qpromiseglobal.h"
// Qt // Qt
@ -34,9 +33,10 @@ namespace QtPromisePrivate {
template <typename F> template <typename F>
static void qtpromise_defer(F&& f, const QPointer<QThread>& thread) static void qtpromise_defer(F&& f, const QPointer<QThread>& thread)
{ {
using FType = typename std::decay<F>::type;
struct Event : public QEvent struct Event : public QEvent
{ {
using FType = typename std::decay<F>::type;
Event(FType&& f) : QEvent(QEvent::None), m_f(std::move(f)) { } Event(FType&& f) : QEvent(QEvent::None), m_f(std::move(f)) { }
Event(const FType& f) : QEvent(QEvent::None), m_f(f) { } Event(const FType& f) : QEvent(QEvent::None), m_f(f) { }
~Event() { m_f(); } ~Event() { m_f(); }
@ -71,6 +71,43 @@ static void qtpromise_defer(F&& f)
qtpromise_defer(std::forward<F>(f), QThread::currentThread()); qtpromise_defer(std::forward<F>(f), QThread::currentThread());
} }
template <typename T>
class PromiseValue
{
public:
PromiseValue() { }
PromiseValue(const T& data) : m_data(new T(data)) { }
PromiseValue(T&& data) : m_data(new T(std::move(data))) { }
bool isNull() const { return m_data.isNull(); }
const T& data() const { return *m_data; }
private:
QSharedPointer<T> m_data;
};
class PromiseError
{
public:
template <typename T>
PromiseError(const T& value)
{
try {
throw value;
} catch (...) {
m_data = std::current_exception();
}
}
PromiseError() { }
PromiseError(const std::exception_ptr& exception) : m_data(exception) { }
void rethrow() const { std::rethrow_exception(m_data); }
bool isNull() const { return m_data == nullptr; }
private:
// NOTE(SB) std::exception_ptr is already a shared pointer
std::exception_ptr m_data;
};
template <typename T> template <typename T>
struct PromiseDeduce struct PromiseDeduce
{ {
@ -85,10 +122,8 @@ struct PromiseDeduce<QtPromise::QPromise<T> >
template <typename T> template <typename T>
struct PromiseFulfill struct PromiseFulfill
{ {
static void call( template <typename TResolve, typename TReject>
T&& value, static void call(T&& value, const TResolve& resolve, const TReject&)
const QtPromise::QPromiseResolve<T>& resolve,
const QtPromise::QPromiseReject<T>&)
{ {
resolve(std::move(value)); resolve(std::move(value));
} }
@ -97,10 +132,11 @@ struct PromiseFulfill
template <typename T> template <typename T>
struct PromiseFulfill<QtPromise::QPromise<T>> struct PromiseFulfill<QtPromise::QPromise<T>>
{ {
template <typename TResolve, typename TReject>
static void call( static void call(
const QtPromise::QPromise<T>& promise, const QtPromise::QPromise<T>& promise,
const QtPromise::QPromiseResolve<T>& resolve, const TResolve& resolve,
const QtPromise::QPromiseReject<T>& reject) const TReject& reject)
{ {
if (promise.isFulfilled()) { if (promise.isFulfilled()) {
resolve(promise.m_d->value()); resolve(promise.m_d->value());
@ -119,9 +155,9 @@ struct PromiseFulfill<QtPromise::QPromise<T> >
template <> template <>
struct PromiseFulfill<QtPromise::QPromise<void>> struct PromiseFulfill<QtPromise::QPromise<void>>
{ {
template <typename TPromise, typename TResolve, typename TReject> template <typename TResolve, typename TReject>
static void call( static void call(
const TPromise& promise, const QtPromise::QPromise<void>& promise,
const TResolve& resolve, const TResolve& resolve,
const TReject& reject) const TReject& reject)
{ {
@ -305,12 +341,12 @@ struct PromiseCatcher
using ResType = typename std::result_of<THandler(TArg)>::type; using ResType = typename std::result_of<THandler(TArg)>::type;
template <typename TResolve, typename TReject> template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create( static std::function<void(const PromiseError&)> create(
const THandler& handler, const THandler& handler,
const TResolve& resolve, const TResolve& resolve,
const TReject& reject) const TReject& reject)
{ {
return [=](const QtPromise::QPromiseError& error) { return [=](const PromiseError& error) {
try { try {
error.rethrow(); error.rethrow();
} catch (const TArg& error) { } catch (const TArg& error) {
@ -328,12 +364,12 @@ struct PromiseCatcher<T, THandler, void>
using ResType = typename std::result_of<THandler()>::type; using ResType = typename std::result_of<THandler()>::type;
template <typename TResolve, typename TReject> template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create( static std::function<void(const PromiseError&)> create(
const THandler& handler, const THandler& handler,
const TResolve& resolve, const TResolve& resolve,
const TReject& reject) const TReject& reject)
{ {
return [=](const QtPromise::QPromiseError& error) { return [=](const PromiseError& error) {
try { try {
error.rethrow(); error.rethrow();
} catch (...) { } catch (...) {
@ -347,12 +383,12 @@ template <typename T>
struct PromiseCatcher<T, std::nullptr_t, void> struct PromiseCatcher<T, std::nullptr_t, void>
{ {
template <typename TResolve, typename TReject> template <typename TResolve, typename TReject>
static std::function<void(const QtPromise::QPromiseError&)> create( static std::function<void(const PromiseError&)> create(
std::nullptr_t, std::nullptr_t,
const TResolve&, const TResolve&,
const TReject& reject) 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, // 2.2.7.4. If onRejected is not a function and promise1 is rejected,
// promise2 must be rejected with the same reason as promise1 // promise2 must be rejected with the same reason as promise1
reject(error); reject(error);
@ -360,15 +396,75 @@ struct PromiseCatcher<T, std::nullptr_t, void>
} }
}; };
template <typename T, typename U>
struct PromiseCast
{
template <typename F>
static auto apply(const F& resolve)
{
return [=](const QSharedPointer<T>& value) {
resolve(static_cast<T>(*value));
};
}
};
template <typename T>
struct PromiseCast<T, void>
{
template <typename F>
static auto apply(const F& resolve)
{
return [=](const QSharedPointer<T>&) {
resolve();
};
}
};
/*
template <typename T>
struct PromiseCast<T, QVariant>
{
static QtPromise::QPromise<QVariant> cast(
const QtPromise::QPromiseBase<T>& input)
{
return input.then([](const T& res) {
return QVariant::fromValue(res);
});
}
};
template <typename U>
struct PromiseCast<QVariant, U>
{
static QtPromise::QPromise<U> cast(
const QtPromise::QPromiseBase<QVariant>& input)
{
return input.then([](const QVariant& res) {
return res.value<U>();
});
}
};
template <>
struct PromiseCast<void, QVariant>
{
static QtPromise::QPromise<void> cast(
const QtPromise::QPromiseBase<QVariant>& input)
{
return input.then([]() {
});
}
};
*/
template <typename T> class PromiseData; template <typename T> class PromiseData;
template <typename T, typename F> template <typename T, typename F>
class PromiseDataBase : public QSharedData class PromiseDataBase : public QSharedData
{ {
public: public:
using Error = QtPromise::QPromiseError;
using Handler = std::pair<QPointer<QThread>, std::function<F>>; using Handler = std::pair<QPointer<QThread>, std::function<F>>;
using Catcher = std::pair<QPointer<QThread>, std::function<void(const Error&)> >; using Catcher = std::pair<QPointer<QThread>, std::function<void(const PromiseError&)>>;
virtual ~PromiseDataBase() {} virtual ~PromiseDataBase() {}
@ -394,29 +490,22 @@ public:
m_handlers.append({QThread::currentThread(), std::move(handler)}); m_handlers.append({QThread::currentThread(), std::move(handler)});
} }
void addCatcher(std::function<void(const Error&)> catcher) void addCatcher(std::function<void(const PromiseError&)> catcher)
{ {
QWriteLocker lock(&m_lock); QWriteLocker lock(&m_lock);
m_catchers.append({QThread::currentThread(), std::move(catcher)}); m_catchers.append({QThread::currentThread(), std::move(catcher)});
} }
void reject(Error error) template <typename E>
void reject(E&& error)
{ {
Q_ASSERT(isPending()); Q_ASSERT(isPending());
Q_ASSERT(m_error.isNull()); Q_ASSERT(m_error.isNull());
m_error.reset(new Error(std::move(error))); m_error = PromiseError(std::forward<E>(error));
setSettled(); setSettled();
} }
void reject(const QSharedPointer<Error>& error) const PromiseError& error() const
{
Q_ASSERT(isPending());
Q_ASSERT(m_error.isNull());
m_error = error;
this->setSettled();
}
const QSharedPointer<Error>& error() const
{ {
Q_ASSERT(isRejected()); Q_ASSERT(isRejected());
return m_error; return m_error;
@ -445,13 +534,13 @@ public:
return; return;
} }
QSharedPointer<Error> error = m_error; PromiseError error(m_error);
Q_ASSERT(!error.isNull()); Q_ASSERT(!error.isNull());
for (const auto& catcher: catchers) { for (const auto& catcher: catchers) {
const auto& fn = catcher.second; const auto& fn = catcher.second;
qtpromise_defer([=]() { qtpromise_defer([=]() {
fn(*error); fn(error);
}, catcher.first); }, catcher.first);
} }
} }
@ -472,7 +561,7 @@ private:
bool m_settled = false; bool m_settled = false;
QVector<Handler> m_handlers; QVector<Handler> m_handlers;
QVector<Catcher> m_catchers; QVector<Catcher> m_catchers;
QSharedPointer<Error> m_error; PromiseError m_error;
}; };
template <typename T> template <typename T>
@ -481,31 +570,16 @@ class PromiseData : public PromiseDataBase<T, void(const T&)>
using Handler = typename PromiseDataBase<T, void(const T&)>::Handler; using Handler = typename PromiseDataBase<T, void(const T&)>::Handler;
public: public:
void resolve(T&& value) template <typename V>
void resolve(V&& value)
{ {
Q_ASSERT(this->isPending()); Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull()); Q_ASSERT(m_value.isNull());
m_value.reset(new T(std::move(value))); m_value = PromiseValue<T>(std::forward<V>(value));
this->setSettled(); this->setSettled();
} }
void resolve(const T& value) const PromiseValue<T>& value() const
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value.reset(new T(value));
this->setSettled();
}
void resolve(const QSharedPointer<T>& value)
{
Q_ASSERT(this->isPending());
Q_ASSERT(m_value.isNull());
m_value = value;
this->setSettled();
}
const QSharedPointer<T>& value() const
{ {
Q_ASSERT(this->isFulfilled()); Q_ASSERT(this->isFulfilled());
return m_value; return m_value;
@ -513,19 +587,19 @@ public:
void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE void notify(const QVector<Handler>& handlers) Q_DECL_OVERRIDE
{ {
QSharedPointer<T> value(m_value); PromiseValue<T> value(m_value);
Q_ASSERT(!value.isNull()); Q_ASSERT(!value.isNull());
for (const auto& handler: handlers) { for (const auto& handler: handlers) {
const auto& fn = handler.second; const auto& fn = handler.second;
qtpromise_defer([=]() { qtpromise_defer([=]() {
fn(*value); fn(value.data());
}, handler.first); }, handler.first);
} }
} }
private: private:
QSharedPointer<T> m_value; PromiseValue<T> m_value;
}; };
template <> template <>
@ -548,6 +622,68 @@ protected:
} }
}; };
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();
}
}
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 QtPromise } // namespace QtPromise
#endif // ifndef QTPROMISE_QPROMISE_H #endif // ifndef QTPROMISE_QPROMISE_P_H

View File

@ -2,6 +2,7 @@
#define QTPROMISE_QPROMISEERROR_H #define QTPROMISE_QPROMISEERROR_H
// QtPromise // QtPromise
#include "qpromise_p.h"
#include "qpromiseglobal.h" #include "qpromiseglobal.h"
// Qt // Qt
@ -9,53 +10,6 @@
namespace QtPromise { namespace QtPromise {
class QPromiseError
{
public:
template <typename T>
QPromiseError(const T& value)
{
try {
throw value;
} catch (...) {
m_exception = std::current_exception();
}
}
QPromiseError(const std::exception_ptr& exception)
: m_exception(exception)
{ }
QPromiseError(const QPromiseError& error)
: m_exception(error.m_exception)
{ }
QPromiseError(QPromiseError&& other)
: m_exception(nullptr)
{
swap(other);
}
QPromiseError& operator=(QPromiseError other)
{
swap(other);
return *this;
}
void swap(QPromiseError& other)
{
qSwap(m_exception, other.m_exception);
}
void rethrow() const
{
std::rethrow_exception(m_exception);
}
private:
std::exception_ptr m_exception;
};
class QPromiseTimeoutException : public QException class QPromiseTimeoutException : public QException
{ {
public: public:
@ -66,6 +20,12 @@ public:
} }
}; };
// QPromiseError is provided for backward compatibility and will be
// removed in the next major version: it wasn't intended to be used
// directly and thus should not be part of the public API.
// TODO Remove QPromiseError at version 1.0
using QPromiseError = QtPromisePrivate::PromiseError;
} // namespace QtPromise } // namespace QtPromise
#endif // QTPROMISE_QPROMISEERROR_H #endif // QTPROMISE_QPROMISEERROR_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,135 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_cast : public QObject
{
Q_OBJECT
private Q_SLOTS:
void typeToVoid();
//void castToVoidOperator();
//void typeToVariant(); // QPromise<T>.cast<QVariant>()
//void variantToType();
//void voidToVariant(); // QPromise<void>.cast<QVariant>()
//void variantToVoid();
//void jsonValueToJsonObject();
};
QTEST_MAIN(tst_qpromise_cast)
#include "tst_cast.moc"
void tst_qpromise_cast::typeToVoid()
{
QPromise<void> p0 = QPromise<int>::resolve(42);
QPromise<void> p1 = QPromise<QString>::resolve(QString("foo"));
QVERIFY(p0.isFulfilled());
QVERIFY(p1.isFulfilled());
}
//void tst_qpromise_cast::castToVoidOperator()
//{
//auto p0 = QPromise<int>::resolve(42);
//QPromise<double> p1(p0);
//QPromise<void> p2(p0);
//auto p4 = QPromise<QString>::resolve("foo");
//
//p0.then([](int res) { qDebug() << res; });
//p1.then([](double res) { qDebug() << res; });
//p2.then([]() { qDebug() << "done"; });
//
//foo().then([]() {
// qDebug() << "done";
//}).wait();
//
//QPromise<QVariant>::all({p0, p1, p4}).then([](const QVector<QVariant>& res) {
// qDebug() << "all done!" << res;
//}).wait();
//}
/*
namespace {
template <typename T, typename U>
void test_qpromise_cast(const T& t, const U& u)
{
auto p = QPromise<T>::resolve(t).cast<U>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<U> >::value));
QCOMPARE(waitForValue(p, U()), u);
}
template <typename U>
void test_qpromise_cast(const U& u)
{
auto p = QPromise<void>::resolve().cast<U>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<U> >::value));
QCOMPARE(waitForValue(p, U()), u);
}
} // anonymous namespace
void tst_qpromise_cast::typeToVariant()
{
test_qpromise_cast(42, QVariant(42));
test_qpromise_cast(4.2, QVariant(4.2));
test_qpromise_cast(true, QVariant(true));
test_qpromise_cast(QString("foo"), QVariant(QString("foo")));
test_qpromise_cast(QUrl("http://x.y.z"), QVariant(QUrl("http://x.y.z")));
test_qpromise_cast(QSize(128, 256), QVariant(QSize(128, 256)));
test_qpromise_cast(QDate(2018, 1, 1), QVariant(QDate(2018, 1, 1)));
test_qpromise_cast(QJsonValue("foo"), QVariant(QJsonValue("foo")));
test_qpromise_cast(QStringList{"foo", "bar"}, QVariant(QStringList{"foo", "bar"}));
test_qpromise_cast(QList<QVariant>{"foo", 42}, QVariant(QList<QVariant>{"foo", 42}));
test_qpromise_cast(QMap<QString, QVariant>{{"foo", 42}}, QVariant(QVariantMap{{"foo", 42}}));
}
void tst_qpromise_cast::voidToVariant()
{
test_qpromise_cast(QVariant());
}
void tst_qpromise_cast::variantToType()
{
// invalid
// int
// QString
// QColor
// QList
}
/*
void tst_qpromise_cast::jsonValueToJsonObject()
{
{ // QJsonValue(Null) -> QJsonObject
auto p = QPromise<QJsonValue>::resolve(QJsonValue()).cast<QJsonObject>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QJsonObject> >::value));
const QJsonObject res = waitForValue(p, QJsonObject());
QVERIFY(res.isEmpty());
}
{ // QJsonValue(int) -> QJsonObject
auto p = QPromise<QJsonValue>::resolve(QJsonValue(42)).cast<QJsonObject>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QJsonObject> >::value));
const QJsonObject res = waitForValue(p, QJsonObject());
QVERIFY(res.isEmpty());
}
{ // QJsonValue(QJsonObject) -> QJsonObject
const QJsonObject object{{"magic", 42}};
auto p = QPromise<QJsonValue>::resolve(QJsonValue(object)).cast<QJsonObject>();
Q_STATIC_ASSERT((std::is_same<decltype(p), QPromise<QJsonObject> >::value));
const QJsonObject res = waitForValue(p, QJsonObject());
QCOMPARE(res.value("magic"), 42);
}
}
*/

View File

@ -7,6 +7,9 @@
// Qt // Qt
#include <QtTest> #include <QtTest>
// STL
#include <memory>
using namespace QtPromise; using namespace QtPromise;
class tst_qpromise_construct : public QObject class tst_qpromise_construct : public QObject
@ -30,6 +33,8 @@ private Q_SLOTS:
void rejectSync_void(); void rejectSync_void();
void rejectAsync(); void rejectAsync();
void rejectAsync_void(); void rejectAsync_void();
void connectAndResolve();
void connectAndReject();
}; };
QTEST_MAIN(tst_qpromise_construct) QTEST_MAIN(tst_qpromise_construct)
@ -228,3 +233,77 @@ void tst_qpromise_construct::rejectThrowTwoArgs_void()
QCOMPARE(waitForValue(p, -1, 42), -1); QCOMPARE(waitForValue(p, -1, 42), -1);
QCOMPARE(waitForError(p, QString()), QString("foo")); QCOMPARE(waitForError(p, QString()), QString("foo"));
} }
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_construct::connectAndResolve()
{
QScopedPointer<QObject> object(new QObject());
std::weak_ptr<int> wptr;
{
auto p = QPromise<std::shared_ptr<int>>([&](
const QPromiseResolve<std::shared_ptr<int>>& resolve,
const QPromiseReject<std::shared_ptr<int>>& reject) {
connect(object.data(), &QObject::objectNameChanged,
[=, &wptr](const QString& name) {
std::shared_ptr<int> sptr(new int(42));
wptr = sptr;
if (name == "foobar") {
resolve(sptr);
} else {
reject(42);
}
});
});
QCOMPARE(p.isPending(), true);
object->setObjectName("foobar");
QCOMPARE(waitForValue(p, std::shared_ptr<int>()), wptr.lock());
QCOMPARE(wptr.use_count(), 1l); // "p" still holds a reference
}
QCOMPARE(wptr.use_count(), 0l);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_construct::connectAndReject()
{
QScopedPointer<QObject> object(new QObject());
std::weak_ptr<int> wptr;
{
auto p = QPromise<int>([&](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& reject) {
connect(object.data(), &QObject::objectNameChanged,
[=, &wptr](const QString& name) {
std::shared_ptr<int> sptr(new int(42));
wptr = sptr;
if (name == "foobar") {
reject(sptr);
} else {
resolve(42);
}
});
});
QCOMPARE(p.isPending(), true);
object->setObjectName("foobar");
QCOMPARE(waitForError(p, std::shared_ptr<int>()), wptr.lock());
QCOMPARE(wptr.use_count(), 1l); // "p" still holds a reference
}
QCOMPARE(wptr.use_count(), 0l);
}

View File

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

View File

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

View File

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

View File

@ -7,6 +7,9 @@
// Qt // Qt
#include <QtTest> #include <QtTest>
// STL
#include <memory>
using namespace QtPromise; using namespace QtPromise;
class tst_qpromise_resolve : public QObject class tst_qpromise_resolve : public QObject
@ -14,14 +17,16 @@ class tst_qpromise_resolve : public QObject
Q_OBJECT Q_OBJECT
private Q_SLOTS: private Q_SLOTS:
void value(); void resolveWithValue();
void empty_void(); void resolveWithNoValue();
void resolveWithQSharedPtr();
void resolveWithStdSharedPtr();
}; };
QTEST_MAIN(tst_qpromise_resolve) QTEST_MAIN(tst_qpromise_resolve)
#include "tst_resolve.moc" #include "tst_resolve.moc"
void tst_qpromise_resolve::value() void tst_qpromise_resolve::resolveWithValue()
{ {
const int value = 42; const int value = 42;
auto p0 = QPromise<int>::resolve(value); auto p0 = QPromise<int>::resolve(value);
@ -33,9 +38,49 @@ void tst_qpromise_resolve::value()
QCOMPARE(waitForValue(p1, -1), 43); QCOMPARE(waitForValue(p1, -1), 43);
} }
void tst_qpromise_resolve::empty_void() void tst_qpromise_resolve::resolveWithNoValue()
{ {
auto p = QPromise<void>::resolve(); auto p = QPromise<void>::resolve();
QCOMPARE(p.isFulfilled(), true); QCOMPARE(p.isFulfilled(), true);
} }
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_resolve::resolveWithQSharedPtr()
{
QWeakPointer<int> wptr;
{
QSharedPointer<int> sptr(new int(42));
auto p = QPromise<QSharedPointer<int>>::resolve(sptr);
QCOMPARE(waitForValue(p, QSharedPointer<int>()), sptr);
wptr = sptr;
sptr.reset();
QCOMPARE(wptr.isNull(), false); // "p" still holds a reference
}
QCOMPARE(wptr.isNull(), true);
}
// https://github.com/simonbrunel/qtpromise/issues/6
void tst_qpromise_resolve::resolveWithStdSharedPtr()
{
std::weak_ptr<int> wptr;
{
std::shared_ptr<int> sptr(new int(42));
auto p = QPromise<std::shared_ptr<int>>::resolve(sptr);
QCOMPARE(waitForValue(p, std::shared_ptr<int>()), sptr);
wptr = sptr;
sptr.reset();
QCOMPARE(wptr.use_count(), 1l); // "p" still holds a reference
}
QCOMPARE(wptr.use_count(), 0l);
}

View File

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

View File

@ -0,0 +1,159 @@
// Tests
#include "../../shared/utils.h"
// QtPromise
#include <QtPromise>
// Qt
#include <QtTest>
using namespace QtPromise;
class tst_qpromise_tapfail : public QObject
{
Q_OBJECT
private Q_SLOTS:
void fulfilled();
void fulfilled_void();
void rejected();
void rejected_void();
void throws();
void throws_void();
void delayedResolved();
void delayedRejected();
};
QTEST_MAIN(tst_qpromise_tapfail)
#include "tst_tapfail.moc"
void tst_qpromise_tapfail::fulfilled()
{
int value = -1;
auto p = QPromise<int>::resolve(42).tapFail([&]() {
value = 43;
});
QCOMPARE(waitForValue(p, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, -1);
}
void tst_qpromise_tapfail::fulfilled_void()
{
int value = -1;
auto p = QPromise<void>::resolve().tapFail([&]() {
value = 43;
});
QCOMPARE(waitForValue(p, -1, 42), 42);
QCOMPARE(p.isFulfilled(), true);
QCOMPARE(value, -1);
}
void tst_qpromise_tapfail::rejected()
{
QStringList errors;
auto p0 = QPromise<int>::reject(QString("foo"))
.tapFail([&](const QString& err) {
errors << "1:" + err;
});
auto p1 = p0
.fail([&](const QString& err) {
errors << "2:" + err;
return 43;
});
QCOMPARE(waitForError(p0, QString()), QString("foo"));
QCOMPARE(waitForValue(p1, -1), 43);
QCOMPARE(p0.isRejected(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(errors, QStringList() << "1:foo" << "2:foo");
}
void tst_qpromise_tapfail::rejected_void()
{
QStringList errors;
auto p0 = QPromise<void>::reject(QString("foo"))
.tapFail([&](const QString& err) {
errors << "1:" + err;
});
auto p1 = p0
.fail([&](const QString& err) {
errors << "2:" + err;
});
QCOMPARE(waitForError(p0, QString()), QString("foo"));
QCOMPARE(waitForValue(p1, -1, 43), 43);
QCOMPARE(p0.isRejected(), true);
QCOMPARE(p1.isFulfilled(), true);
QCOMPARE(errors, QStringList() << "1:foo" << "2:foo");
}
void tst_qpromise_tapfail::throws()
{
auto p = QPromise<int>::reject(QString("foo"))
.tapFail([&]() {
throw QString("bar");
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_tapfail::throws_void()
{
auto p = QPromise<void>::reject(QString("foo"))
.tapFail([&]() {
throw QString("bar");
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(p.isRejected(), true);
}
void tst_qpromise_tapfail::delayedResolved()
{
QVector<int> values;
auto p = QPromise<int>::reject(QString("foo"))
.tapFail([&]() {
QPromise<void> p([&](const QPromiseResolve<void>& resolve) {
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 3;
resolve(); // ignored!
});
});
values << 2;
return p;
});
QCOMPARE(waitForError(p, QString()), QString("foo"));
QCOMPARE(values, QVector<int>({2, 3}));
}
void tst_qpromise_tapfail::delayedRejected()
{
QVector<int> values;
auto p = QPromise<int>::reject(QString("foo"))
.tapFail([&]() {
QPromise<void> p([&](
const QPromiseResolve<void>&,
const QPromiseReject<void>& reject){
QtPromisePrivate::qtpromise_defer([=, &values]() {
values << 3;
reject(QString("bar"));
});
});
values << 2;
return p;
});
QCOMPARE(waitForError(p, QString()), QString("bar"));
QCOMPARE(values, QVector<int>({2, 3}));
}

View File

@ -1,10 +1,15 @@
TEMPLATE = app TEMPLATE = app
CONFIG += testcase CONFIG += testcase warn_on
QT += testlib QT += testlib
QT -= gui QT -= gui
DEFINES += QT_DEPRECATED_WARNINGS DEFINES += QT_DEPRECATED_WARNINGS
# Additional warnings and make all warnings into errors
# https://github.com/simonbrunel/qtpromise/issues/10
gcc:QMAKE_CXXFLAGS += -Werror -Wold-style-cast
msvc:QMAKE_CXXFLAGS -= -WX
coverage { coverage {
gcc { gcc {
message("Code coverage enabled (gcov)") message("Code coverage enabled (gcov)")

View File

@ -28,159 +28,159 @@ QTEST_MAIN(tst_thread)
void tst_thread::resolve() void tst_thread::resolve()
{ {
int value = -1; int value = -1;
size_t target = 0; QThread* target = nullptr;
size_t source = 0; QThread* source = nullptr;
QPromise<int>([&](const QPromiseResolve<int>& resolve) { QPromise<int>([&](const QPromiseResolve<int>& resolve) {
QtConcurrent::run([=, &source]() { QtConcurrent::run([=, &source]() {
source = (size_t)QThread::currentThread(); source = QThread::currentThread();
resolve(42); resolve(42);
}); });
}).then([&](int res) { }).then([&](int res) {
target = (size_t)QThread::currentThread(); target = QThread::currentThread();
value = res; value = res;
}).wait(); }).wait();
QVERIFY(source != 0); QVERIFY(source != nullptr);
QVERIFY(source != target); QVERIFY(source != target);
QCOMPARE(target, (size_t)QThread::currentThread()); QCOMPARE(target, QThread::currentThread());
QCOMPARE(value, 42); QCOMPARE(value, 42);
} }
void tst_thread::resolve_void() void tst_thread::resolve_void()
{ {
int value = -1; int value = -1;
size_t target = 0; QThread* target = nullptr;
size_t source = 0; QThread* source = nullptr;
QPromise<void>([&](const QPromiseResolve<void>& resolve) { QPromise<void>([&](const QPromiseResolve<void>& resolve) {
QtConcurrent::run([=, &source]() { QtConcurrent::run([=, &source]() {
source = (size_t)QThread::currentThread(); source = QThread::currentThread();
resolve(); resolve();
}); });
}).then([&]() { }).then([&]() {
target = (size_t)QThread::currentThread(); target = QThread::currentThread();
value = 43; value = 43;
}).wait(); }).wait();
QVERIFY(source != 0); QVERIFY(source != nullptr);
QVERIFY(source != target); QVERIFY(source != target);
QCOMPARE(target, (size_t)QThread::currentThread()); QCOMPARE(target, QThread::currentThread());
QCOMPARE(value, 43); QCOMPARE(value, 43);
} }
void tst_thread::reject() void tst_thread::reject()
{ {
QString error; QString error;
size_t target = 0; QThread* target = nullptr;
size_t source = 0; QThread* source = nullptr;
QPromise<int>([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) { QPromise<int>([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QtConcurrent::run([=, &source]() { QtConcurrent::run([=, &source]() {
source = (size_t)QThread::currentThread(); source = QThread::currentThread();
reject(QString("foo")); reject(QString("foo"));
}); });
}).fail([&](const QString& err) { }).fail([&](const QString& err) {
target = (size_t)QThread::currentThread(); target = QThread::currentThread();
error = err; error = err;
return -1; return -1;
}).wait(); }).wait();
QVERIFY(source != 0); QVERIFY(source != nullptr);
QVERIFY(source != target); QVERIFY(source != target);
QCOMPARE(target, (size_t)QThread::currentThread()); QCOMPARE(target, QThread::currentThread());
QCOMPARE(error, QString("foo")); QCOMPARE(error, QString("foo"));
} }
void tst_thread::then() void tst_thread::then()
{ {
size_t source; QThread* source = nullptr;
QPromise<int> p([&](const QPromiseResolve<int>& resolve) { QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
source = (size_t)QThread::currentThread(); source = QThread::currentThread();
resolve(42); resolve(42);
}); });
size_t target;
int value = -1; int value = -1;
QThread* target = nullptr;
qPromise(QtConcurrent::run([&](const QPromise<int>& p) { qPromise(QtConcurrent::run([&](const QPromise<int>& p) {
p.then([&](int res) { p.then([&](int res) {
target = (size_t)QThread::currentThread(); target = QThread::currentThread();
value = res; value = res;
}).wait(); }).wait();
}, p)).wait(); }, p)).wait();
QVERIFY(target != 0); QVERIFY(target != nullptr);
QVERIFY(source != target); QVERIFY(source != target);
QCOMPARE(source, (size_t)QThread::currentThread()); QCOMPARE(source, QThread::currentThread());
QCOMPARE(value, 42); QCOMPARE(value, 42);
} }
void tst_thread::then_void() void tst_thread::then_void()
{ {
size_t source; QThread* source = nullptr;
QPromise<void> p([&](const QPromiseResolve<void>& resolve) { QPromise<void> p([&](const QPromiseResolve<void>& resolve) {
source = (size_t)QThread::currentThread(); source = QThread::currentThread();
resolve(); resolve();
}); });
size_t target;
int value = -1; int value = -1;
QThread* target = nullptr;
qPromise(QtConcurrent::run([&](const QPromise<void>& p) { qPromise(QtConcurrent::run([&](const QPromise<void>& p) {
p.then([&]() { p.then([&]() {
target = (size_t)QThread::currentThread(); target = QThread::currentThread();
value = 43; value = 43;
}).wait(); }).wait();
}, p)).wait(); }, p)).wait();
QVERIFY(target != 0); QVERIFY(target != nullptr);
QVERIFY(source != target); QVERIFY(source != target);
QCOMPARE(source, (size_t)QThread::currentThread()); QCOMPARE(source, QThread::currentThread());
QCOMPARE(value, 43); QCOMPARE(value, 43);
} }
void tst_thread::fail() void tst_thread::fail()
{ {
size_t source; QThread* source = nullptr;
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) { QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
source = (size_t)QThread::currentThread(); source = QThread::currentThread();
reject(QString("foo")); reject(QString("foo"));
}); });
size_t target;
QString error; QString error;
QThread* target = nullptr;
qPromise(QtConcurrent::run([&](const QPromise<int>& p) { qPromise(QtConcurrent::run([&](const QPromise<int>& p) {
p.fail([&](const QString& err) { p.fail([&](const QString& err) {
target = (size_t)QThread::currentThread(); target = QThread::currentThread();
error = err; error = err;
return -1; return -1;
}).wait(); }).wait();
}, p)).wait(); }, p)).wait();
QVERIFY(target != 0); QVERIFY(target != nullptr);
QVERIFY(source != target); QVERIFY(source != target);
QCOMPARE(source, (size_t)QThread::currentThread()); QCOMPARE(source, QThread::currentThread());
QCOMPARE(error, QString("foo")); QCOMPARE(error, QString("foo"));
} }
void tst_thread::finally() void tst_thread::finally()
{ {
size_t source; QThread* source = nullptr;
QPromise<int> p([&](const QPromiseResolve<int>& resolve) { QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
source = (size_t)QThread::currentThread(); source = QThread::currentThread();
resolve(42); resolve(42);
}); });
size_t target;
int value = -1; int value = -1;
QThread* target = nullptr;
qPromise(QtConcurrent::run([&](const QPromise<int>& p) { qPromise(QtConcurrent::run([&](const QPromise<int>& p) {
p.finally([&]() { p.finally([&]() {
target = (size_t)QThread::currentThread(); target = QThread::currentThread();
value = 43; value = 43;
}).wait(); }).wait();
}, p)).wait(); }, p)).wait();
QVERIFY(target != 0); QVERIFY(target != nullptr);
QVERIFY(source != target); QVERIFY(source != target);
QCOMPARE(source, (size_t)QThread::currentThread()); QCOMPARE(source, QThread::currentThread());
QCOMPARE(value, 43); QCOMPARE(value, 43);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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