From ce3ed72dd402e28037f0331ed3862dc37d00e34f Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 20 May 2017 09:40:42 +0200 Subject: [PATCH] Promise creation from callback only (resolver) Make sure that the promise state can only be changed by the promise producer (and not consumers) by removing the `fulfill` and `reject` methods from the instance members and introducing a new constructor accepting a resolver lambda. That also means that a promise can't anymore be default constructed. Add the static `QPromise::resolve` and `QPromise::reject` methods to create synchronously fulfilled or rejected promises, and fix the `qPromise` helper to handle deduced promises (e.g. `qPromise(QFuture()) -> QPromise`). --- src/qtpromise/qpromise.h | 60 ++-- src/qtpromise/qpromise.inl | 307 +++++++++++++------ src/qtpromise/qpromise_p.h | 230 +++++++------- src/qtpromise/qpromise_qfuture.inl | 77 ++--- src/qtpromise/qpromiseerror.h | 42 +++ src/qtpromise/qpromiseglobal.h | 47 +-- src/qtpromise/qtpromise.pri | 1 + tests/auto/qpromise/tst_qpromise.cpp | 137 ++++----- tests/auto/requirements/tst_requirements.cpp | 265 +++++++++------- 9 files changed, 677 insertions(+), 489 deletions(-) create mode 100644 src/qtpromise/qpromiseerror.h diff --git a/src/qtpromise/qpromise.h b/src/qtpromise/qpromise.h index fb80f15..ccdd055 100644 --- a/src/qtpromise/qpromise.h +++ b/src/qtpromise/qpromise.h @@ -1,25 +1,21 @@ #ifndef _QTPROMISE_QPROMISE_H #define _QTPROMISE_QPROMISE_H -// QPromise +// QtPromise #include "qpromise_p.h" #include "qpromiseglobal.h" // Qt #include -#include namespace QtPromise { -using namespace QtPromisePrivate; - template class QPromiseBase { public: using Type = T; - QPromiseBase() : m_d(new QPromiseData()) { } virtual ~QPromiseBase() { } bool isFulfilled() const { return m_d->resolved && !m_d->rejected; } @@ -27,22 +23,34 @@ public: bool isPending() const { return !m_d->resolved; } template - inline auto then(TFulfilled fulfilled, TRejected rejected = nullptr) - -> typename QPromiseHandler::Promise; + inline typename QtPromisePrivate::PromiseHandler::Promise + then(TFulfilled fulfilled, TRejected rejected = nullptr); template - inline auto fail(TRejected rejected) - -> typename QPromiseHandler::Promise; - - template - inline QPromise reject(const TError& error); + inline typename QtPromisePrivate::PromiseHandler::Promise + fail(TRejected rejected); inline QPromise wait() const; -protected: - QExplicitlySharedDataPointer > m_d; +public: // STATIC + template + inline static QPromise reject(const E& error); - virtual void notify(const typename QPromiseData::HandlerList& handlers) const = 0; +protected: + friend class QPromiseResolve; + friend class QPromiseReject; + + QExplicitlySharedDataPointer > m_d; + + inline QPromiseBase(); + + template ::count == 1, int>::type = 0> + inline QPromiseBase(F resolver); + + template ::count != 1, int>::type = 0> + inline QPromiseBase(F resolver); + + virtual void notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const = 0; inline void dispatch(); }; @@ -51,42 +59,41 @@ template class QPromise: public QPromiseBase { public: - QPromise() : QPromiseBase() {} + template + QPromise(F resolver): QPromiseBase(resolver) { } template inline QPromise finally(THandler handler); - inline QPromise fulfill(const T& value); - public: // STATIC inline static QPromise > all(const QVector >& promises); + inline static QPromise resolve(const T& value); protected: - inline void notify(const typename QPromiseData::HandlerList& handlers) const Q_DECL_OVERRIDE; + inline void notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const Q_DECL_OVERRIDE; private: friend class QPromiseBase; QPromise(const QPromiseBase& p) : QPromiseBase(p) { } - }; template <> class QPromise: public QPromiseBase { public: - QPromise(): QPromiseBase() {} + template + QPromise(F resolver): QPromiseBase(resolver) { } template inline QPromise finally(THandler handler); - inline QPromise fulfill(); - public: // STATIC inline static QPromise all(const QVector >& promises); + inline static QPromise resolve(); protected: - inline void notify(const typename QPromiseData::HandlerList& handlers) const Q_DECL_OVERRIDE; + inline void notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const Q_DECL_OVERRIDE; private: friend class QPromiseBase; @@ -94,13 +101,8 @@ private: QPromise(const QPromiseBase& p) : QPromiseBase(p) { } }; -using QVariantPromise = QtPromise::QPromise; - } // namespace QtPromise -Q_DECLARE_TYPEINFO(QtPromise::QVariantPromise, Q_MOVABLE_TYPE); -Q_DECLARE_METATYPE(QtPromise::QVariantPromise) - #include "qpromise.inl" #include "qpromise_qfuture.inl" diff --git a/src/qtpromise/qpromise.inl b/src/qtpromise/qpromise.inl index 3736c7b..42b5264 100644 --- a/src/qtpromise/qpromise.inl +++ b/src/qtpromise/qpromise.inl @@ -4,15 +4,91 @@ namespace QtPromise { +template +class QPromiseResolve +{ +public: + QPromiseResolve(const QPromise& p) + : m_promise(p) + { } + + void operator()(const T& value) const + { + auto p = m_promise; + if (p.isPending()) { + p.m_d->rejected = false; + p.m_d->resolved = true; + p.m_d->value = value; + p.dispatch(); + } + } + +private: + QPromise m_promise; +}; + +template <> +class QPromiseResolve +{ +public: + QPromiseResolve(const QPromise& p) + : m_promise(p) + { } + + void operator()() const + { + auto p = m_promise; + if (p.isPending()) { + p.m_d->rejected = false; + p.m_d->resolved = true; + p.dispatch(); + } + } + +private: + QPromise m_promise; +}; + +template +class QPromiseReject +{ +public: + QPromiseReject() + { } + + QPromiseReject(const QPromise& p) + : m_promise(p) + { } + + void operator()(const QPromiseError& error) const + { + auto p = m_promise; + if (p.isPending()) { + p.m_d->rejected = true; + p.m_d->resolved = true; + p.m_d->error = error; + p.dispatch(); + } + } + +private: + QPromise m_promise; +}; + template template -inline auto QPromiseBase::then(TFulfilled fulfilled, TRejected rejected) - -> typename QPromiseHandler::Promise +inline typename QtPromisePrivate::PromiseHandler::Promise +QPromiseBase::then(TFulfilled fulfilled, TRejected rejected) { - typename QPromiseHandler::Promise next; + using namespace QtPromisePrivate; + using PromiseType = typename PromiseHandler::Promise; - m_d->handlers << QPromiseHandler::create(next, fulfilled); - m_d->catchers << QPromiseCatcher::create(next, rejected); + PromiseType next([=]( + const QPromiseResolve& resolve, + const QPromiseReject& reject) { + m_d->handlers << PromiseHandler::create(fulfilled, resolve, reject); + m_d->catchers << PromiseCatcher::create(rejected, resolve, reject); + }); if (m_d->resolved) { dispatch(); @@ -23,26 +99,12 @@ inline auto QPromiseBase::then(TFulfilled fulfilled, TRejected rejected) template template -inline auto QPromiseBase::fail(TRejected rejected) - -> typename QPromiseHandler::Promise +inline typename QtPromisePrivate::PromiseHandler::Promise +QPromiseBase::fail(TRejected rejected) { return then(nullptr, rejected); } -template -template -inline QPromise QPromiseBase::reject(const TError& error) -{ - if (!m_d->resolved) { - m_d->error = QtPromisePrivate::to_exception_ptr(error); - m_d->rejected = true; - m_d->resolved = true; - dispatch(); - } - - return *this; -} - template inline QPromise QPromiseBase::wait() const { @@ -55,20 +117,67 @@ inline QPromise QPromiseBase::wait() const return *this; } +template +template +inline QPromise QPromiseBase::reject(const E& error) +{ + return QPromise([=](const QPromiseResolve&) { + throw error; + }); +} + +template +inline QPromiseBase::QPromiseBase() + : m_d(new QtPromisePrivate::PromiseData()) +{ +} + +template +template ::count == 1, int>::type> +inline QPromiseBase::QPromiseBase(F resolver) + : m_d(new QtPromisePrivate::PromiseData()) +{ + auto resolve = QPromiseResolve(*this); + auto reject = QPromiseReject(*this); + + try { + resolver(resolve); + } catch(...) { + reject(std::current_exception()); + } +} + +template +template ::count != 1, int>::type> +inline QPromiseBase::QPromiseBase(F resolver) + : m_d(new QtPromisePrivate::PromiseData()) +{ + auto resolve = QPromiseResolve(*this); + auto reject = QPromiseReject(*this); + + try { + resolver(resolve, reject); + } catch(...) { + reject(std::current_exception()); + } +} + template inline void QPromiseBase::dispatch() { + using namespace QtPromisePrivate; + Q_ASSERT(m_d->resolved); - typename QPromiseData::HandlerList handlers; - typename QPromiseData::CatcherList catchers; + typename PromiseData::HandlerList handlers; + typename PromiseData::CatcherList catchers; handlers.swap(m_d->handlers); catchers.swap(m_d->catchers); if (m_d->rejected) { - const std::exception_ptr error = m_d->error; - qtpromise_defer([catchers, error]() { + const QPromiseError error = m_d->error; + qtpromise_defer([=]() { for (auto catcher: catchers) { catcher(error); } @@ -83,67 +192,62 @@ template inline QPromise QPromise::finally(THandler handler) { return this->then([=](const T& res) { - return QPromise().fulfill().then(handler).then([=](){ + return QPromise::resolve().then(handler).then([=](){ return res; }); }, [=]() { const auto exception = std::current_exception(); - return QPromise().fulfill().then(handler).then([=](){ + return QPromise::resolve().then(handler).then([=](){ std::rethrow_exception(exception); return T(); }); }); } -template -inline QPromise QPromise::fulfill(const T& value) -{ - if (!this->m_d->resolved) { - this->m_d->rejected = false; - this->m_d->resolved = true; - this->m_d->value = value; - this->dispatch(); - } - - return *this; -} - template inline QPromise > QPromise::all(const QVector >& promises) { - QPromise > next; - const int count = promises.size(); if (count == 0) { - return next.fulfill({}); + return QPromise >::resolve({}); } - QSharedPointer remaining(new int(count)); - QSharedPointer > results(new QVector(count)); + return QPromise >([=]( + const QPromiseResolve >& resolve, + const QPromiseReject >& reject) { - for (int i=0; i(promises[i]).then([=](const T& res) mutable { - if (next.isPending()) { + QSharedPointer remaining(new int(count)); + QSharedPointer > results(new QVector(count)); + + for (int i=0; i(promises[i]).then([=](const T& res) mutable { (*results)[i] = res; if (--(*remaining) == 0) { - next.fulfill(*results); + resolve(*results); } - } - }, [=]() mutable { - if (next.isPending()) { - next.reject(std::current_exception()); - } - }); - } - - return next; + }, [=]() mutable { + if (*remaining != -1) { + *remaining = -1; + reject(std::current_exception()); + } + }); + } + }); } template -inline void QPromise::notify(const typename QPromiseData::HandlerList& handlers) const +inline QPromise QPromise::resolve(const T& value) +{ + return QPromise([=](const QPromiseResolve& resolve) { + resolve(value); + }); +} + +template +inline void QPromise::notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const { const T value = this->m_d->value; - qtpromise_defer([handlers, value]() { + QtPromisePrivate::qtpromise_defer([handlers, value]() { for (auto handler: handlers) { handler(value); } @@ -154,52 +258,53 @@ template inline QPromise QPromise::finally(THandler handler) { return this->then([=]() { - return QPromise().fulfill().then(handler).then([](){}); + return QPromise::resolve().then(handler).then([](){}); }, [=]() { const auto exception = std::current_exception(); - return QPromise().fulfill().then(handler).then([=](){ + return QPromise::resolve().then(handler).then([=](){ std::rethrow_exception(exception); }); }); } -inline QPromise QPromise::fulfill() -{ - if (!m_d->resolved) { - m_d->rejected = false; - m_d->resolved = true; - dispatch(); - } - - return *this; -} - inline QPromise QPromise::all(const QVector >& promises) { - QPromise next; - - QSharedPointer remaining(new int(promises.size())); - - for (const auto& promise: promises) { - QPromise(promise).then([=]() mutable { - if (next.isPending()) { - if (--(*remaining) == 0) { - next.fulfill(); - } - } - }, [=]() mutable { - if (next.isPending()) { - next.reject(std::current_exception()); - } - }); + const int count = promises.size(); + if (count == 0) { + return QPromise::resolve(); } - return next; + return QPromise([=]( + const QPromiseResolve& resolve, + const QPromiseReject& reject) { + + QSharedPointer remaining(new int(promises.size())); + + for (const auto& promise: promises) { + QPromise(promise).then([=]() { + if (--(*remaining) == 0) { + resolve(); + } + }, [=]() { + if (*remaining != -1) { + *remaining = -1; + reject(std::current_exception()); + } + }); + } + }); } -inline void QPromise::notify(const typename QPromiseData::HandlerList& handlers) const +inline QPromise QPromise::resolve() { - qtpromise_defer([handlers]() { + return QPromise([](const QPromiseResolve& resolve) { + resolve(); + }); +} + +inline void QPromise::notify(const typename QtPromisePrivate::PromiseData::HandlerList& handlers) const +{ + QtPromisePrivate::qtpromise_defer([handlers]() { for (const auto& handler: handlers) { handler(); } @@ -209,9 +314,23 @@ inline void QPromise::notify(const typename QPromiseData::HandlerLis // Helpers template -QPromise qPromise(const T& value) +typename QtPromisePrivate::PromiseDeduce::Type qPromise(const T& value) { - return QPromise().fulfill(value); + using namespace QtPromisePrivate; + using Promise = typename PromiseDeduce::Type; + return Promise([=]( + const QPromiseResolve& resolve, + const QPromiseReject& reject) { + PromiseFulfill::call(value, resolve, reject); + }); +} + +QPromise qPromise() +{ + return QPromise([]( + const QPromiseResolve& resolve) { + resolve(); + }); } template diff --git a/src/qtpromise/qpromise_p.h b/src/qtpromise/qpromise_p.h index d8e49b7..eddc2aa 100644 --- a/src/qtpromise/qpromise_p.h +++ b/src/qtpromise/qpromise_p.h @@ -1,7 +1,8 @@ #ifndef _QTPROMISE_QPROMISE_P_H #define _QTPROMISE_QPROMISE_P_H -// QPromise +// QtPromise +#include "qpromiseerror.h" #include "qpromiseglobal.h" // Qt @@ -10,18 +11,21 @@ #include #include -// STL -#include - namespace QtPromise { -template + +template class QPromise; -} + +template +class QPromiseResolve; + +template +class QPromiseReject; + +} // namespace QtPromise namespace QtPromisePrivate { -using namespace QtPromise; - template inline void qtpromise_defer(F f) { @@ -29,265 +33,269 @@ inline void qtpromise_defer(F f) } template -struct QPromiseDeduce +struct PromiseDeduce { - using Type = QPromise >; + using Type = QtPromise::QPromise >; }; template -struct QPromiseDeduce > - : public QPromiseDeduce +struct PromiseDeduce > + : public PromiseDeduce { }; -template ::Type> -struct QPromiseFulfill +template +struct PromiseFulfill { - static void call(TPromise next, const T& value) + static void call( + const T& value, + const QtPromise::QPromiseResolve& resolve, + const QtPromise::QPromiseReject&) { - next.fulfill(value); + resolve(value); } }; template -struct QPromiseFulfill, QPromise > +struct PromiseFulfill > { - static void call(QPromise next, QPromise promise) + static void call( + QtPromise::QPromise promise, + const QtPromise::QPromiseResolve& resolve, + const QtPromise::QPromiseReject& reject) { promise.then( - [=](const T& value) mutable { - next.fulfill(value); + [=](const T& value) { + resolve(value); }, - [=]() mutable { - next.reject(std::current_exception()); + [=]() { // catch all + reject(std::current_exception()); }); } }; template <> -struct QPromiseFulfill, QPromise > +struct PromiseFulfill > { - template - static void call(TPromise next, TPromise promise) + template + static void call(TPromise promise, TResolve resolve, TReject reject) { promise.then( - [=]() mutable { - next.fulfill(); + [=]() { + resolve(); }, - [=]() mutable { - next.reject(std::current_exception()); + [=]() { // catch all + reject(std::current_exception()); }); } }; template -struct QPromiseDispatch +struct PromiseDispatch { - using Promise = typename QPromiseDeduce::Type; + using Promise = typename PromiseDeduce::Type; using ResType = Unqualified; - template - static void call(TPromise next, THandler handler, const T& value) + template + static void call(const T& value, THandler handler, TResolve resolve, TReject reject) { - ResType res; try { - res = handler(value); + const ResType res = handler(value); + PromiseFulfill::call(res, resolve, reject); } catch (...) { - next.reject(std::current_exception()); - return; + reject(std::current_exception()); } - QPromiseFulfill::call(next, res); } }; template -struct QPromiseDispatch +struct PromiseDispatch { - using Promise = QPromise; + using Promise = QtPromise::QPromise; - template - static void call(TPromise next, THandler handler, const T& value) + template + static void call(const T& value, THandler handler, TResolve resolve, TReject reject) { try { handler(value); + resolve(); } catch (...) { - next.reject(std::current_exception()); - return; + reject(std::current_exception()); } - next.fulfill(); } }; template -struct QPromiseDispatch +struct PromiseDispatch { - using Promise = typename QPromiseDeduce::Type; + using Promise = typename PromiseDeduce::Type; using ResType = Unqualified; - template - static void call(TPromise next, THandler handler) + template + static void call(THandler handler, TResolve resolve, TReject reject) { - ResType res; try { - res = handler(); + const ResType res = handler(); + PromiseFulfill::call(res, resolve, reject); } catch (...) { - next.reject(std::current_exception()); - return; + reject(std::current_exception()); } - QPromiseFulfill::call(next, res); } }; template <> -struct QPromiseDispatch +struct PromiseDispatch { - using Promise = QPromise; + using Promise = QtPromise::QPromise; - template - static void call(TPromise next, THandler handler) + template + static void call(THandler handler, TResolve resolve, TReject reject) { try { handler(); + resolve(); } catch (...) { - next.reject(std::current_exception()); - return; + reject(std::current_exception()); } - next.fulfill(); } }; template ::first> -struct QPromiseHandler +struct PromiseHandler { using ResType = typename std::result_of::type; - using Promise = typename QPromiseDispatch::Promise; + using Promise = typename PromiseDispatch::Promise; - static std::function create(const Promise& next, THandler handler) + template + static std::function create(THandler handler, TResolve resolve, TReject reject) { - return [=](const T& value) mutable { - QPromiseDispatch::call(next, handler, value); + return [=](const T& value) { + PromiseDispatch::call(value, handler, resolve, reject); }; } }; template -struct QPromiseHandler +struct PromiseHandler { using ResType = typename std::result_of::type; - using Promise = typename QPromiseDispatch::Promise; + using Promise = typename PromiseDispatch::Promise; - static std::function create(const Promise& next, THandler handler) + template + static std::function create(THandler handler, TResolve resolve, TReject reject) { - return [=](const T&) mutable { - QPromiseDispatch::call(next, handler); + return [=](const T&) { + PromiseDispatch::call(handler, resolve, reject); }; } }; template -struct QPromiseHandler +struct PromiseHandler { using ResType = typename std::result_of::type; - using Promise = typename QPromiseDispatch::Promise; + using Promise = typename PromiseDispatch::Promise; - static std::function create(const Promise& next, THandler handler) + template + static std::function create(THandler handler, TResolve resolve, TReject reject) { return [=]() { - QPromiseDispatch::call(next, handler); + PromiseDispatch::call(handler, resolve, reject); }; } }; template -struct QPromiseHandler +struct PromiseHandler { - using Promise = QPromise; + using Promise = QtPromise::QPromise; - static std::function create(const Promise& next, std::nullptr_t) + template + static std::function create(std::nullptr_t, TResolve resolve, TReject reject) { - return [next](const T& value) mutable { + return [=](const T& value) { // 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled, // promise2 must be fulfilled with the same value as promise1. - QPromiseFulfill::call(next, value); + PromiseFulfill::call(value, resolve, reject); }; } }; template <> -struct QPromiseHandler +struct PromiseHandler { - using Promise = QPromise; + using Promise = QtPromise::QPromise; - template - static std::function create(const TPromise& next, std::nullptr_t) + template + static std::function create(std::nullptr_t, TResolve resolve, TReject) { - return [=]() mutable { + return [=]() { // 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled, // promise2 must be fulfilled with the same value as promise1. - TPromise(next).fulfill(); + resolve(); }; } }; template ::first> -struct QPromiseCatcher +struct PromiseCatcher { - using Type = std::function; + using Functor = std::function; using ResType = typename std::result_of::type; - template - static Type create(const TPromise& next, THandler handler) + template + static Functor create(THandler handler, TResolve resolve, TReject reject) { - return [=](const std::exception_ptr& eptr) mutable { + return [=](const QtPromise::QPromiseError& error) { try { - std::rethrow_exception(eptr); + error.rethrow(); } catch (const TArg& error) { - QPromiseDispatch::call(next, handler, error); + PromiseDispatch::call(error, handler, resolve, reject); } catch(...) { - TPromise(next).reject(std::current_exception()); + reject(std::current_exception()); } }; } }; template -struct QPromiseCatcher +struct PromiseCatcher { - using Type = std::function; + using Functor = std::function; using ResType = typename std::result_of::type; - template - static Type create(const TPromise& next, THandler handler) + template + static Functor create(THandler handler, TResolve resolve, TReject reject) { - return [=](const std::exception_ptr& eptr) mutable { + return [=](const QtPromise::QPromiseError& error) { try { - std::rethrow_exception(eptr); + error.rethrow(); } catch (...) { - QPromiseDispatch::call(next, handler); + PromiseDispatch::call(handler, resolve, reject); } }; } }; template -struct QPromiseCatcher +struct PromiseCatcher { - using Type = std::function; + using Functor = std::function; - template - static Type create(const TPromise& next, std::nullptr_t) + template + static Functor create(std::nullptr_t, TResolve, TReject reject) { - return [=](const std::exception_ptr& eptr) mutable { + return [=](const QtPromise::QPromiseError& 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 - TPromise(next).reject(eptr); + reject(error); }; } }; template -struct QPromiseDataBase: public QSharedData +struct PromiseDataBase: public QSharedData { - using ErrorType = std::exception_ptr; + using ErrorType = QtPromise::QPromiseError; using CatcherList = QVector >; bool resolved; @@ -297,7 +305,7 @@ struct QPromiseDataBase: public QSharedData }; template -struct QPromiseData: QPromiseDataBase +struct PromiseData: PromiseDataBase { using HandlerList = QVector >; @@ -306,7 +314,7 @@ struct QPromiseData: QPromiseDataBase }; template <> -struct QPromiseData: QPromiseDataBase +struct PromiseData: PromiseDataBase { using HandlerList = QVector >; diff --git a/src/qtpromise/qpromise_qfuture.inl b/src/qtpromise/qpromise_qfuture.inl index cc29194..eec8726 100644 --- a/src/qtpromise/qpromise_qfuture.inl +++ b/src/qtpromise/qpromise_qfuture.inl @@ -4,76 +4,61 @@ namespace QtPromisePrivate { template -struct QPromiseDeduce > - : public QPromiseDeduce +struct PromiseDeduce > + : public PromiseDeduce { }; template -struct QPromiseFulfill, QPromise > +struct PromiseFulfill > { - static void call(QPromise next, const QFuture& future) + static void call( + const QFuture& future, + const QtPromise::QPromiseResolve& resolve, + const QtPromise::QPromiseReject& reject) { using Watcher = QFutureWatcher; Watcher* watcher = new Watcher(); - QObject::connect( - watcher, &Watcher::finished, - [next, watcher]() mutable { - T res; - try { - res = watcher->result(); - } catch(...) { - next.reject(std::current_exception()); - } + QObject::connect(watcher, &Watcher::finished, [=]() mutable { + try { + T res = watcher->result(); + PromiseFulfill::call(res, resolve, reject); + } catch(...) { + reject(std::current_exception()); + } - watcher->deleteLater(); - if (next.isPending()) { - QPromiseFulfill::call(next, res); - } - }); + watcher->deleteLater(); + }); watcher->setFuture(future); } }; template <> -struct QPromiseFulfill, QPromise > +struct PromiseFulfill > { - static void call(QPromise next, const QFuture& future) + static void call( + const QFuture& future, + const QtPromise::QPromiseResolve& resolve, + const QtPromise::QPromiseReject& reject) { using Watcher = QFutureWatcher; Watcher* watcher = new Watcher(); - QObject::connect( - watcher, &Watcher::finished, - [next, watcher]() mutable { - try { - // let's rethrown possibe exception - watcher->waitForFinished(); - } catch(...) { - next.reject(std::current_exception()); - } + QObject::connect(watcher, &Watcher::finished, [=]() mutable { + try { + // let's rethrown possibe exception + watcher->waitForFinished(); + resolve(); + } catch(...) { + reject(std::current_exception()); + } - watcher->deleteLater(); - if (next.isPending()) { - next.fulfill(); - } - }); + watcher->deleteLater(); + }); watcher->setFuture(future); } }; -} // namespace QtPromisePrivate - -namespace QtPromise { - -template -QPromise qPromise(const QFuture& future) -{ - QPromise next; - QPromiseFulfill >::call(next, future); - return next; -} - } // namespace QtPromise diff --git a/src/qtpromise/qpromiseerror.h b/src/qtpromise/qpromiseerror.h new file mode 100644 index 0000000..e21a148 --- /dev/null +++ b/src/qtpromise/qpromiseerror.h @@ -0,0 +1,42 @@ +#ifndef _QTPROMISE_QPROMISEERROR_H +#define _QTPROMISE_QPROMISEERROR_H + +// QtPromise +#include "qpromiseglobal.h" + +namespace QtPromise { + +class QPromiseError +{ +public: + QPromiseError() + : exception(nullptr) + { } + + template + QPromiseError(const T& value) + : exception(nullptr) + { + try { + throw value; + } catch(...) { + exception = std::current_exception(); + } + } + + QPromiseError(const std::exception_ptr& exception) + : exception(exception) + { } + + void rethrow() const + { + std::rethrow_exception(exception); + } + +private: + std::exception_ptr exception; +}; + +} // namespace QtPromise + +#endif // _QTPROMISE_QPROMISEERROR_H diff --git a/src/qtpromise/qpromiseglobal.h b/src/qtpromise/qpromiseglobal.h index bfd1e46..94bea65 100644 --- a/src/qtpromise/qpromiseglobal.h +++ b/src/qtpromise/qpromiseglobal.h @@ -6,6 +6,7 @@ // STL #include +#include namespace QtPromisePrivate { @@ -13,6 +14,22 @@ namespace QtPromisePrivate template using Unqualified = typename std::remove_cv::type>::type; +/*! + * \struct HasCallOperator + * http://stackoverflow.com/a/5839442 + */ +template +struct HasCallOperator +{ + template + static auto check(const U* u) + -> decltype(&U::operator(), char(0)); + + static std::array check(...); + + static const bool value = (sizeof(check((T*)0)) == 1); +}; + /*! * \struct ArgsOf * http://stackoverflow.com/a/7943765 @@ -23,6 +40,7 @@ struct ArgsTraits { using types = std::tuple; using first = typename std::tuple_element<0, types>::type; + static const size_t count = std::tuple_size::value; }; template <> @@ -30,14 +48,16 @@ struct ArgsTraits<> { using types = std::tuple<>; using first = void; + static const size_t count = 0; }; -template -struct ArgsOf : public ArgsOf +template +struct ArgsOf : public ArgsTraits<> { }; -template <> -struct ArgsOf : public ArgsTraits<> +template +struct ArgsOf::value>::type> + : public ArgsOf { }; template @@ -100,25 +120,6 @@ template struct ArgsOf : public ArgsOf { }; -/*! - * \fn to_exception_ptr - */ -template -std::exception_ptr to_exception_ptr(const T& value) -{ - try { - throw value; - } catch(...) { - return std::current_exception(); - } -} - -template <> -std::exception_ptr to_exception_ptr(const std::exception_ptr& value) -{ - return value; -} - } // namespace QtPromisePrivate #endif // ifndef _QTPROMISE_QPROMISEGLOBAL_H diff --git a/src/qtpromise/qtpromise.pri b/src/qtpromise/qtpromise.pri index ed263e4..a941102 100644 --- a/src/qtpromise/qtpromise.pri +++ b/src/qtpromise/qtpromise.pri @@ -3,4 +3,5 @@ HEADERS += \ $$PWD/qpromise.inl \ $$PWD/qpromise_p.h \ $$PWD/qpromise_qfuture.inl \ + $$PWD/qpromiseerror.h \ $$PWD/qpromiseglobal.h diff --git a/tests/auto/qpromise/tst_qpromise.cpp b/tests/auto/qpromise/tst_qpromise.cpp index 9efbeee..231a986 100644 --- a/tests/auto/qpromise/tst_qpromise.cpp +++ b/tests/auto/qpromise/tst_qpromise.cpp @@ -5,15 +5,13 @@ #include using namespace QtPromise; - -static const int ASYNC_DELAY = 256; +using namespace QtPromisePrivate; class tst_qpromise: public QObject { Q_OBJECT private Q_SLOTS: - void finallyReturns(); void finallyThrows(); void finallyDelayedFulfilled(); @@ -26,75 +24,68 @@ QTEST_MAIN(tst_qpromise) void tst_qpromise::finallyReturns() { - { - QPromise p; + { // fulfilled QVector values; - auto next = p.finally([&values]() { + auto next = QPromise::resolve(42).finally([&]() { values << 8; - return 16; + return 16; // ignored! }); - p.fulfill(42); - next.then([&values](int r) { + next.then([&](int r) { values << r; }).wait(); - QVERIFY(p.isFulfilled()); QVERIFY(next.isFulfilled()); QCOMPARE(values, QVector({8, 42})); } - { - QPromise p; - QVector values; - auto next = p.finally([&values]() { - values << 8; - return 16; + { // rejected + QString error; + int value = -1; + auto next = QPromise([](const QPromiseResolve) { + throw QString("foo"); + }).finally([&]() { + value = 8; + return 16; // ignored! }); - p.reject(QString("foo")); - next.then([&values](int r) { - values << r; + next.fail([&](const QString& err) { + error = err; + return 42; }).wait(); - QVERIFY(p.isRejected()); QVERIFY(next.isRejected()); - QCOMPARE(values, QVector({8})); + QCOMPARE(error, QString("foo")); + QCOMPARE(value, 8); } } void tst_qpromise::finallyThrows() { - { - QPromise p; + { // fulfilled QString error; - auto next = p.finally([]() { + auto next = QPromise::resolve(42).finally([&]() { throw QString("bar"); }); - p.fulfill(42); - next.fail([&error](const QString& err) { + next.fail([&](const QString& err) { error = err; return 0; }).wait(); - QVERIFY(p.isFulfilled()); QVERIFY(next.isRejected()); QCOMPARE(error, QString("bar")); } - { - QPromise p; + { // rejected QString error; - auto next = p.finally([]() { + auto next = QPromise::reject(QString("foo")).finally([&]() { throw QString("bar"); }); - p.reject(QString("foo")); - next.fail([&error](const QString& err) { + next.fail([&](const QString& err) { error = err; return 0; }).wait(); - QVERIFY(p.isRejected()); QVERIFY(next.isRejected()); QCOMPARE(error, QString("bar")); } @@ -102,97 +93,89 @@ void tst_qpromise::finallyThrows() void tst_qpromise::finallyDelayedFulfilled() { - { - QPromise p0; + { // fulfilled QVector values; - auto next = p0.finally([&values]() { - QPromise p1; - QTimer::singleShot(ASYNC_DELAY, [p1, &values]() mutable { - values << 64; - p1.fulfill(16); + auto next = QPromise::resolve(42).finally([&]() { + QPromise p([&](const QPromiseResolve& resolve) { + qtpromise_defer([=, &values]() { + values << 64; + resolve(16); // ignored! + }); }); values << 8; - return p1; + return p; }); - p0.fulfill(42); - next.then([&values](int r) { + next.then([&](int r) { values << r; }).wait(); - QVERIFY(p0.isFulfilled()); QVERIFY(next.isFulfilled()); QCOMPARE(values, QVector({8, 64, 42})); } - { - QPromise p0; + { // rejected + QString error; QVector values; - auto next = p0.finally([&values]() { - QPromise p1; - QTimer::singleShot(ASYNC_DELAY, [p1, &values]() mutable { - values << 64; - p1.fulfill(16); + auto next = QPromise::reject(QString("foo")).finally([&]() { + QPromise p([&](const QPromiseResolve& resolve) { + qtpromise_defer([=, &values]() { + values << 64; + resolve(16); // ignored! + }); }); values << 8; - return p1; + return p; }); - p0.reject(QString("foo")); - next.then([&values](int r) { + next.then([&](int r) { values << r; + }, [&](const QString& err) { + error = err; }).wait(); - QVERIFY(p0.isRejected()); QVERIFY(next.isRejected()); + QCOMPARE(error, QString("foo")); QCOMPARE(values, QVector({8, 64})); } } void tst_qpromise::finallyDelayedRejected() { - { - QPromise p0; + { // fulfilled QString error; - auto next = p0.finally([]() { - QPromise p1; - QTimer::singleShot(ASYNC_DELAY, [p1]() mutable { - p1.reject(QString("bar")); + auto next = QPromise::resolve(42).finally([]() { + return QPromise([](const QPromiseResolve&, const QPromiseReject& reject) { + qtpromise_defer([=]() { + reject(QString("bar")); + }); }); - - return p1; }); - p0.fulfill(42); - next.fail([&error](const QString& err) { + next.fail([&](const QString& err) { error = err; return 0; }).wait(); - QVERIFY(p0.isFulfilled()); QVERIFY(next.isRejected()); QCOMPARE(error, QString("bar")); } - { - QPromise p0; + { // rejected QString error; - auto next = p0.finally([]() { - QPromise p1; - QTimer::singleShot(ASYNC_DELAY, [p1]() mutable { - p1.reject(QString("bar")); + auto next = QPromise::reject(QString("foo")).finally([]() { + return QPromise([](const QPromiseResolve&, const QPromiseReject& reject) { + qtpromise_defer([=]() { + reject(QString("bar")); + }); }); - - return p1; }); - p0.reject(QString("foo")); - next.fail([&error](const QString& err) { + next.fail([&](const QString& err) { error = err; return 0; }).wait(); - QVERIFY(p0.isRejected()); QVERIFY(next.isRejected()); QCOMPARE(error, QString("bar")); } diff --git a/tests/auto/requirements/tst_requirements.cpp b/tests/auto/requirements/tst_requirements.cpp index 8832b5e..f6d0110 100644 --- a/tests/auto/requirements/tst_requirements.cpp +++ b/tests/auto/requirements/tst_requirements.cpp @@ -5,6 +5,7 @@ #include using namespace QtPromise; +using namespace QtPromisePrivate; // https://promisesaplus.com/#requirements class tst_requirements: public QObject @@ -37,115 +38,151 @@ QTEST_MAIN(tst_requirements) void tst_requirements::statePending() { // 2.1.1. When pending, a promise: + // 2.1.1.1. may transition to either the fulfilled state { - QPromise<> p; + QPromise p([&](const QPromiseResolve& resolve) { + qtpromise_defer([=]() { resolve(42); }); + }); + QVERIFY(p.isPending()); QVERIFY(!p.isFulfilled()); QVERIFY(!p.isRejected()); - } - // 2.1.1.1. may transition to either the fulfilled state - { - QPromise<> p; - p.fulfill(); + p.wait(); + QVERIFY(!p.isPending()); QVERIFY(p.isFulfilled()); + QVERIFY(!p.isRejected()); } // 2.1.1.1. ... or the rejected state - { - QPromise<> p; - p.reject("foo"); + { + QPromise p([&](const QPromiseResolve&, const QPromiseReject& reject) { + qtpromise_defer([=]() { reject(QString("foo")); }); + }); + + QVERIFY(p.isPending()); + QVERIFY(!p.isFulfilled()); + QVERIFY(!p.isRejected()); + + p.wait(); + QVERIFY(!p.isPending()); + QVERIFY(!p.isFulfilled()); QVERIFY(p.isRejected()); } } void tst_requirements::stateFulfilled() { - QVector values; - auto log_value = [&values](int res) { values << res; }; - - QPromise p; - QVERIFY(p.isPending()); + QString error; + int value = -1; // 2.1.2. When fulfilled, a promise: - p.fulfill(42).then(log_value).wait(); + QPromise p([]( + const QPromiseResolve& resolve, + const QPromiseReject& reject) { + qtpromise_defer([=]() { + // 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(QString("foo")); + }); + }); + + QVERIFY(p.isPending()); + + p.then([&](int res) { + value = res; + }, [&](const QString& err) { + error = err; + }).wait(); + QVERIFY(p.isFulfilled()); QVERIFY(!p.isRejected()); - - // 2.1.2.1. must not transition to any other state. - p.reject("foo").then(log_value).wait(); - QVERIFY(p.isFulfilled()); - QVERIFY(!p.isRejected()); - - // 2.1.2.2. must have a value, which must not change. - p.fulfill(51).then(log_value).wait(); - QVERIFY(p.isFulfilled()); - QVERIFY(!p.isRejected()); - - QCOMPARE(values, QVector({42, 42, 42})); + QVERIFY(error.isEmpty()); + QCOMPARE(value, 42); } void tst_requirements::stateRejected() { - QStringList errors; - auto log_error = [&errors](const QString& err) { errors << err; return -1; }; - - QPromise p; - QVERIFY(p.isPending()); + QString error; + int value = -1; // 2.1.3 When rejected, a promise: - p.reject(QString("foo")).then(nullptr, log_error).wait(); + QPromise p([]( + const QPromiseResolve& resolve, + const QPromiseReject& reject) { + qtpromise_defer([=]() { + // 2.1.3.2. must have a reason, which must not change. + reject(QString("foo")); + reject(QString("bar")); + + // 2.1.3.1. must not transition to any other state. + resolve(42); + }); + }); + + QVERIFY(p.isPending()); + + p.then([&](int res) { + value = res; + }, [&](const QString& err) { + error = err; + }).wait(); + QVERIFY(!p.isFulfilled()); QVERIFY(p.isRejected()); - - // 2.1.3.1. must not transition to any other state. - p.fulfill(51).then(nullptr, log_error).wait(); - QVERIFY(!p.isFulfilled()); - QVERIFY(p.isRejected()); - - // 2.1.3.2. must have a reason, which must not change. - p.reject(QString("bar")).then(nullptr, log_error).wait(); - QVERIFY(!p.isFulfilled()); - QVERIFY(p.isRejected()); - - QCOMPARE(errors, QStringList({"foo", "foo", "foo"})); + QCOMPARE(error, QString("foo")); + QCOMPARE(value, -1); } void tst_requirements::thenArguments() { - // Both onFulfilled and onRejected are given + // 2.2.1. Both onFulfilled and onRejected are given { - int value = 0; - QPromise p; - p.fulfill(42).then([&value](int res) { value = res; }, [](const QString&){}).wait(); - QVERIFY(p.isFulfilled()); + QString error; + int value = -1; + QPromise::resolve(42).then( + [&](int res) { value = res; }, + [&](const QString& err) { error = err; } + ).wait(); + + QVERIFY(error.isEmpty()); QCOMPARE(value, 42); } { QString error; - QPromise p; - p.reject(QString("foo")).then([](int) {}, [&error](const QString& err){ error = err; }).wait(); - QVERIFY(p.isRejected()); + int value = -1; + QPromise::reject(QString("foo")).then( + [&](int res) { value = res; }, + [&](const QString& err){ error = err; } + ).wait(); + QCOMPARE(error, QString("foo")); + QCOMPARE(value, -1); } // 2.2.1. onFulfilled is an optional arguments: { QString error; - QPromise p; - p.reject(QString("foo")).then(nullptr, [&error](const QString& err){ error = err; return 42; }).wait(); - QVERIFY(p.isRejected()); + QPromise::reject(QString("foo")).then( + nullptr, + [&](const QString& err){ error = err; return 42; } + ).wait(); + QCOMPARE(error, QString("foo")); } // 2.2.1. onRejected is an optional arguments: { - int value = 0; - QPromise p; - p.fulfill(42).then([&value](int res) { value = res; }).wait(); - QVERIFY(p.isFulfilled()); + int value = -1; + QPromise::resolve(42).then( + [&value](int res) { value = res; } + ).wait(); + QCOMPARE(value, 42); } @@ -157,55 +194,57 @@ void tst_requirements::thenArguments() void tst_requirements::thenOnFulfilled() { // 2.2.2. If onFulfilled is a function: - - QPromise p0; QVector values; - auto p1 = p0.then([&values](int res) { values << res; }); // @TODO .wait(10) + QPromise p0([](const QPromiseResolve& resolve) { + qtpromise_defer([=]() { + // 2.2.2.3. it must not be called more than once + resolve(42); + resolve(43); + }); + }); + + auto p1 = p0.then([&](int res) { values << res; }); // 2.2.2.2. it must not be called before promise is fulfilled. QVERIFY(p0.isPending()); QVERIFY(p1.isPending()); QVERIFY(values.isEmpty()); + p1.wait(); + // 2.2.2.1. it must be called after promise is fulfilled, // with promise’s value as its first argument. - p0.fulfill(42); - p1.wait(); QVERIFY(p0.isFulfilled()); QVERIFY(p1.isFulfilled()); QCOMPARE(values, QVector({42})); - - // 2.2.2.3. it must not be called more than once - p0.fulfill(12); - p1.wait(); - QCOMPARE(values, QVector({42})); } void tst_requirements::thenOnRejected() { // 2.2.3. If onRejected is a function: - - QPromise<> p0; QStringList errors; - auto p1 = p0.then(nullptr, [&errors](const QString& err) { errors << err; }); // @TODO .wait(10) + QPromise p0([](const QPromiseResolve&, const QPromiseReject& reject) { + qtpromise_defer([=]() { + // 2.2.3.3. it must not be called more than once. + reject(QString("foo")); + reject(QString("bar")); + }); + }); + + auto p1 = p0.then(nullptr, [&](const QString& err) { errors << err; }); // 2.2.3.2. it must not be called before promise is rejected. QVERIFY(p0.isPending()); QVERIFY(p1.isPending()); QVERIFY(errors.isEmpty()); + p1.wait(); + // 2.2.3.1. it must be called after promise is rejected, // with promise’s reason as its first argument. - p0.reject(QString("foo")); - p1.wait(); QVERIFY(p0.isRejected()); QVERIFY(p1.isFulfilled()); QCOMPARE(errors, QStringList({"foo"})); - - // 2.2.2.3. it must not be called more than once. - p0.reject(12); - p1.wait(); - QCOMPARE(errors, QStringList({"foo"})); } void tst_requirements::thenAsynchronous() @@ -214,14 +253,13 @@ void tst_requirements::thenAsynchronous() // stack contains only platform code (ie. executed asynchronously, after the event // loop turn in which then is called, and with a fresh stack). - int value = 0; - QPromise p0; - p0.fulfill(42); + int value = -1; + auto p0 = QPromise::resolve(42); QVERIFY(p0.isFulfilled()); - auto p1 = p0.then([&value](int res){ value = res; }); + auto p1 = p0.then([&](int res){ value = res; }); QVERIFY(p1.isPending()); - QCOMPARE(value, 0); + QCOMPARE(value, -1); p1.wait(); QVERIFY(p1.isFulfilled()); @@ -235,32 +273,38 @@ void tst_requirements::thenMultipleCalls() // 2.2.6.1. If/when promise is fulfilled, all respective onFulfilled callbacks // must execute in the order of their originating calls to then: { - QPromise p; QVector values; - auto all = qPromiseAll(QVector >{ - p.then([&values](int r) { values << r + 1; }), - p.then([&values](int r) { values << r + 2; }), - p.then([&values](int r) { values << r + 3; }) + QPromise p([](const QPromiseResolve& resolve) { + qtpromise_defer([=]() { + resolve(42); + }); }); - p.fulfill(42); - all.wait(); + qPromiseAll(QVector >{ + p.then([&](int r) { values << r + 1; }), + p.then([&](int r) { values << r + 2; }), + p.then([&](int r) { values << r + 3; }) + }).wait(); + QCOMPARE(values, QVector({43, 44, 45})); } // 2.2.6.2. If/when promise is rejected, all respective onRejected callbacks // must execute in the order of their originating calls to then: { - QPromise p; QVector values; - auto all = qPromiseAll(QVector >{ - p.then(nullptr, [&values](int r) { values << r + 1; return r + 1; }), - p.then(nullptr, [&values](int r) { values << r + 2; return r + 2; }), - p.then(nullptr, [&values](int r) { values << r + 3; return r + 3; }) + QPromise p([](const QPromiseResolve&, const QPromiseReject& reject) { + qtpromise_defer([=]() { + reject(8); + }); }); - p.reject(8); - all.wait(); + qPromiseAll(QVector >{ + 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({9, 10, 11})); } } @@ -269,8 +313,8 @@ void tst_requirements::thenHandlers() { // 2.2.7. then must return a promise: p2 = p1.then(onFulfilled, onRejected); { - QPromise p1; auto handler = [](){ return 42; }; + auto p1 = QPromise::resolve(42); Q_STATIC_ASSERT((std::is_same >::value)); Q_STATIC_ASSERT((std::is_same >::value)); Q_STATIC_ASSERT((std::is_same >::value)); @@ -283,19 +327,21 @@ void tst_requirements::thenHandlers() // p2 must be rejected with e as the reason. { QString reason; - QPromise p1; - auto p2 = p1.fulfill(42).then([](){ throw QString("foo"); }); - p2.then(nullptr, [&reason](const QString& e) { reason = e; }).wait(); + auto p1 = QPromise::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; - QPromise p1; - auto p2 = p1.reject(QString("foo")).then(nullptr, [](){ throw QString("bar"); return 42; }); - p2.then(nullptr, [&reason](const QString& e) { reason = e; return 0; }).wait(); + auto p1 = QPromise::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")); } @@ -304,11 +350,12 @@ void tst_requirements::thenHandlers() // p2 must be fulfilled with the same value as promise1 { QString value; - QPromise p1; - auto p2 = p1.fulfill("42").then(nullptr, [](){ return QString(); }); + auto p1 = QPromise::resolve("42"); + auto p2 = p1.then(nullptr, [](){ return QString(); }); Q_STATIC_ASSERT((std::is_same >::value)); - p2.then([&value](const QString& e) { value = e; }).wait(); + p2.then([&](const QString& e) { value = e; }).wait(); + QVERIFY(p1.isFulfilled()); QVERIFY(p2.isFulfilled()); QCOMPARE(value, QString("42")); }