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<T>::resolve` and `QPromise<T>::reject` methods to create synchronously fulfilled or rejected promises, and fix the `qPromise` helper to handle deduced promises (e.g. `qPromise(QFuture<int>()) -> QPromise<int>`).
This commit is contained in:
Simon Brunel 2017-05-20 09:40:42 +02:00
parent 6a642446df
commit ce3ed72dd4
9 changed files with 677 additions and 489 deletions

View File

@ -1,25 +1,21 @@
#ifndef _QTPROMISE_QPROMISE_H #ifndef _QTPROMISE_QPROMISE_H
#define _QTPROMISE_QPROMISE_H #define _QTPROMISE_QPROMISE_H
// QPromise // QtPromise
#include "qpromise_p.h" #include "qpromise_p.h"
#include "qpromiseglobal.h" #include "qpromiseglobal.h"
// Qt // Qt
#include <QExplicitlySharedDataPointer> #include <QExplicitlySharedDataPointer>
#include <QVariant>
namespace QtPromise { namespace QtPromise {
using namespace QtPromisePrivate;
template <typename T> template <typename T>
class QPromiseBase class QPromiseBase
{ {
public: public:
using Type = T; using Type = T;
QPromiseBase() : m_d(new QPromiseData<T>()) { }
virtual ~QPromiseBase() { } virtual ~QPromiseBase() { }
bool isFulfilled() const { return m_d->resolved && !m_d->rejected; } bool isFulfilled() const { return m_d->resolved && !m_d->rejected; }
@ -27,22 +23,34 @@ public:
bool isPending() const { return !m_d->resolved; } bool isPending() const { return !m_d->resolved; }
template <typename TFulfilled, typename TRejected = std::nullptr_t> template <typename TFulfilled, typename TRejected = std::nullptr_t>
inline auto then(TFulfilled fulfilled, TRejected rejected = nullptr) inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
-> typename QPromiseHandler<T, TFulfilled>::Promise; then(TFulfilled fulfilled, TRejected rejected = nullptr);
template <typename TRejected> template <typename TRejected>
inline auto fail(TRejected rejected) inline typename QtPromisePrivate::PromiseHandler<T, std::nullptr_t>::Promise
-> typename QPromiseHandler<T, std::nullptr_t>::Promise; fail(TRejected rejected);
template <typename TError>
inline QPromise<T> reject(const TError& error);
inline QPromise<T> wait() const; inline QPromise<T> wait() const;
protected: public: // STATIC
QExplicitlySharedDataPointer<QPromiseData<T> > m_d; template <typename E>
inline static QPromise<T> reject(const E& error);
virtual void notify(const typename QPromiseData<T>::HandlerList& handlers) const = 0; protected:
friend class QPromiseResolve<T>;
friend class QPromiseReject<T>;
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T> > m_d;
inline QPromiseBase();
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type = 0>
inline QPromiseBase(F resolver);
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type = 0>
inline QPromiseBase(F resolver);
virtual void notify(const typename QtPromisePrivate::PromiseData<T>::HandlerList& handlers) const = 0;
inline void dispatch(); inline void dispatch();
}; };
@ -51,42 +59,41 @@ template <typename T>
class QPromise: public QPromiseBase<T> class QPromise: public QPromiseBase<T>
{ {
public: public:
QPromise() : QPromiseBase<T>() {} template <typename F>
QPromise(F resolver): QPromiseBase<T>(resolver) { }
template <typename THandler> template <typename THandler>
inline QPromise<T> finally(THandler handler); inline QPromise<T> finally(THandler handler);
inline QPromise<T> fulfill(const T& value);
public: // STATIC public: // STATIC
inline static QPromise<QVector<T> > all(const QVector<QPromise<T> >& promises); inline static QPromise<QVector<T> > all(const QVector<QPromise<T> >& promises);
inline static QPromise<T> resolve(const T& value);
protected: protected:
inline void notify(const typename QPromiseData<T>::HandlerList& handlers) const Q_DECL_OVERRIDE; inline void notify(const typename QtPromisePrivate::PromiseData<T>::HandlerList& handlers) const Q_DECL_OVERRIDE;
private: private:
friend class QPromiseBase<T>; friend class QPromiseBase<T>;
QPromise(const QPromiseBase<T>& p) : QPromiseBase<T>(p) { } QPromise(const QPromiseBase<T>& p) : QPromiseBase<T>(p) { }
}; };
template <> template <>
class QPromise<void>: public QPromiseBase<void> class QPromise<void>: public QPromiseBase<void>
{ {
public: public:
QPromise(): QPromiseBase<void>() {} template <typename F>
QPromise(F resolver): QPromiseBase<void>(resolver) { }
template <typename THandler> template <typename THandler>
inline QPromise<void> finally(THandler handler); inline QPromise<void> finally(THandler handler);
inline QPromise<void> fulfill();
public: // STATIC public: // STATIC
inline static QPromise<void> all(const QVector<QPromise<void> >& promises); inline static QPromise<void> all(const QVector<QPromise<void> >& promises);
inline static QPromise<void> resolve();
protected: protected:
inline void notify(const typename QPromiseData<void>::HandlerList& handlers) const Q_DECL_OVERRIDE; inline void notify(const typename QtPromisePrivate::PromiseData<void>::HandlerList& handlers) const Q_DECL_OVERRIDE;
private: private:
friend class QPromiseBase<void>; friend class QPromiseBase<void>;
@ -94,13 +101,8 @@ private:
QPromise(const QPromiseBase<void>& p) : QPromiseBase<void>(p) { } QPromise(const QPromiseBase<void>& p) : QPromiseBase<void>(p) { }
}; };
using QVariantPromise = QtPromise::QPromise<QVariant>;
} // namespace QtPromise } // namespace QtPromise
Q_DECLARE_TYPEINFO(QtPromise::QVariantPromise, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(QtPromise::QVariantPromise)
#include "qpromise.inl" #include "qpromise.inl"
#include "qpromise_qfuture.inl" #include "qpromise_qfuture.inl"

View File

@ -4,15 +4,91 @@
namespace QtPromise { namespace QtPromise {
template <class T = void>
class QPromiseResolve
{
public:
QPromiseResolve(const QPromise<T>& 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<T> m_promise;
};
template <>
class QPromiseResolve<void>
{
public:
QPromiseResolve(const QPromise<void>& 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<void> m_promise;
};
template <class T = void>
class QPromiseReject
{
public:
QPromiseReject()
{ }
QPromiseReject(const QPromise<T>& 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<T> m_promise;
};
template <typename T> template <typename T>
template <typename TFulfilled, typename TRejected> template <typename TFulfilled, typename TRejected>
inline auto QPromiseBase<T>::then(TFulfilled fulfilled, TRejected rejected) inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
-> typename QPromiseHandler<T, TFulfilled>::Promise QPromiseBase<T>::then(TFulfilled fulfilled, TRejected rejected)
{ {
typename QPromiseHandler<T, TFulfilled>::Promise next; using namespace QtPromisePrivate;
using PromiseType = typename PromiseHandler<T, TFulfilled>::Promise;
m_d->handlers << QPromiseHandler<T, TFulfilled>::create(next, fulfilled); PromiseType next([=](
m_d->catchers << QPromiseCatcher<T, TRejected>::create(next, rejected); const QPromiseResolve<typename PromiseType::Type>& resolve,
const QPromiseReject<typename PromiseType::Type>& reject) {
m_d->handlers << PromiseHandler<T, TFulfilled>::create(fulfilled, resolve, reject);
m_d->catchers << PromiseCatcher<T, TRejected>::create(rejected, resolve, reject);
});
if (m_d->resolved) { if (m_d->resolved) {
dispatch(); dispatch();
@ -23,26 +99,12 @@ inline auto QPromiseBase<T>::then(TFulfilled fulfilled, TRejected rejected)
template <typename T> template <typename T>
template <typename TRejected> template <typename TRejected>
inline auto QPromiseBase<T>::fail(TRejected rejected) inline typename QtPromisePrivate::PromiseHandler<T, std::nullptr_t>::Promise
-> typename QPromiseHandler<T, std::nullptr_t>::Promise QPromiseBase<T>::fail(TRejected rejected)
{ {
return then(nullptr, rejected); return then(nullptr, rejected);
} }
template <typename T>
template <typename TError>
inline QPromise<T> QPromiseBase<T>::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 <typename T> template <typename T>
inline QPromise<T> QPromiseBase<T>::wait() const inline QPromise<T> QPromiseBase<T>::wait() const
{ {
@ -55,20 +117,67 @@ inline QPromise<T> QPromiseBase<T>::wait() const
return *this; return *this;
} }
template <typename T>
template <typename E>
inline QPromise<T> QPromiseBase<T>::reject(const E& error)
{
return QPromise<T>([=](const QPromiseResolve<T>&) {
throw error;
});
}
template <typename T>
inline QPromiseBase<T>::QPromiseBase()
: m_d(new QtPromisePrivate::PromiseData<T>())
{
}
template <typename T>
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F resolver)
: m_d(new QtPromisePrivate::PromiseData<T>())
{
auto resolve = QPromiseResolve<T>(*this);
auto reject = QPromiseReject<T>(*this);
try {
resolver(resolve);
} catch(...) {
reject(std::current_exception());
}
}
template <typename T>
template <typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F resolver)
: m_d(new QtPromisePrivate::PromiseData<T>())
{
auto resolve = QPromiseResolve<T>(*this);
auto reject = QPromiseReject<T>(*this);
try {
resolver(resolve, reject);
} catch(...) {
reject(std::current_exception());
}
}
template <typename T> template <typename T>
inline void QPromiseBase<T>::dispatch() inline void QPromiseBase<T>::dispatch()
{ {
using namespace QtPromisePrivate;
Q_ASSERT(m_d->resolved); Q_ASSERT(m_d->resolved);
typename QPromiseData<T>::HandlerList handlers; typename PromiseData<T>::HandlerList handlers;
typename QPromiseData<T>::CatcherList catchers; typename PromiseData<T>::CatcherList catchers;
handlers.swap(m_d->handlers); handlers.swap(m_d->handlers);
catchers.swap(m_d->catchers); catchers.swap(m_d->catchers);
if (m_d->rejected) { if (m_d->rejected) {
const std::exception_ptr error = m_d->error; const QPromiseError error = m_d->error;
qtpromise_defer([catchers, error]() { qtpromise_defer([=]() {
for (auto catcher: catchers) { for (auto catcher: catchers) {
catcher(error); catcher(error);
} }
@ -83,67 +192,62 @@ template <typename THandler>
inline QPromise<T> QPromise<T>::finally(THandler handler) inline QPromise<T> QPromise<T>::finally(THandler handler)
{ {
return this->then([=](const T& res) { return this->then([=](const T& res) {
return QPromise<void>().fulfill().then(handler).then([=](){ return QPromise<void>::resolve().then(handler).then([=](){
return res; return res;
}); });
}, [=]() { }, [=]() {
const auto exception = std::current_exception(); const auto exception = std::current_exception();
return QPromise<void>().fulfill().then(handler).then([=](){ return QPromise<void>::resolve().then(handler).then([=](){
std::rethrow_exception(exception); std::rethrow_exception(exception);
return T(); return T();
}); });
}); });
} }
template <typename T>
inline QPromise<T> QPromise<T>::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 <typename T> template <typename T>
inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises) inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises)
{ {
QPromise<QVector<T> > next;
const int count = promises.size(); const int count = promises.size();
if (count == 0) { if (count == 0) {
return next.fulfill({}); return QPromise<QVector<T> >::resolve({});
} }
QSharedPointer<int> remaining(new int(count)); return QPromise<QVector<T> >([=](
QSharedPointer<QVector<T> > results(new QVector<T>(count)); const QPromiseResolve<QVector<T> >& resolve,
const QPromiseReject<QVector<T> >& reject) {
for (int i=0; i<count; ++i) { QSharedPointer<int> remaining(new int(count));
QPromise<T>(promises[i]).then([=](const T& res) mutable { QSharedPointer<QVector<T> > results(new QVector<T>(count));
if (next.isPending()) {
for (int i=0; i<count; ++i) {
QPromise<T>(promises[i]).then([=](const T& res) mutable {
(*results)[i] = res; (*results)[i] = res;
if (--(*remaining) == 0) { if (--(*remaining) == 0) {
next.fulfill(*results); resolve(*results);
} }
} }, [=]() mutable {
}, [=]() mutable { if (*remaining != -1) {
if (next.isPending()) { *remaining = -1;
next.reject(std::current_exception()); reject(std::current_exception());
} }
}); });
} }
});
return next;
} }
template <typename T> template <typename T>
inline void QPromise<T>::notify(const typename QPromiseData<T>::HandlerList& handlers) const inline QPromise<T> QPromise<T>::resolve(const T& value)
{
return QPromise<T>([=](const QPromiseResolve<T>& resolve) {
resolve(value);
});
}
template <typename T>
inline void QPromise<T>::notify(const typename QtPromisePrivate::PromiseData<T>::HandlerList& handlers) const
{ {
const T value = this->m_d->value; const T value = this->m_d->value;
qtpromise_defer([handlers, value]() { QtPromisePrivate::qtpromise_defer([handlers, value]() {
for (auto handler: handlers) { for (auto handler: handlers) {
handler(value); handler(value);
} }
@ -154,52 +258,53 @@ template <typename THandler>
inline QPromise<void> QPromise<void>::finally(THandler handler) inline QPromise<void> QPromise<void>::finally(THandler handler)
{ {
return this->then([=]() { return this->then([=]() {
return QPromise<void>().fulfill().then(handler).then([](){}); return QPromise<void>::resolve().then(handler).then([](){});
}, [=]() { }, [=]() {
const auto exception = std::current_exception(); const auto exception = std::current_exception();
return QPromise<void>().fulfill().then(handler).then([=](){ return QPromise<void>::resolve().then(handler).then([=](){
std::rethrow_exception(exception); std::rethrow_exception(exception);
}); });
}); });
} }
inline QPromise<void> QPromise<void>::fulfill()
{
if (!m_d->resolved) {
m_d->rejected = false;
m_d->resolved = true;
dispatch();
}
return *this;
}
inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promises) inline QPromise<void> QPromise<void>::all(const QVector<QPromise<void> >& promises)
{ {
QPromise<void> next; const int count = promises.size();
if (count == 0) {
QSharedPointer<int> remaining(new int(promises.size())); return QPromise<void>::resolve();
for (const auto& promise: promises) {
QPromise<void>(promise).then([=]() mutable {
if (next.isPending()) {
if (--(*remaining) == 0) {
next.fulfill();
}
}
}, [=]() mutable {
if (next.isPending()) {
next.reject(std::current_exception());
}
});
} }
return next; return QPromise<void>([=](
const QPromiseResolve<void>& resolve,
const QPromiseReject<void>& reject) {
QSharedPointer<int> remaining(new int(promises.size()));
for (const auto& promise: promises) {
QPromise<void>(promise).then([=]() {
if (--(*remaining) == 0) {
resolve();
}
}, [=]() {
if (*remaining != -1) {
*remaining = -1;
reject(std::current_exception());
}
});
}
});
} }
inline void QPromise<void>::notify(const typename QPromiseData<void>::HandlerList& handlers) const inline QPromise<void> QPromise<void>::resolve()
{ {
qtpromise_defer([handlers]() { return QPromise<void>([](const QPromiseResolve<void>& resolve) {
resolve();
});
}
inline void QPromise<void>::notify(const typename QtPromisePrivate::PromiseData<void>::HandlerList& handlers) const
{
QtPromisePrivate::qtpromise_defer([handlers]() {
for (const auto& handler: handlers) { for (const auto& handler: handlers) {
handler(); handler();
} }
@ -209,9 +314,23 @@ inline void QPromise<void>::notify(const typename QPromiseData<void>::HandlerLis
// Helpers // Helpers
template <typename T> template <typename T>
QPromise<T> qPromise(const T& value) typename QtPromisePrivate::PromiseDeduce<T>::Type qPromise(const T& value)
{ {
return QPromise<T>().fulfill(value); using namespace QtPromisePrivate;
using Promise = typename PromiseDeduce<T>::Type;
return Promise([=](
const QPromiseResolve<typename Promise::Type>& resolve,
const QPromiseReject<typename Promise::Type>& reject) {
PromiseFulfill<T>::call(value, resolve, reject);
});
}
QPromise<void> qPromise()
{
return QPromise<void>([](
const QPromiseResolve<void>& resolve) {
resolve();
});
} }
template <typename T> template <typename T>

View File

@ -1,7 +1,8 @@
#ifndef _QTPROMISE_QPROMISE_P_H #ifndef _QTPROMISE_QPROMISE_P_H
#define _QTPROMISE_QPROMISE_P_H #define _QTPROMISE_QPROMISE_P_H
// QPromise // QtPromise
#include "qpromiseerror.h"
#include "qpromiseglobal.h" #include "qpromiseglobal.h"
// Qt // Qt
@ -10,18 +11,21 @@
#include <QSharedData> #include <QSharedData>
#include <QVector> #include <QVector>
// STL
#include <functional>
namespace QtPromise { namespace QtPromise {
template <typename T = void>
template <typename T>
class QPromise; class QPromise;
}
template <typename T>
class QPromiseResolve;
template <typename T>
class QPromiseReject;
} // namespace QtPromise
namespace QtPromisePrivate { namespace QtPromisePrivate {
using namespace QtPromise;
template <typename F> template <typename F>
inline void qtpromise_defer(F f) inline void qtpromise_defer(F f)
{ {
@ -29,265 +33,269 @@ inline void qtpromise_defer(F f)
} }
template <typename T> template <typename T>
struct QPromiseDeduce struct PromiseDeduce
{ {
using Type = QPromise<Unqualified<T> >; using Type = QtPromise::QPromise<Unqualified<T> >;
}; };
template <typename T> template <typename T>
struct QPromiseDeduce<QPromise<T> > struct PromiseDeduce<QtPromise::QPromise<T> >
: public QPromiseDeduce<T> : public PromiseDeduce<T>
{ }; { };
template <typename T, typename TPromise = typename QPromiseDeduce<T>::Type> template <typename T>
struct QPromiseFulfill struct PromiseFulfill
{ {
static void call(TPromise next, const T& value) static void call(
const T& value,
const QtPromise::QPromiseResolve<T>& resolve,
const QtPromise::QPromiseReject<T>&)
{ {
next.fulfill(value); resolve(value);
} }
}; };
template <typename T> template <typename T>
struct QPromiseFulfill<QPromise<T>, QPromise<T> > struct PromiseFulfill<QtPromise::QPromise<T> >
{ {
static void call(QPromise<T> next, QPromise<T> promise) static void call(
QtPromise::QPromise<T> promise,
const QtPromise::QPromiseResolve<T>& resolve,
const QtPromise::QPromiseReject<T>& reject)
{ {
promise.then( promise.then(
[=](const T& value) mutable { [=](const T& value) {
next.fulfill(value); resolve(value);
}, },
[=]() mutable { [=]() { // catch all
next.reject(std::current_exception()); reject(std::current_exception());
}); });
} }
}; };
template <> template <>
struct QPromiseFulfill<QPromise<void>, QPromise<void> > struct PromiseFulfill<QtPromise::QPromise<void> >
{ {
template <typename TPromise> template <typename TPromise, typename TResolve, typename TReject>
static void call(TPromise next, TPromise promise) static void call(TPromise promise, TResolve resolve, TReject reject)
{ {
promise.then( promise.then(
[=]() mutable { [=]() {
next.fulfill(); resolve();
}, },
[=]() mutable { [=]() { // catch all
next.reject(std::current_exception()); reject(std::current_exception());
}); });
} }
}; };
template <typename T, typename TRes> template <typename T, typename TRes>
struct QPromiseDispatch struct PromiseDispatch
{ {
using Promise = typename QPromiseDeduce<TRes>::Type; using Promise = typename PromiseDeduce<TRes>::Type;
using ResType = Unqualified<TRes>; using ResType = Unqualified<TRes>;
template <typename TPromise, typename THandler> template <typename THandler, typename TResolve, typename TReject>
static void call(TPromise next, THandler handler, const T& value) static void call(const T& value, THandler handler, TResolve resolve, TReject reject)
{ {
ResType res;
try { try {
res = handler(value); const ResType res = handler(value);
PromiseFulfill<ResType>::call(res, resolve, reject);
} catch (...) { } catch (...) {
next.reject(std::current_exception()); reject(std::current_exception());
return;
} }
QPromiseFulfill<ResType>::call(next, res);
} }
}; };
template <typename T> template <typename T>
struct QPromiseDispatch<T, void> struct PromiseDispatch<T, void>
{ {
using Promise = QPromise<void>; using Promise = QtPromise::QPromise<void>;
template <typename TPromise, typename THandler> template <typename THandler, typename TResolve, typename TReject>
static void call(TPromise next, THandler handler, const T& value) static void call(const T& value, THandler handler, TResolve resolve, TReject reject)
{ {
try { try {
handler(value); handler(value);
resolve();
} catch (...) { } catch (...) {
next.reject(std::current_exception()); reject(std::current_exception());
return;
} }
next.fulfill();
} }
}; };
template <typename TRes> template <typename TRes>
struct QPromiseDispatch<void, TRes> struct PromiseDispatch<void, TRes>
{ {
using Promise = typename QPromiseDeduce<TRes>::Type; using Promise = typename PromiseDeduce<TRes>::Type;
using ResType = Unqualified<TRes>; using ResType = Unqualified<TRes>;
template <typename TPromise, typename THandler> template <typename THandler, typename TResolve, typename TReject>
static void call(TPromise next, THandler handler) static void call(THandler handler, TResolve resolve, TReject reject)
{ {
ResType res;
try { try {
res = handler(); const ResType res = handler();
PromiseFulfill<ResType>::call(res, resolve, reject);
} catch (...) { } catch (...) {
next.reject(std::current_exception()); reject(std::current_exception());
return;
} }
QPromiseFulfill<ResType>::call(next, res);
} }
}; };
template <> template <>
struct QPromiseDispatch<void, void> struct PromiseDispatch<void, void>
{ {
using Promise = QPromise<void>; using Promise = QtPromise::QPromise<void>;
template <typename TPromise, typename THandler> template <typename THandler, typename TResolve, typename TReject>
static void call(TPromise next, THandler handler) static void call(THandler handler, TResolve resolve, TReject reject)
{ {
try { try {
handler(); handler();
resolve();
} catch (...) { } catch (...) {
next.reject(std::current_exception()); reject(std::current_exception());
return;
} }
next.fulfill();
} }
}; };
template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first> template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
struct QPromiseHandler struct PromiseHandler
{ {
using ResType = typename std::result_of<THandler(T)>::type; using ResType = typename std::result_of<THandler(T)>::type;
using Promise = typename QPromiseDispatch<T, ResType>::Promise; using Promise = typename PromiseDispatch<T, ResType>::Promise;
static std::function<void(T)> create(const Promise& next, THandler handler) template <typename TResolve, typename TReject>
static std::function<void(T)> create(THandler handler, TResolve resolve, TReject reject)
{ {
return [=](const T& value) mutable { return [=](const T& value) {
QPromiseDispatch<T, ResType>::call(next, handler, value); PromiseDispatch<T, ResType>::call(value, handler, resolve, reject);
}; };
} }
}; };
template <typename T, typename THandler> template <typename T, typename THandler>
struct QPromiseHandler<T, THandler, void> struct PromiseHandler<T, THandler, void>
{ {
using ResType = typename std::result_of<THandler()>::type; using ResType = typename std::result_of<THandler()>::type;
using Promise = typename QPromiseDispatch<T, ResType>::Promise; using Promise = typename PromiseDispatch<T, ResType>::Promise;
static std::function<void(T)> create(const Promise& next, THandler handler) template <typename TResolve, typename TReject>
static std::function<void(T)> create(THandler handler, TResolve resolve, TReject reject)
{ {
return [=](const T&) mutable { return [=](const T&) {
QPromiseDispatch<void, ResType>::call(next, handler); PromiseDispatch<void, ResType>::call(handler, resolve, reject);
}; };
} }
}; };
template <typename THandler> template <typename THandler>
struct QPromiseHandler<void, THandler, void> struct PromiseHandler<void, THandler, void>
{ {
using ResType = typename std::result_of<THandler()>::type; using ResType = typename std::result_of<THandler()>::type;
using Promise = typename QPromiseDispatch<void, ResType>::Promise; using Promise = typename PromiseDispatch<void, ResType>::Promise;
static std::function<void()> create(const Promise& next, THandler handler) template <typename TResolve, typename TReject>
static std::function<void()> create(THandler handler, TResolve resolve, TReject reject)
{ {
return [=]() { return [=]() {
QPromiseDispatch<void, ResType>::call(next, handler); PromiseDispatch<void, ResType>::call(handler, resolve, reject);
}; };
} }
}; };
template <typename T> template <typename T>
struct QPromiseHandler<T, std::nullptr_t, void> struct PromiseHandler<T, std::nullptr_t, void>
{ {
using Promise = QPromise<T>; using Promise = QtPromise::QPromise<T>;
static std::function<void(T)> create(const Promise& next, std::nullptr_t) template <typename TResolve, typename TReject>
static std::function<void(T)> 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, // 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
// promise2 must be fulfilled with the same value as promise1. // promise2 must be fulfilled with the same value as promise1.
QPromiseFulfill<T>::call(next, value); PromiseFulfill<T>::call(value, resolve, reject);
}; };
} }
}; };
template <> template <>
struct QPromiseHandler<void, std::nullptr_t, void> struct PromiseHandler<void, std::nullptr_t, void>
{ {
using Promise = QPromise<void>; using Promise = QtPromise::QPromise<void>;
template <typename TPromise> template <typename TResolve, typename TReject>
static std::function<void()> create(const TPromise& next, std::nullptr_t) static std::function<void()> create(std::nullptr_t, TResolve resolve, TReject)
{ {
return [=]() mutable { return [=]() {
// 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled, // 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
// promise2 must be fulfilled with the same value as promise1. // promise2 must be fulfilled with the same value as promise1.
TPromise(next).fulfill(); resolve();
}; };
} }
}; };
template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first> template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
struct QPromiseCatcher struct PromiseCatcher
{ {
using Type = std::function<void(std::exception_ptr)>; using Functor = std::function<void(QtPromise::QPromiseError)>;
using ResType = typename std::result_of<THandler(TArg)>::type; using ResType = typename std::result_of<THandler(TArg)>::type;
template <typename TPromise> template <typename TResolve, typename TReject>
static Type create(const TPromise& next, THandler handler) static Functor create(THandler handler, TResolve resolve, TReject reject)
{ {
return [=](const std::exception_ptr& eptr) mutable { return [=](const QtPromise::QPromiseError& error) {
try { try {
std::rethrow_exception(eptr); error.rethrow();
} catch (const TArg& error) { } catch (const TArg& error) {
QPromiseDispatch<TArg, ResType>::call(next, handler, error); PromiseDispatch<TArg, ResType>::call(error, handler, resolve, reject);
} catch(...) { } catch(...) {
TPromise(next).reject(std::current_exception()); reject(std::current_exception());
} }
}; };
} }
}; };
template <typename T, typename THandler> template <typename T, typename THandler>
struct QPromiseCatcher<T, THandler, void> struct PromiseCatcher<T, THandler, void>
{ {
using Type = std::function<void(std::exception_ptr)>; using Functor = std::function<void(QtPromise::QPromiseError)>;
using ResType = typename std::result_of<THandler()>::type; using ResType = typename std::result_of<THandler()>::type;
template <typename TPromise> template <typename TResolve, typename TReject>
static Type create(const TPromise& next, THandler handler) static Functor create(THandler handler, TResolve resolve, TReject reject)
{ {
return [=](const std::exception_ptr& eptr) mutable { return [=](const QtPromise::QPromiseError& error) {
try { try {
std::rethrow_exception(eptr); error.rethrow();
} catch (...) { } catch (...) {
QPromiseDispatch<void, ResType>::call(next, handler); PromiseDispatch<void, ResType>::call(handler, resolve, reject);
} }
}; };
} }
}; };
template <typename T> template <typename T>
struct QPromiseCatcher<T, std::nullptr_t, void> struct PromiseCatcher<T, std::nullptr_t, void>
{ {
using Type = std::function<void(std::exception_ptr)>; using Functor = std::function<void(QtPromise::QPromiseError)>;
template <typename TPromise> template <typename TResolve, typename TReject>
static Type create(const TPromise& next, std::nullptr_t) 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, // 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
TPromise(next).reject(eptr); reject(error);
}; };
} }
}; };
template <class T> template <class T>
struct QPromiseDataBase: public QSharedData struct PromiseDataBase: public QSharedData
{ {
using ErrorType = std::exception_ptr; using ErrorType = QtPromise::QPromiseError;
using CatcherList = QVector<std::function<void(ErrorType)> >; using CatcherList = QVector<std::function<void(ErrorType)> >;
bool resolved; bool resolved;
@ -297,7 +305,7 @@ struct QPromiseDataBase: public QSharedData
}; };
template <typename T> template <typename T>
struct QPromiseData: QPromiseDataBase<T> struct PromiseData: PromiseDataBase<T>
{ {
using HandlerList = QVector<std::function<void(T)> >; using HandlerList = QVector<std::function<void(T)> >;
@ -306,7 +314,7 @@ struct QPromiseData: QPromiseDataBase<T>
}; };
template <> template <>
struct QPromiseData<void>: QPromiseDataBase<void> struct PromiseData<void>: PromiseDataBase<void>
{ {
using HandlerList = QVector<std::function<void()> >; using HandlerList = QVector<std::function<void()> >;

View File

@ -4,76 +4,61 @@
namespace QtPromisePrivate { namespace QtPromisePrivate {
template <typename T> template <typename T>
struct QPromiseDeduce<QFuture<T> > struct PromiseDeduce<QFuture<T> >
: public QPromiseDeduce<T> : public PromiseDeduce<T>
{ }; { };
template <typename T> template <typename T>
struct QPromiseFulfill<QFuture<T>, QPromise<T> > struct PromiseFulfill<QFuture<T> >
{ {
static void call(QPromise<T> next, const QFuture<T>& future) static void call(
const QFuture<T>& future,
const QtPromise::QPromiseResolve<T>& resolve,
const QtPromise::QPromiseReject<T>& reject)
{ {
using Watcher = QFutureWatcher<T>; using Watcher = QFutureWatcher<T>;
Watcher* watcher = new Watcher(); Watcher* watcher = new Watcher();
QObject::connect( QObject::connect(watcher, &Watcher::finished, [=]() mutable {
watcher, &Watcher::finished, try {
[next, watcher]() mutable { T res = watcher->result();
T res; PromiseFulfill<T>::call(res, resolve, reject);
try { } catch(...) {
res = watcher->result(); reject(std::current_exception());
} catch(...) { }
next.reject(std::current_exception());
}
watcher->deleteLater(); watcher->deleteLater();
if (next.isPending()) { });
QPromiseFulfill<T>::call(next, res);
}
});
watcher->setFuture(future); watcher->setFuture(future);
} }
}; };
template <> template <>
struct QPromiseFulfill<QFuture<void>, QPromise<void> > struct PromiseFulfill<QFuture<void> >
{ {
static void call(QPromise<void> next, const QFuture<void>& future) static void call(
const QFuture<void>& future,
const QtPromise::QPromiseResolve<void>& resolve,
const QtPromise::QPromiseReject<void>& reject)
{ {
using Watcher = QFutureWatcher<void>; using Watcher = QFutureWatcher<void>;
Watcher* watcher = new Watcher(); Watcher* watcher = new Watcher();
QObject::connect( QObject::connect(watcher, &Watcher::finished, [=]() mutable {
watcher, &Watcher::finished, try {
[next, watcher]() mutable { // let's rethrown possibe exception
try { watcher->waitForFinished();
// let's rethrown possibe exception resolve();
watcher->waitForFinished(); } catch(...) {
} catch(...) { reject(std::current_exception());
next.reject(std::current_exception()); }
}
watcher->deleteLater(); watcher->deleteLater();
if (next.isPending()) { });
next.fulfill();
}
});
watcher->setFuture(future); watcher->setFuture(future);
} }
}; };
} // namespace QtPromisePrivate
namespace QtPromise {
template <typename T>
QPromise<T> qPromise(const QFuture<T>& future)
{
QPromise<T> next;
QPromiseFulfill<QFuture<T> >::call(next, future);
return next;
}
} // namespace QtPromise } // namespace QtPromise

View File

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

View File

@ -6,6 +6,7 @@
// STL // STL
#include <functional> #include <functional>
#include <array>
namespace QtPromisePrivate namespace QtPromisePrivate
{ {
@ -13,6 +14,22 @@ namespace QtPromisePrivate
template <typename T> template <typename T>
using Unqualified = typename std::remove_cv<typename std::remove_reference<T>::type>::type; using Unqualified = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
/*!
* \struct HasCallOperator
* http://stackoverflow.com/a/5839442
*/
template <typename T>
struct HasCallOperator
{
template <class U>
static auto check(const U* u)
-> decltype(&U::operator(), char(0));
static std::array<char, 2> check(...);
static const bool value = (sizeof(check((T*)0)) == 1);
};
/*! /*!
* \struct ArgsOf * \struct ArgsOf
* http://stackoverflow.com/a/7943765 * http://stackoverflow.com/a/7943765
@ -23,6 +40,7 @@ struct ArgsTraits
{ {
using types = std::tuple<Args...>; using types = std::tuple<Args...>;
using first = typename std::tuple_element<0, types>::type; using first = typename std::tuple_element<0, types>::type;
static const size_t count = std::tuple_size<types>::value;
}; };
template <> template <>
@ -30,14 +48,16 @@ struct ArgsTraits<>
{ {
using types = std::tuple<>; using types = std::tuple<>;
using first = void; using first = void;
static const size_t count = 0;
}; };
template <typename T> template <typename T, typename Enable = void>
struct ArgsOf : public ArgsOf<decltype(&T::operator())> struct ArgsOf : public ArgsTraits<>
{ }; { };
template <> template <typename T>
struct ArgsOf<std::nullptr_t> : public ArgsTraits<> struct ArgsOf<T, typename std::enable_if<HasCallOperator<T>::value>::type>
: public ArgsOf<decltype(&T::operator())>
{ }; { };
template <typename TReturn, typename... Args> template <typename TReturn, typename... Args>
@ -100,25 +120,6 @@ template <typename T>
struct ArgsOf<const volatile T&&> : public ArgsOf<T> struct ArgsOf<const volatile T&&> : public ArgsOf<T>
{ }; { };
/*!
* \fn to_exception_ptr
*/
template <typename T>
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 } // namespace QtPromisePrivate
#endif // ifndef _QTPROMISE_QPROMISEGLOBAL_H #endif // ifndef _QTPROMISE_QPROMISEGLOBAL_H

View File

@ -3,4 +3,5 @@ HEADERS += \
$$PWD/qpromise.inl \ $$PWD/qpromise.inl \
$$PWD/qpromise_p.h \ $$PWD/qpromise_p.h \
$$PWD/qpromise_qfuture.inl \ $$PWD/qpromise_qfuture.inl \
$$PWD/qpromiseerror.h \
$$PWD/qpromiseglobal.h $$PWD/qpromiseglobal.h

View File

@ -5,15 +5,13 @@
#include <QtTest> #include <QtTest>
using namespace QtPromise; using namespace QtPromise;
using namespace QtPromisePrivate;
static const int ASYNC_DELAY = 256;
class tst_qpromise: public QObject class tst_qpromise: public QObject
{ {
Q_OBJECT Q_OBJECT
private Q_SLOTS: private Q_SLOTS:
void finallyReturns(); void finallyReturns();
void finallyThrows(); void finallyThrows();
void finallyDelayedFulfilled(); void finallyDelayedFulfilled();
@ -26,75 +24,68 @@ QTEST_MAIN(tst_qpromise)
void tst_qpromise::finallyReturns() void tst_qpromise::finallyReturns()
{ {
{ { // fulfilled
QPromise<int> p;
QVector<int> values; QVector<int> values;
auto next = p.finally([&values]() { auto next = QPromise<int>::resolve(42).finally([&]() {
values << 8; values << 8;
return 16; return 16; // ignored!
}); });
p.fulfill(42); next.then([&](int r) {
next.then([&values](int r) {
values << r; values << r;
}).wait(); }).wait();
QVERIFY(p.isFulfilled());
QVERIFY(next.isFulfilled()); QVERIFY(next.isFulfilled());
QCOMPARE(values, QVector<int>({8, 42})); QCOMPARE(values, QVector<int>({8, 42}));
} }
{ { // rejected
QPromise<int> p; QString error;
QVector<int> values; int value = -1;
auto next = p.finally([&values]() { auto next = QPromise<int>([](const QPromiseResolve<int>) {
values << 8; throw QString("foo");
return 16; }).finally([&]() {
value = 8;
return 16; // ignored!
}); });
p.reject(QString("foo")); next.fail([&](const QString& err) {
next.then([&values](int r) { error = err;
values << r; return 42;
}).wait(); }).wait();
QVERIFY(p.isRejected());
QVERIFY(next.isRejected()); QVERIFY(next.isRejected());
QCOMPARE(values, QVector<int>({8})); QCOMPARE(error, QString("foo"));
QCOMPARE(value, 8);
} }
} }
void tst_qpromise::finallyThrows() void tst_qpromise::finallyThrows()
{ {
{ { // fulfilled
QPromise<int> p;
QString error; QString error;
auto next = p.finally([]() { auto next = QPromise<int>::resolve(42).finally([&]() {
throw QString("bar"); throw QString("bar");
}); });
p.fulfill(42); next.fail([&](const QString& err) {
next.fail([&error](const QString& err) {
error = err; error = err;
return 0; return 0;
}).wait(); }).wait();
QVERIFY(p.isFulfilled());
QVERIFY(next.isRejected()); QVERIFY(next.isRejected());
QCOMPARE(error, QString("bar")); QCOMPARE(error, QString("bar"));
} }
{ { // rejected
QPromise<int> p;
QString error; QString error;
auto next = p.finally([]() { auto next = QPromise<int>::reject(QString("foo")).finally([&]() {
throw QString("bar"); throw QString("bar");
}); });
p.reject(QString("foo")); next.fail([&](const QString& err) {
next.fail([&error](const QString& err) {
error = err; error = err;
return 0; return 0;
}).wait(); }).wait();
QVERIFY(p.isRejected());
QVERIFY(next.isRejected()); QVERIFY(next.isRejected());
QCOMPARE(error, QString("bar")); QCOMPARE(error, QString("bar"));
} }
@ -102,97 +93,89 @@ void tst_qpromise::finallyThrows()
void tst_qpromise::finallyDelayedFulfilled() void tst_qpromise::finallyDelayedFulfilled()
{ {
{ { // fulfilled
QPromise<int> p0;
QVector<int> values; QVector<int> values;
auto next = p0.finally([&values]() { auto next = QPromise<int>::resolve(42).finally([&]() {
QPromise<int> p1; QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
QTimer::singleShot(ASYNC_DELAY, [p1, &values]() mutable { qtpromise_defer([=, &values]() {
values << 64; values << 64;
p1.fulfill(16); resolve(16); // ignored!
});
}); });
values << 8; values << 8;
return p1; return p;
}); });
p0.fulfill(42); next.then([&](int r) {
next.then([&values](int r) {
values << r; values << r;
}).wait(); }).wait();
QVERIFY(p0.isFulfilled());
QVERIFY(next.isFulfilled()); QVERIFY(next.isFulfilled());
QCOMPARE(values, QVector<int>({8, 64, 42})); QCOMPARE(values, QVector<int>({8, 64, 42}));
} }
{ { // rejected
QPromise<int> p0; QString error;
QVector<int> values; QVector<int> values;
auto next = p0.finally([&values]() { auto next = QPromise<int>::reject(QString("foo")).finally([&]() {
QPromise<int> p1; QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
QTimer::singleShot(ASYNC_DELAY, [p1, &values]() mutable { qtpromise_defer([=, &values]() {
values << 64; values << 64;
p1.fulfill(16); resolve(16); // ignored!
});
}); });
values << 8; values << 8;
return p1; return p;
}); });
p0.reject(QString("foo")); next.then([&](int r) {
next.then([&values](int r) {
values << r; values << r;
}, [&](const QString& err) {
error = err;
}).wait(); }).wait();
QVERIFY(p0.isRejected());
QVERIFY(next.isRejected()); QVERIFY(next.isRejected());
QCOMPARE(error, QString("foo"));
QCOMPARE(values, QVector<int>({8, 64})); QCOMPARE(values, QVector<int>({8, 64}));
} }
} }
void tst_qpromise::finallyDelayedRejected() void tst_qpromise::finallyDelayedRejected()
{ {
{ { // fulfilled
QPromise<int> p0;
QString error; QString error;
auto next = p0.finally([]() { auto next = QPromise<int>::resolve(42).finally([]() {
QPromise<int> p1; return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QTimer::singleShot(ASYNC_DELAY, [p1]() mutable { qtpromise_defer([=]() {
p1.reject(QString("bar")); reject(QString("bar"));
});
}); });
return p1;
}); });
p0.fulfill(42); next.fail([&](const QString& err) {
next.fail([&error](const QString& err) {
error = err; error = err;
return 0; return 0;
}).wait(); }).wait();
QVERIFY(p0.isFulfilled());
QVERIFY(next.isRejected()); QVERIFY(next.isRejected());
QCOMPARE(error, QString("bar")); QCOMPARE(error, QString("bar"));
} }
{ { // rejected
QPromise<int> p0;
QString error; QString error;
auto next = p0.finally([]() { auto next = QPromise<int>::reject(QString("foo")).finally([]() {
QPromise<int> p1; return QPromise<int>([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
QTimer::singleShot(ASYNC_DELAY, [p1]() mutable { qtpromise_defer([=]() {
p1.reject(QString("bar")); reject(QString("bar"));
});
}); });
return p1;
}); });
p0.reject(QString("foo")); next.fail([&](const QString& err) {
next.fail([&error](const QString& err) {
error = err; error = err;
return 0; return 0;
}).wait(); }).wait();
QVERIFY(p0.isRejected());
QVERIFY(next.isRejected()); QVERIFY(next.isRejected());
QCOMPARE(error, QString("bar")); QCOMPARE(error, QString("bar"));
} }

View File

@ -5,6 +5,7 @@
#include <QtTest> #include <QtTest>
using namespace QtPromise; using namespace QtPromise;
using namespace QtPromisePrivate;
// https://promisesaplus.com/#requirements // https://promisesaplus.com/#requirements
class tst_requirements: public QObject class tst_requirements: public QObject
@ -37,115 +38,151 @@ QTEST_MAIN(tst_requirements)
void tst_requirements::statePending() void tst_requirements::statePending()
{ {
// 2.1.1. When pending, a promise: // 2.1.1. When pending, a promise:
// 2.1.1.1. may transition to either the fulfilled state
{ {
QPromise<> p; QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
qtpromise_defer([=]() { resolve(42); });
});
QVERIFY(p.isPending()); QVERIFY(p.isPending());
QVERIFY(!p.isFulfilled()); QVERIFY(!p.isFulfilled());
QVERIFY(!p.isRejected()); QVERIFY(!p.isRejected());
}
// 2.1.1.1. may transition to either the fulfilled state p.wait();
{
QPromise<> p;
p.fulfill();
QVERIFY(!p.isPending()); QVERIFY(!p.isPending());
QVERIFY(p.isFulfilled()); QVERIFY(p.isFulfilled());
QVERIFY(!p.isRejected());
} }
// 2.1.1.1. ... or the rejected state // 2.1.1.1. ... or the rejected state
{ {
QPromise<> p; QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
p.reject("foo"); qtpromise_defer([=]() { reject(QString("foo")); });
});
QVERIFY(p.isPending());
QVERIFY(!p.isFulfilled());
QVERIFY(!p.isRejected());
p.wait();
QVERIFY(!p.isPending()); QVERIFY(!p.isPending());
QVERIFY(!p.isFulfilled());
QVERIFY(p.isRejected()); QVERIFY(p.isRejected());
} }
} }
void tst_requirements::stateFulfilled() void tst_requirements::stateFulfilled()
{ {
QVector<int> values; QString error;
auto log_value = [&values](int res) { values << res; }; int value = -1;
QPromise<int> p;
QVERIFY(p.isPending());
// 2.1.2. When fulfilled, a promise: // 2.1.2. When fulfilled, a promise:
p.fulfill(42).then(log_value).wait(); QPromise<int> p([](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& 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.isFulfilled());
QVERIFY(!p.isRejected()); QVERIFY(!p.isRejected());
QVERIFY(error.isEmpty());
// 2.1.2.1. must not transition to any other state. QCOMPARE(value, 42);
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<int>({42, 42, 42}));
} }
void tst_requirements::stateRejected() void tst_requirements::stateRejected()
{ {
QStringList errors; QString error;
auto log_error = [&errors](const QString& err) { errors << err; return -1; }; int value = -1;
QPromise<int> p;
QVERIFY(p.isPending());
// 2.1.3 When rejected, a promise: // 2.1.3 When rejected, a promise:
p.reject(QString("foo")).then(nullptr, log_error).wait(); QPromise<int> p([](
const QPromiseResolve<int>& resolve,
const QPromiseReject<int>& 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.isFulfilled());
QVERIFY(p.isRejected()); QVERIFY(p.isRejected());
QCOMPARE(error, QString("foo"));
// 2.1.3.1. must not transition to any other state. QCOMPARE(value, -1);
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"}));
} }
void tst_requirements::thenArguments() void tst_requirements::thenArguments()
{ {
// Both onFulfilled and onRejected are given // 2.2.1. Both onFulfilled and onRejected are given
{ {
int value = 0; QString error;
QPromise<int> p; int value = -1;
p.fulfill(42).then([&value](int res) { value = res; }, [](const QString&){}).wait(); QPromise<int>::resolve(42).then(
QVERIFY(p.isFulfilled()); [&](int res) { value = res; },
[&](const QString& err) { error = err; }
).wait();
QVERIFY(error.isEmpty());
QCOMPARE(value, 42); QCOMPARE(value, 42);
} }
{ {
QString error; QString error;
QPromise<int> p; int value = -1;
p.reject(QString("foo")).then([](int) {}, [&error](const QString& err){ error = err; }).wait(); QPromise<int>::reject(QString("foo")).then(
QVERIFY(p.isRejected()); [&](int res) { value = res; },
[&](const QString& err){ error = err; }
).wait();
QCOMPARE(error, QString("foo")); QCOMPARE(error, QString("foo"));
QCOMPARE(value, -1);
} }
// 2.2.1. onFulfilled is an optional arguments: // 2.2.1. onFulfilled is an optional arguments:
{ {
QString error; QString error;
QPromise<int> p; QPromise<int>::reject(QString("foo")).then(
p.reject(QString("foo")).then(nullptr, [&error](const QString& err){ error = err; return 42; }).wait(); nullptr,
QVERIFY(p.isRejected()); [&](const QString& err){ error = err; return 42; }
).wait();
QCOMPARE(error, QString("foo")); QCOMPARE(error, QString("foo"));
} }
// 2.2.1. onRejected is an optional arguments: // 2.2.1. onRejected is an optional arguments:
{ {
int value = 0; int value = -1;
QPromise<int> p; QPromise<int>::resolve(42).then(
p.fulfill(42).then([&value](int res) { value = res; }).wait(); [&value](int res) { value = res; }
QVERIFY(p.isFulfilled()); ).wait();
QCOMPARE(value, 42); QCOMPARE(value, 42);
} }
@ -157,55 +194,57 @@ void tst_requirements::thenArguments()
void tst_requirements::thenOnFulfilled() void tst_requirements::thenOnFulfilled()
{ {
// 2.2.2. If onFulfilled is a function: // 2.2.2. If onFulfilled is a function:
QPromise<int> p0;
QVector<int> values; QVector<int> values;
auto p1 = p0.then([&values](int res) { values << res; }); // @TODO .wait(10) QPromise<int> p0([](const QPromiseResolve<int>& 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. // 2.2.2.2. it must not be called before promise is fulfilled.
QVERIFY(p0.isPending()); QVERIFY(p0.isPending());
QVERIFY(p1.isPending()); QVERIFY(p1.isPending());
QVERIFY(values.isEmpty()); QVERIFY(values.isEmpty());
p1.wait();
// 2.2.2.1. it must be called after promise is fulfilled, // 2.2.2.1. it must be called after promise is fulfilled,
// with promises value as its first argument. // with promises value as its first argument.
p0.fulfill(42);
p1.wait();
QVERIFY(p0.isFulfilled()); QVERIFY(p0.isFulfilled());
QVERIFY(p1.isFulfilled()); QVERIFY(p1.isFulfilled());
QCOMPARE(values, QVector<int>({42})); QCOMPARE(values, QVector<int>({42}));
// 2.2.2.3. it must not be called more than once
p0.fulfill(12);
p1.wait();
QCOMPARE(values, QVector<int>({42}));
} }
void tst_requirements::thenOnRejected() void tst_requirements::thenOnRejected()
{ {
// 2.2.3. If onRejected is a function: // 2.2.3. If onRejected is a function:
QPromise<> p0;
QStringList errors; QStringList errors;
auto p1 = p0.then(nullptr, [&errors](const QString& err) { errors << err; }); // @TODO .wait(10) QPromise<void> p0([](const QPromiseResolve<void>&, const QPromiseReject<void>& 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. // 2.2.3.2. it must not be called before promise is rejected.
QVERIFY(p0.isPending()); QVERIFY(p0.isPending());
QVERIFY(p1.isPending()); QVERIFY(p1.isPending());
QVERIFY(errors.isEmpty()); QVERIFY(errors.isEmpty());
p1.wait();
// 2.2.3.1. it must be called after promise is rejected, // 2.2.3.1. it must be called after promise is rejected,
// with promises reason as its first argument. // with promises reason as its first argument.
p0.reject(QString("foo"));
p1.wait();
QVERIFY(p0.isRejected()); QVERIFY(p0.isRejected());
QVERIFY(p1.isFulfilled()); QVERIFY(p1.isFulfilled());
QCOMPARE(errors, QStringList({"foo"})); 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() void tst_requirements::thenAsynchronous()
@ -214,14 +253,13 @@ void tst_requirements::thenAsynchronous()
// stack contains only platform code (ie. executed asynchronously, after the event // stack contains only platform code (ie. executed asynchronously, after the event
// loop turn in which then is called, and with a fresh stack). // loop turn in which then is called, and with a fresh stack).
int value = 0; int value = -1;
QPromise<int> p0; auto p0 = QPromise<int>::resolve(42);
p0.fulfill(42);
QVERIFY(p0.isFulfilled()); QVERIFY(p0.isFulfilled());
auto p1 = p0.then([&value](int res){ value = res; }); auto p1 = p0.then([&](int res){ value = res; });
QVERIFY(p1.isPending()); QVERIFY(p1.isPending());
QCOMPARE(value, 0); QCOMPARE(value, -1);
p1.wait(); p1.wait();
QVERIFY(p1.isFulfilled()); QVERIFY(p1.isFulfilled());
@ -235,32 +273,38 @@ void tst_requirements::thenMultipleCalls()
// 2.2.6.1. If/when promise is fulfilled, all respective onFulfilled callbacks // 2.2.6.1. If/when promise is fulfilled, all respective onFulfilled callbacks
// must execute in the order of their originating calls to then: // must execute in the order of their originating calls to then:
{ {
QPromise<int> p;
QVector<int> values; QVector<int> values;
auto all = qPromiseAll(QVector<QPromise<void> >{ QPromise<int> p([](const QPromiseResolve<int>& resolve) {
p.then([&values](int r) { values << r + 1; }), qtpromise_defer([=]() {
p.then([&values](int r) { values << r + 2; }), resolve(42);
p.then([&values](int r) { values << r + 3; }) });
}); });
p.fulfill(42); qPromiseAll(QVector<QPromise<void> >{
all.wait(); 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<int>({43, 44, 45})); QCOMPARE(values, QVector<int>({43, 44, 45}));
} }
// 2.2.6.2. If/when promise is rejected, all respective onRejected callbacks // 2.2.6.2. If/when promise is rejected, all respective onRejected callbacks
// must execute in the order of their originating calls to then: // must execute in the order of their originating calls to then:
{ {
QPromise<int> p;
QVector<int> values; QVector<int> values;
auto all = qPromiseAll(QVector<QPromise<int> >{ QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
p.then(nullptr, [&values](int r) { values << r + 1; return r + 1; }), qtpromise_defer([=]() {
p.then(nullptr, [&values](int r) { values << r + 2; return r + 2; }), reject(8);
p.then(nullptr, [&values](int r) { values << r + 3; return r + 3; }) });
}); });
p.reject(8); qPromiseAll(QVector<QPromise<int> >{
all.wait(); 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})); QCOMPARE(values, QVector<int>({9, 10, 11}));
} }
} }
@ -269,8 +313,8 @@ void tst_requirements::thenHandlers()
{ {
// 2.2.7. then must return a promise: p2 = p1.then(onFulfilled, onRejected); // 2.2.7. then must return a promise: p2 = p1.then(onFulfilled, onRejected);
{ {
QPromise<int> p1;
auto handler = [](){ return 42; }; 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(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(nullptr, handler)), QPromise<int> >::value));
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, handler)), QPromise<int> >::value)); Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, handler)), QPromise<int> >::value));
@ -283,19 +327,21 @@ void tst_requirements::thenHandlers()
// p2 must be rejected with e as the reason. // p2 must be rejected with e as the reason.
{ {
QString reason; QString reason;
QPromise<int> p1; auto p1 = QPromise<int>::resolve(42);
auto p2 = p1.fulfill(42).then([](){ throw QString("foo"); }); auto p2 = p1.then([](){ throw QString("foo"); });
p2.then(nullptr, [&reason](const QString& e) { reason = e; }).wait(); p2.then(nullptr, [&](const QString& e) { reason = e; }).wait();
QVERIFY(p1.isFulfilled());
QVERIFY(p2.isRejected()); QVERIFY(p2.isRejected());
QCOMPARE(reason, QString("foo")); QCOMPARE(reason, QString("foo"));
} }
{ {
QString reason; QString reason;
QPromise<int> p1; auto p1 = QPromise<int>::reject(QString("foo"));
auto p2 = p1.reject(QString("foo")).then(nullptr, [](){ throw QString("bar"); return 42; }); auto p2 = p1.then(nullptr, [](){ throw QString("bar"); return 42; });
p2.then(nullptr, [&reason](const QString& e) { reason = e; return 0; }).wait(); p2.then(nullptr, [&](const QString& e) { reason = e; return 0; }).wait();
QVERIFY(p1.isRejected());
QVERIFY(p2.isRejected()); QVERIFY(p2.isRejected());
QCOMPARE(reason, QString("bar")); QCOMPARE(reason, QString("bar"));
} }
@ -304,11 +350,12 @@ void tst_requirements::thenHandlers()
// p2 must be fulfilled with the same value as promise1 // p2 must be fulfilled with the same value as promise1
{ {
QString value; QString value;
QPromise<QString> p1; auto p1 = QPromise<QString>::resolve("42");
auto p2 = p1.fulfill("42").then(nullptr, [](){ return QString(); }); auto p2 = p1.then(nullptr, [](){ return QString(); });
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<QString> >::value)); Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<QString> >::value));
p2.then([&value](const QString& e) { value = e; }).wait(); p2.then([&](const QString& e) { value = e; }).wait();
QVERIFY(p1.isFulfilled());
QVERIFY(p2.isFulfilled()); QVERIFY(p2.isFulfilled());
QCOMPARE(value, QString("42")); QCOMPARE(value, QString("42"));
} }