mirror of
https://github.com/simonbrunel/qtpromise.git
synced 2024-11-22 02:34:30 +08:00
Initial implementation
This commit is contained in:
commit
6a642446df
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/*.user
|
9
LICENSE.md
Normal file
9
LICENSE.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
**The MIT License (MIT)**
|
||||||
|
|
||||||
|
Copyright (c) 2017 Simon Brunel
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<a href="https://promisesaplus.com/" title="Promises/A+ 1.1"><img src="http://promisesaplus.com/assets/logo-small.png" alt="Promises/A+" align="right"/></a>
|
||||||
|
|
||||||
|
# QtPromise
|
||||||
|
[Promises/A+](https://promisesaplus.com/) implementation for [Qt](https://www.qt.io/).
|
||||||
|
|
||||||
|
## License
|
||||||
|
QtPromise is available under the [MIT license](LICENSE.md).
|
1
package/features/qtpromise.prf
Normal file
1
package/features/qtpromise.prf
Normal file
@ -0,0 +1 @@
|
|||||||
|
include(../../qtpromise.pri)
|
2
qtpromise.pri
Normal file
2
qtpromise.pri
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
INCLUDEPATH += $$PWD/src
|
||||||
|
DEPENDPATH += $$PWD/src
|
10
qtpromise.pro
Normal file
10
qtpromise.pro
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
TEMPLATE = subdirs
|
||||||
|
SUBDIRS = \
|
||||||
|
src \
|
||||||
|
tests
|
||||||
|
|
||||||
|
tests.depends = src
|
||||||
|
|
||||||
|
OTHER_FILES = \
|
||||||
|
package/features/*.prf \
|
||||||
|
qtpromise.pri
|
107
src/qtpromise/qpromise.h
Normal file
107
src/qtpromise/qpromise.h
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#ifndef _QTPROMISE_QPROMISE_H
|
||||||
|
#define _QTPROMISE_QPROMISE_H
|
||||||
|
|
||||||
|
// QPromise
|
||||||
|
#include "qpromise_p.h"
|
||||||
|
#include "qpromiseglobal.h"
|
||||||
|
|
||||||
|
// Qt
|
||||||
|
#include <QExplicitlySharedDataPointer>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
namespace QtPromise {
|
||||||
|
|
||||||
|
using namespace QtPromisePrivate;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class QPromiseBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Type = T;
|
||||||
|
|
||||||
|
QPromiseBase() : m_d(new QPromiseData<T>()) { }
|
||||||
|
virtual ~QPromiseBase() { }
|
||||||
|
|
||||||
|
bool isFulfilled() const { return m_d->resolved && !m_d->rejected; }
|
||||||
|
bool isRejected() const { return m_d->resolved && m_d->rejected; }
|
||||||
|
bool isPending() const { return !m_d->resolved; }
|
||||||
|
|
||||||
|
template <typename TFulfilled, typename TRejected = std::nullptr_t>
|
||||||
|
inline auto then(TFulfilled fulfilled, TRejected rejected = nullptr)
|
||||||
|
-> typename QPromiseHandler<T, TFulfilled>::Promise;
|
||||||
|
|
||||||
|
template <typename TRejected>
|
||||||
|
inline auto fail(TRejected rejected)
|
||||||
|
-> typename QPromiseHandler<T, std::nullptr_t>::Promise;
|
||||||
|
|
||||||
|
template <typename TError>
|
||||||
|
inline QPromise<T> reject(const TError& error);
|
||||||
|
|
||||||
|
inline QPromise<T> wait() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QExplicitlySharedDataPointer<QPromiseData<T> > m_d;
|
||||||
|
|
||||||
|
virtual void notify(const typename QPromiseData<T>::HandlerList& handlers) const = 0;
|
||||||
|
|
||||||
|
inline void dispatch();
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class QPromise: public QPromiseBase<T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QPromise() : QPromiseBase<T>() {}
|
||||||
|
|
||||||
|
template <typename THandler>
|
||||||
|
inline QPromise<T> finally(THandler handler);
|
||||||
|
|
||||||
|
inline QPromise<T> fulfill(const T& value);
|
||||||
|
|
||||||
|
public: // STATIC
|
||||||
|
inline static QPromise<QVector<T> > all(const QVector<QPromise<T> >& promises);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
inline void notify(const typename QPromiseData<T>::HandlerList& handlers) const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class QPromiseBase<T>;
|
||||||
|
|
||||||
|
QPromise(const QPromiseBase<T>& p) : QPromiseBase<T>(p) { }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class QPromise<void>: public QPromiseBase<void>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QPromise(): QPromiseBase<void>() {}
|
||||||
|
|
||||||
|
template <typename THandler>
|
||||||
|
inline QPromise<void> finally(THandler handler);
|
||||||
|
|
||||||
|
inline QPromise<void> fulfill();
|
||||||
|
|
||||||
|
public: // STATIC
|
||||||
|
inline static QPromise<void> all(const QVector<QPromise<void> >& promises);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
inline void notify(const typename QPromiseData<void>::HandlerList& handlers) const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class QPromiseBase<void>;
|
||||||
|
|
||||||
|
QPromise(const QPromiseBase<void>& p) : QPromiseBase<void>(p) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
using QVariantPromise = QtPromise::QPromise<QVariant>;
|
||||||
|
|
||||||
|
} // namespace QtPromise
|
||||||
|
|
||||||
|
Q_DECLARE_TYPEINFO(QtPromise::QVariantPromise, Q_MOVABLE_TYPE);
|
||||||
|
Q_DECLARE_METATYPE(QtPromise::QVariantPromise)
|
||||||
|
|
||||||
|
#include "qpromise.inl"
|
||||||
|
#include "qpromise_qfuture.inl"
|
||||||
|
|
||||||
|
#endif // ifndef _QTPROMISE_QPROMISE_H
|
228
src/qtpromise/qpromise.inl
Normal file
228
src/qtpromise/qpromise.inl
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// Qt
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
|
namespace QtPromise {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
template <typename TFulfilled, typename TRejected>
|
||||||
|
inline auto QPromiseBase<T>::then(TFulfilled fulfilled, TRejected rejected)
|
||||||
|
-> typename QPromiseHandler<T, TFulfilled>::Promise
|
||||||
|
{
|
||||||
|
typename QPromiseHandler<T, TFulfilled>::Promise next;
|
||||||
|
|
||||||
|
m_d->handlers << QPromiseHandler<T, TFulfilled>::create(next, fulfilled);
|
||||||
|
m_d->catchers << QPromiseCatcher<T, TRejected>::create(next, rejected);
|
||||||
|
|
||||||
|
if (m_d->resolved) {
|
||||||
|
dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
template <typename TRejected>
|
||||||
|
inline auto QPromiseBase<T>::fail(TRejected rejected)
|
||||||
|
-> typename QPromiseHandler<T, std::nullptr_t>::Promise
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
inline QPromise<T> QPromiseBase<T>::wait() const
|
||||||
|
{
|
||||||
|
// @TODO wait timeout + global timeout
|
||||||
|
while (!m_d->resolved) {
|
||||||
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
||||||
|
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void QPromiseBase<T>::dispatch()
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_d->resolved);
|
||||||
|
|
||||||
|
typename QPromiseData<T>::HandlerList handlers;
|
||||||
|
typename QPromiseData<T>::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]() {
|
||||||
|
for (auto catcher: catchers) {
|
||||||
|
catcher(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notify(handlers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
template <typename THandler>
|
||||||
|
inline QPromise<T> QPromise<T>::finally(THandler handler)
|
||||||
|
{
|
||||||
|
return this->then([=](const T& res) {
|
||||||
|
return QPromise<void>().fulfill().then(handler).then([=](){
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}, [=]() {
|
||||||
|
const auto exception = std::current_exception();
|
||||||
|
return QPromise<void>().fulfill().then(handler).then([=](){
|
||||||
|
std::rethrow_exception(exception);
|
||||||
|
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>
|
||||||
|
inline QPromise<QVector<T> > QPromise<T>::all(const QVector<QPromise<T> >& promises)
|
||||||
|
{
|
||||||
|
QPromise<QVector<T> > next;
|
||||||
|
|
||||||
|
const int count = promises.size();
|
||||||
|
if (count == 0) {
|
||||||
|
return next.fulfill({});
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<int> remaining(new int(count));
|
||||||
|
QSharedPointer<QVector<T> > results(new QVector<T>(count));
|
||||||
|
|
||||||
|
for (int i=0; i<count; ++i) {
|
||||||
|
QPromise<T>(promises[i]).then([=](const T& res) mutable {
|
||||||
|
if (next.isPending()) {
|
||||||
|
(*results)[i] = res;
|
||||||
|
if (--(*remaining) == 0) {
|
||||||
|
next.fulfill(*results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [=]() mutable {
|
||||||
|
if (next.isPending()) {
|
||||||
|
next.reject(std::current_exception());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline void QPromise<T>::notify(const typename QPromiseData<T>::HandlerList& handlers) const
|
||||||
|
{
|
||||||
|
const T value = this->m_d->value;
|
||||||
|
qtpromise_defer([handlers, value]() {
|
||||||
|
for (auto handler: handlers) {
|
||||||
|
handler(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename THandler>
|
||||||
|
inline QPromise<void> QPromise<void>::finally(THandler handler)
|
||||||
|
{
|
||||||
|
return this->then([=]() {
|
||||||
|
return QPromise<void>().fulfill().then(handler).then([](){});
|
||||||
|
}, [=]() {
|
||||||
|
const auto exception = std::current_exception();
|
||||||
|
return QPromise<void>().fulfill().then(handler).then([=](){
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
QPromise<void> next;
|
||||||
|
|
||||||
|
QSharedPointer<int> remaining(new int(promises.size()));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void QPromise<void>::notify(const typename QPromiseData<void>::HandlerList& handlers) const
|
||||||
|
{
|
||||||
|
qtpromise_defer([handlers]() {
|
||||||
|
for (const auto& handler: handlers) {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
QPromise<T> qPromise(const T& value)
|
||||||
|
{
|
||||||
|
return QPromise<T>().fulfill(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
QPromise<QVector<T> > qPromiseAll(const QVector<QPromise<T> >& promises)
|
||||||
|
{
|
||||||
|
return QPromise<T>::all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPromise<void> qPromiseAll(const QVector<QPromise<void> >& promises)
|
||||||
|
{
|
||||||
|
return QPromise<void>::all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QtPromise
|
318
src/qtpromise/qpromise_p.h
Normal file
318
src/qtpromise/qpromise_p.h
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
#ifndef _QTPROMISE_QPROMISE_P_H
|
||||||
|
#define _QTPROMISE_QPROMISE_P_H
|
||||||
|
|
||||||
|
// QPromise
|
||||||
|
#include "qpromiseglobal.h"
|
||||||
|
|
||||||
|
// Qt
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QSharedData>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
// STL
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace QtPromise {
|
||||||
|
template <typename T = void>
|
||||||
|
class QPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace QtPromisePrivate {
|
||||||
|
|
||||||
|
using namespace QtPromise;
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
inline void qtpromise_defer(F f)
|
||||||
|
{
|
||||||
|
QTimer::singleShot(0, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct QPromiseDeduce
|
||||||
|
{
|
||||||
|
using Type = QPromise<Unqualified<T> >;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct QPromiseDeduce<QPromise<T> >
|
||||||
|
: public QPromiseDeduce<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T, typename TPromise = typename QPromiseDeduce<T>::Type>
|
||||||
|
struct QPromiseFulfill
|
||||||
|
{
|
||||||
|
static void call(TPromise next, const T& value)
|
||||||
|
{
|
||||||
|
next.fulfill(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct QPromiseFulfill<QPromise<T>, QPromise<T> >
|
||||||
|
{
|
||||||
|
static void call(QPromise<T> next, QPromise<T> promise)
|
||||||
|
{
|
||||||
|
promise.then(
|
||||||
|
[=](const T& value) mutable {
|
||||||
|
next.fulfill(value);
|
||||||
|
},
|
||||||
|
[=]() mutable {
|
||||||
|
next.reject(std::current_exception());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct QPromiseFulfill<QPromise<void>, QPromise<void> >
|
||||||
|
{
|
||||||
|
template <typename TPromise>
|
||||||
|
static void call(TPromise next, TPromise promise)
|
||||||
|
{
|
||||||
|
promise.then(
|
||||||
|
[=]() mutable {
|
||||||
|
next.fulfill();
|
||||||
|
},
|
||||||
|
[=]() mutable {
|
||||||
|
next.reject(std::current_exception());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename TRes>
|
||||||
|
struct QPromiseDispatch
|
||||||
|
{
|
||||||
|
using Promise = typename QPromiseDeduce<TRes>::Type;
|
||||||
|
using ResType = Unqualified<TRes>;
|
||||||
|
|
||||||
|
template <typename TPromise, typename THandler>
|
||||||
|
static void call(TPromise next, THandler handler, const T& value)
|
||||||
|
{
|
||||||
|
ResType res;
|
||||||
|
try {
|
||||||
|
res = handler(value);
|
||||||
|
} catch (...) {
|
||||||
|
next.reject(std::current_exception());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QPromiseFulfill<ResType>::call(next, res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct QPromiseDispatch<T, void>
|
||||||
|
{
|
||||||
|
using Promise = QPromise<void>;
|
||||||
|
|
||||||
|
template <typename TPromise, typename THandler>
|
||||||
|
static void call(TPromise next, THandler handler, const T& value)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
handler(value);
|
||||||
|
} catch (...) {
|
||||||
|
next.reject(std::current_exception());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next.fulfill();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TRes>
|
||||||
|
struct QPromiseDispatch<void, TRes>
|
||||||
|
{
|
||||||
|
using Promise = typename QPromiseDeduce<TRes>::Type;
|
||||||
|
using ResType = Unqualified<TRes>;
|
||||||
|
|
||||||
|
template <typename TPromise, typename THandler>
|
||||||
|
static void call(TPromise next, THandler handler)
|
||||||
|
{
|
||||||
|
ResType res;
|
||||||
|
try {
|
||||||
|
res = handler();
|
||||||
|
} catch (...) {
|
||||||
|
next.reject(std::current_exception());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QPromiseFulfill<ResType>::call(next, res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct QPromiseDispatch<void, void>
|
||||||
|
{
|
||||||
|
using Promise = QPromise<void>;
|
||||||
|
|
||||||
|
template <typename TPromise, typename THandler>
|
||||||
|
static void call(TPromise next, THandler handler)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
handler();
|
||||||
|
} catch (...) {
|
||||||
|
next.reject(std::current_exception());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next.fulfill();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
|
||||||
|
struct QPromiseHandler
|
||||||
|
{
|
||||||
|
using ResType = typename std::result_of<THandler(T)>::type;
|
||||||
|
using Promise = typename QPromiseDispatch<T, ResType>::Promise;
|
||||||
|
|
||||||
|
static std::function<void(T)> create(const Promise& next, THandler handler)
|
||||||
|
{
|
||||||
|
return [=](const T& value) mutable {
|
||||||
|
QPromiseDispatch<T, ResType>::call(next, handler, value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename THandler>
|
||||||
|
struct QPromiseHandler<T, THandler, void>
|
||||||
|
{
|
||||||
|
using ResType = typename std::result_of<THandler()>::type;
|
||||||
|
using Promise = typename QPromiseDispatch<T, ResType>::Promise;
|
||||||
|
|
||||||
|
static std::function<void(T)> create(const Promise& next, THandler handler)
|
||||||
|
{
|
||||||
|
return [=](const T&) mutable {
|
||||||
|
QPromiseDispatch<void, ResType>::call(next, handler);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename THandler>
|
||||||
|
struct QPromiseHandler<void, THandler, void>
|
||||||
|
{
|
||||||
|
using ResType = typename std::result_of<THandler()>::type;
|
||||||
|
using Promise = typename QPromiseDispatch<void, ResType>::Promise;
|
||||||
|
|
||||||
|
static std::function<void()> create(const Promise& next, THandler handler)
|
||||||
|
{
|
||||||
|
return [=]() {
|
||||||
|
QPromiseDispatch<void, ResType>::call(next, handler);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct QPromiseHandler<T, std::nullptr_t, void>
|
||||||
|
{
|
||||||
|
using Promise = QPromise<T>;
|
||||||
|
|
||||||
|
static std::function<void(T)> create(const Promise& next, std::nullptr_t)
|
||||||
|
{
|
||||||
|
return [next](const T& value) mutable {
|
||||||
|
// 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<T>::call(next, value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct QPromiseHandler<void, std::nullptr_t, void>
|
||||||
|
{
|
||||||
|
using Promise = QPromise<void>;
|
||||||
|
|
||||||
|
template <typename TPromise>
|
||||||
|
static std::function<void()> create(const TPromise& next, std::nullptr_t)
|
||||||
|
{
|
||||||
|
return [=]() mutable {
|
||||||
|
// 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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename THandler, typename TArg = typename ArgsOf<THandler>::first>
|
||||||
|
struct QPromiseCatcher
|
||||||
|
{
|
||||||
|
using Type = std::function<void(std::exception_ptr)>;
|
||||||
|
using ResType = typename std::result_of<THandler(TArg)>::type;
|
||||||
|
|
||||||
|
template <typename TPromise>
|
||||||
|
static Type create(const TPromise& next, THandler handler)
|
||||||
|
{
|
||||||
|
return [=](const std::exception_ptr& eptr) mutable {
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(eptr);
|
||||||
|
} catch (const TArg& error) {
|
||||||
|
QPromiseDispatch<TArg, ResType>::call(next, handler, error);
|
||||||
|
} catch(...) {
|
||||||
|
TPromise(next).reject(std::current_exception());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename THandler>
|
||||||
|
struct QPromiseCatcher<T, THandler, void>
|
||||||
|
{
|
||||||
|
using Type = std::function<void(std::exception_ptr)>;
|
||||||
|
using ResType = typename std::result_of<THandler()>::type;
|
||||||
|
|
||||||
|
template <typename TPromise>
|
||||||
|
static Type create(const TPromise& next, THandler handler)
|
||||||
|
{
|
||||||
|
return [=](const std::exception_ptr& eptr) mutable {
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(eptr);
|
||||||
|
} catch (...) {
|
||||||
|
QPromiseDispatch<void, ResType>::call(next, handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct QPromiseCatcher<T, std::nullptr_t, void>
|
||||||
|
{
|
||||||
|
using Type = std::function<void(std::exception_ptr)>;
|
||||||
|
|
||||||
|
template <typename TPromise>
|
||||||
|
static Type create(const TPromise& next, std::nullptr_t)
|
||||||
|
{
|
||||||
|
return [=](const std::exception_ptr& eptr) mutable {
|
||||||
|
// 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct QPromiseDataBase: public QSharedData
|
||||||
|
{
|
||||||
|
using ErrorType = std::exception_ptr;
|
||||||
|
using CatcherList = QVector<std::function<void(ErrorType)> >;
|
||||||
|
|
||||||
|
bool resolved;
|
||||||
|
bool rejected;
|
||||||
|
ErrorType error;
|
||||||
|
CatcherList catchers;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct QPromiseData: QPromiseDataBase<T>
|
||||||
|
{
|
||||||
|
using HandlerList = QVector<std::function<void(T)> >;
|
||||||
|
|
||||||
|
HandlerList handlers;
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct QPromiseData<void>: QPromiseDataBase<void>
|
||||||
|
{
|
||||||
|
using HandlerList = QVector<std::function<void()> >;
|
||||||
|
|
||||||
|
HandlerList handlers;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QtPromise
|
||||||
|
|
||||||
|
#endif // ifndef _QTPROMISE_QPROMISE_H
|
79
src/qtpromise/qpromise_qfuture.inl
Normal file
79
src/qtpromise/qpromise_qfuture.inl
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QFuture>
|
||||||
|
|
||||||
|
namespace QtPromisePrivate {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct QPromiseDeduce<QFuture<T> >
|
||||||
|
: public QPromiseDeduce<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct QPromiseFulfill<QFuture<T>, QPromise<T> >
|
||||||
|
{
|
||||||
|
static void call(QPromise<T> next, const QFuture<T>& future)
|
||||||
|
{
|
||||||
|
using Watcher = QFutureWatcher<T>;
|
||||||
|
|
||||||
|
Watcher* watcher = new Watcher();
|
||||||
|
QObject::connect(
|
||||||
|
watcher, &Watcher::finished,
|
||||||
|
[next, watcher]() mutable {
|
||||||
|
T res;
|
||||||
|
try {
|
||||||
|
res = watcher->result();
|
||||||
|
} catch(...) {
|
||||||
|
next.reject(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher->deleteLater();
|
||||||
|
if (next.isPending()) {
|
||||||
|
QPromiseFulfill<T>::call(next, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher->setFuture(future);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct QPromiseFulfill<QFuture<void>, QPromise<void> >
|
||||||
|
{
|
||||||
|
static void call(QPromise<void> next, const QFuture<void>& future)
|
||||||
|
{
|
||||||
|
using Watcher = QFutureWatcher<void>;
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher->deleteLater();
|
||||||
|
if (next.isPending()) {
|
||||||
|
next.fulfill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
124
src/qtpromise/qpromiseglobal.h
Normal file
124
src/qtpromise/qpromiseglobal.h
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#ifndef _QTPROMISE_QPROMISEGLOBAL_H
|
||||||
|
#define _QTPROMISE_QPROMISEGLOBAL_H
|
||||||
|
|
||||||
|
// QtCore
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
// STL
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace QtPromisePrivate
|
||||||
|
{
|
||||||
|
// https://rmf.io/cxx11/even-more-traits#unqualified_types
|
||||||
|
template <typename T>
|
||||||
|
using Unqualified = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \struct ArgsOf
|
||||||
|
* http://stackoverflow.com/a/7943765
|
||||||
|
* http://stackoverflow.com/a/27885283
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
struct ArgsTraits
|
||||||
|
{
|
||||||
|
using types = std::tuple<Args...>;
|
||||||
|
using first = typename std::tuple_element<0, types>::type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct ArgsTraits<>
|
||||||
|
{
|
||||||
|
using types = std::tuple<>;
|
||||||
|
using first = void;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf : public ArgsOf<decltype(&T::operator())>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct ArgsOf<std::nullptr_t> : public ArgsTraits<>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename TReturn, typename... Args>
|
||||||
|
struct ArgsOf<TReturn(Args...)> : public ArgsTraits<Args...>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename TReturn, typename... Args>
|
||||||
|
struct ArgsOf<TReturn(*)(Args...)> : public ArgsTraits<Args...>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T, typename TReturn, typename... Args>
|
||||||
|
struct ArgsOf<TReturn(T::*)(Args...)> : public ArgsTraits<Args...>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T, typename TReturn, typename... Args>
|
||||||
|
struct ArgsOf<TReturn(T::*)(Args...) const> : public ArgsTraits<Args...>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T, typename TReturn, typename... Args>
|
||||||
|
struct ArgsOf<TReturn(T::*)(Args...) volatile> : public ArgsTraits<Args...>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T, typename TReturn, typename... Args>
|
||||||
|
struct ArgsOf<TReturn(T::*)(Args...) const volatile> : public ArgsTraits<Args...>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf<std::function<T> > : public ArgsOf<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf<T&> : public ArgsOf<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf<const T&> : public ArgsOf<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf<volatile T&> : public ArgsOf<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf<const volatile T&> : public ArgsOf<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf<T&&> : public ArgsOf<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf<const T&&> : public ArgsOf<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf<volatile T&&> : public ArgsOf<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct ArgsOf<const volatile T&&> : public ArgsOf<T>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \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
|
||||||
|
|
||||||
|
#endif // ifndef _QTPROMISE_QPROMISEGLOBAL_H
|
6
src/qtpromise/qtpromise.pri
Normal file
6
src/qtpromise/qtpromise.pri
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
HEADERS += \
|
||||||
|
$$PWD/qpromise.h \
|
||||||
|
$$PWD/qpromise.inl \
|
||||||
|
$$PWD/qpromise_p.h \
|
||||||
|
$$PWD/qpromise_qfuture.inl \
|
||||||
|
$$PWD/qpromiseglobal.h
|
5
src/qtpromise/qtpromise.pro
Normal file
5
src/qtpromise/qtpromise.pro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
TEMPLATE = lib
|
||||||
|
CONFIG += c++11 qt thread warn_on
|
||||||
|
DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
|
|
||||||
|
include(qtpromise.pri)
|
2
src/src.pro
Normal file
2
src/src.pro
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
TEMPLATE = subdirs
|
||||||
|
SUBDIRS = qtpromise
|
4
tests/auto/auto.pro
Normal file
4
tests/auto/auto.pro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
TEMPLATE = subdirs
|
||||||
|
SUBDIRS += \
|
||||||
|
qpromise \
|
||||||
|
requirements
|
4
tests/auto/qpromise/qpromise.pro
Normal file
4
tests/auto/qpromise/qpromise.pro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
TARGET = tst_qpromise
|
||||||
|
SOURCES += $$PWD/tst_qpromise.cpp
|
||||||
|
|
||||||
|
include(../tests.pri)
|
199
tests/auto/qpromise/tst_qpromise.cpp
Normal file
199
tests/auto/qpromise/tst_qpromise.cpp
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// QtPromise
|
||||||
|
#include <qtpromise/qpromise.h>
|
||||||
|
|
||||||
|
// Qt
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
using namespace QtPromise;
|
||||||
|
|
||||||
|
static const int ASYNC_DELAY = 256;
|
||||||
|
|
||||||
|
class tst_qpromise: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
|
||||||
|
void finallyReturns();
|
||||||
|
void finallyThrows();
|
||||||
|
void finallyDelayedFulfilled();
|
||||||
|
void finallyDelayedRejected();
|
||||||
|
|
||||||
|
}; // class tst_qpromise
|
||||||
|
|
||||||
|
QTEST_MAIN(tst_qpromise)
|
||||||
|
#include "tst_qpromise.moc"
|
||||||
|
|
||||||
|
void tst_qpromise::finallyReturns()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QPromise<int> p;
|
||||||
|
QVector<int> values;
|
||||||
|
auto next = p.finally([&values]() {
|
||||||
|
values << 8;
|
||||||
|
return 16;
|
||||||
|
});
|
||||||
|
|
||||||
|
p.fulfill(42);
|
||||||
|
next.then([&values](int r) {
|
||||||
|
values << r;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QVERIFY(p.isFulfilled());
|
||||||
|
QVERIFY(next.isFulfilled());
|
||||||
|
QCOMPARE(values, QVector<int>({8, 42}));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QPromise<int> p;
|
||||||
|
QVector<int> values;
|
||||||
|
auto next = p.finally([&values]() {
|
||||||
|
values << 8;
|
||||||
|
return 16;
|
||||||
|
});
|
||||||
|
|
||||||
|
p.reject(QString("foo"));
|
||||||
|
next.then([&values](int r) {
|
||||||
|
values << r;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QVERIFY(p.isRejected());
|
||||||
|
QVERIFY(next.isRejected());
|
||||||
|
QCOMPARE(values, QVector<int>({8}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_qpromise::finallyThrows()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QPromise<int> p;
|
||||||
|
QString error;
|
||||||
|
auto next = p.finally([]() {
|
||||||
|
throw QString("bar");
|
||||||
|
});
|
||||||
|
|
||||||
|
p.fulfill(42);
|
||||||
|
next.fail([&error](const QString& err) {
|
||||||
|
error = err;
|
||||||
|
return 0;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QVERIFY(p.isFulfilled());
|
||||||
|
QVERIFY(next.isRejected());
|
||||||
|
QCOMPARE(error, QString("bar"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QPromise<int> p;
|
||||||
|
QString error;
|
||||||
|
auto next = p.finally([]() {
|
||||||
|
throw QString("bar");
|
||||||
|
});
|
||||||
|
|
||||||
|
p.reject(QString("foo"));
|
||||||
|
next.fail([&error](const QString& err) {
|
||||||
|
error = err;
|
||||||
|
return 0;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QVERIFY(p.isRejected());
|
||||||
|
QVERIFY(next.isRejected());
|
||||||
|
QCOMPARE(error, QString("bar"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_qpromise::finallyDelayedFulfilled()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QPromise<int> p0;
|
||||||
|
QVector<int> values;
|
||||||
|
auto next = p0.finally([&values]() {
|
||||||
|
QPromise<int> p1;
|
||||||
|
QTimer::singleShot(ASYNC_DELAY, [p1, &values]() mutable {
|
||||||
|
values << 64;
|
||||||
|
p1.fulfill(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
values << 8;
|
||||||
|
return p1;
|
||||||
|
});
|
||||||
|
|
||||||
|
p0.fulfill(42);
|
||||||
|
next.then([&values](int r) {
|
||||||
|
values << r;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QVERIFY(p0.isFulfilled());
|
||||||
|
QVERIFY(next.isFulfilled());
|
||||||
|
QCOMPARE(values, QVector<int>({8, 64, 42}));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QPromise<int> p0;
|
||||||
|
QVector<int> values;
|
||||||
|
auto next = p0.finally([&values]() {
|
||||||
|
QPromise<int> p1;
|
||||||
|
QTimer::singleShot(ASYNC_DELAY, [p1, &values]() mutable {
|
||||||
|
values << 64;
|
||||||
|
p1.fulfill(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
values << 8;
|
||||||
|
return p1;
|
||||||
|
});
|
||||||
|
|
||||||
|
p0.reject(QString("foo"));
|
||||||
|
next.then([&values](int r) {
|
||||||
|
values << r;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QVERIFY(p0.isRejected());
|
||||||
|
QVERIFY(next.isRejected());
|
||||||
|
QCOMPARE(values, QVector<int>({8, 64}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_qpromise::finallyDelayedRejected()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QPromise<int> p0;
|
||||||
|
QString error;
|
||||||
|
auto next = p0.finally([]() {
|
||||||
|
QPromise<int> p1;
|
||||||
|
QTimer::singleShot(ASYNC_DELAY, [p1]() mutable {
|
||||||
|
p1.reject(QString("bar"));
|
||||||
|
});
|
||||||
|
|
||||||
|
return p1;
|
||||||
|
});
|
||||||
|
|
||||||
|
p0.fulfill(42);
|
||||||
|
next.fail([&error](const QString& err) {
|
||||||
|
error = err;
|
||||||
|
return 0;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QVERIFY(p0.isFulfilled());
|
||||||
|
QVERIFY(next.isRejected());
|
||||||
|
QCOMPARE(error, QString("bar"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QPromise<int> p0;
|
||||||
|
QString error;
|
||||||
|
auto next = p0.finally([]() {
|
||||||
|
QPromise<int> p1;
|
||||||
|
QTimer::singleShot(ASYNC_DELAY, [p1]() mutable {
|
||||||
|
p1.reject(QString("bar"));
|
||||||
|
});
|
||||||
|
|
||||||
|
return p1;
|
||||||
|
});
|
||||||
|
|
||||||
|
p0.reject(QString("foo"));
|
||||||
|
next.fail([&error](const QString& err) {
|
||||||
|
error = err;
|
||||||
|
return 0;
|
||||||
|
}).wait();
|
||||||
|
|
||||||
|
QVERIFY(p0.isRejected());
|
||||||
|
QVERIFY(next.isRejected());
|
||||||
|
QCOMPARE(error, QString("bar"));
|
||||||
|
}
|
||||||
|
}
|
4
tests/auto/requirements/requirements.pro
Normal file
4
tests/auto/requirements/requirements.pro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
TARGET = tst_requirements
|
||||||
|
SOURCES += $$PWD/tst_requirements.cpp
|
||||||
|
|
||||||
|
include(../tests.pri)
|
315
tests/auto/requirements/tst_requirements.cpp
Normal file
315
tests/auto/requirements/tst_requirements.cpp
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
// QtPromise
|
||||||
|
#include <qtpromise/qpromise.h>
|
||||||
|
|
||||||
|
// Qt
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
using namespace QtPromise;
|
||||||
|
|
||||||
|
// https://promisesaplus.com/#requirements
|
||||||
|
class tst_requirements: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
|
||||||
|
// 2.1. Promise States
|
||||||
|
void statePending(); // 2.1.1.
|
||||||
|
void stateFulfilled(); // 2.1.2.
|
||||||
|
void stateRejected(); // 2.1.3.
|
||||||
|
|
||||||
|
// 2.2. The then Method
|
||||||
|
void thenArguments(); // 2.2.1.
|
||||||
|
void thenOnFulfilled(); // 2.2.2.
|
||||||
|
void thenOnRejected(); // 2.2.3.
|
||||||
|
void thenAsynchronous(); // 2.2.4.
|
||||||
|
// 2.2.5. NOT APPLICABLE
|
||||||
|
void thenMultipleCalls(); // 2.2.6.
|
||||||
|
void thenHandlers(); // 2.2.7.
|
||||||
|
|
||||||
|
// 2.3. The Promise Resolution Procedure
|
||||||
|
|
||||||
|
}; // class tst_requirements
|
||||||
|
|
||||||
|
QTEST_MAIN(tst_requirements)
|
||||||
|
#include "tst_requirements.moc"
|
||||||
|
|
||||||
|
void tst_requirements::statePending()
|
||||||
|
{
|
||||||
|
// 2.1.1. When pending, a promise:
|
||||||
|
{
|
||||||
|
QPromise<> p;
|
||||||
|
QVERIFY(p.isPending());
|
||||||
|
QVERIFY(!p.isFulfilled());
|
||||||
|
QVERIFY(!p.isRejected());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.1.1.1. may transition to either the fulfilled state
|
||||||
|
{
|
||||||
|
QPromise<> p;
|
||||||
|
p.fulfill();
|
||||||
|
QVERIFY(!p.isPending());
|
||||||
|
QVERIFY(p.isFulfilled());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.1.1.1. ... or the rejected state
|
||||||
|
{
|
||||||
|
QPromise<> p;
|
||||||
|
p.reject("foo");
|
||||||
|
QVERIFY(!p.isPending());
|
||||||
|
QVERIFY(p.isRejected());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_requirements::stateFulfilled()
|
||||||
|
{
|
||||||
|
QVector<int> values;
|
||||||
|
auto log_value = [&values](int res) { values << res; };
|
||||||
|
|
||||||
|
QPromise<int> p;
|
||||||
|
QVERIFY(p.isPending());
|
||||||
|
|
||||||
|
// 2.1.2. When fulfilled, a promise:
|
||||||
|
p.fulfill(42).then(log_value).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<int>({42, 42, 42}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_requirements::stateRejected()
|
||||||
|
{
|
||||||
|
QStringList errors;
|
||||||
|
auto log_error = [&errors](const QString& err) { errors << err; return -1; };
|
||||||
|
|
||||||
|
QPromise<int> p;
|
||||||
|
QVERIFY(p.isPending());
|
||||||
|
|
||||||
|
// 2.1.3 When rejected, a promise:
|
||||||
|
p.reject(QString("foo")).then(nullptr, log_error).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"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_requirements::thenArguments()
|
||||||
|
{
|
||||||
|
// Both onFulfilled and onRejected are given
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
QPromise<int> p;
|
||||||
|
p.fulfill(42).then([&value](int res) { value = res; }, [](const QString&){}).wait();
|
||||||
|
QVERIFY(p.isFulfilled());
|
||||||
|
QCOMPARE(value, 42);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
QPromise<int> p;
|
||||||
|
p.reject(QString("foo")).then([](int) {}, [&error](const QString& err){ error = err; }).wait();
|
||||||
|
QVERIFY(p.isRejected());
|
||||||
|
QCOMPARE(error, QString("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.2.1. onFulfilled is an optional arguments:
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
QPromise<int> p;
|
||||||
|
p.reject(QString("foo")).then(nullptr, [&error](const QString& err){ error = err; return 42; }).wait();
|
||||||
|
QVERIFY(p.isRejected());
|
||||||
|
QCOMPARE(error, QString("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.2.1. onRejected is an optional arguments:
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
QPromise<int> p;
|
||||||
|
p.fulfill(42).then([&value](int res) { value = res; }).wait();
|
||||||
|
QVERIFY(p.isFulfilled());
|
||||||
|
QCOMPARE(value, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.2.1.1. If onFulfilled is not a function, it must be ignored.
|
||||||
|
// 2.2.1.2. If onRejected is not a function, it must be ignored.
|
||||||
|
// -> compilation fails on type check.
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_requirements::thenOnFulfilled()
|
||||||
|
{
|
||||||
|
// 2.2.2. If onFulfilled is a function:
|
||||||
|
|
||||||
|
QPromise<int> p0;
|
||||||
|
QVector<int> values;
|
||||||
|
auto p1 = p0.then([&values](int res) { values << res; }); // @TODO .wait(10)
|
||||||
|
|
||||||
|
// 2.2.2.2. it must not be called before promise is fulfilled.
|
||||||
|
QVERIFY(p0.isPending());
|
||||||
|
QVERIFY(p1.isPending());
|
||||||
|
QVERIFY(values.isEmpty());
|
||||||
|
|
||||||
|
// 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<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()
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// 2.2.3.2. it must not be called before promise is rejected.
|
||||||
|
QVERIFY(p0.isPending());
|
||||||
|
QVERIFY(p1.isPending());
|
||||||
|
QVERIFY(errors.isEmpty());
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
{
|
||||||
|
// 2.2.4. onFulfilled or onRejected must not be called until the execution context
|
||||||
|
// stack contains only platform code (ie. executed asynchronously, after the event
|
||||||
|
// loop turn in which then is called, and with a fresh stack).
|
||||||
|
|
||||||
|
int value = 0;
|
||||||
|
QPromise<int> p0;
|
||||||
|
p0.fulfill(42);
|
||||||
|
QVERIFY(p0.isFulfilled());
|
||||||
|
|
||||||
|
auto p1 = p0.then([&value](int res){ value = res; });
|
||||||
|
QVERIFY(p1.isPending());
|
||||||
|
QCOMPARE(value, 0);
|
||||||
|
|
||||||
|
p1.wait();
|
||||||
|
QVERIFY(p1.isFulfilled());
|
||||||
|
QCOMPARE(value, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_requirements::thenMultipleCalls()
|
||||||
|
{
|
||||||
|
// 2.2.6. then may be called multiple times on the same promise:
|
||||||
|
|
||||||
|
// 2.2.6.1. If/when promise is fulfilled, all respective onFulfilled callbacks
|
||||||
|
// must execute in the order of their originating calls to then:
|
||||||
|
{
|
||||||
|
QPromise<int> p;
|
||||||
|
QVector<int> values;
|
||||||
|
auto all = qPromiseAll(QVector<QPromise<void> >{
|
||||||
|
p.then([&values](int r) { values << r + 1; }),
|
||||||
|
p.then([&values](int r) { values << r + 2; }),
|
||||||
|
p.then([&values](int r) { values << r + 3; })
|
||||||
|
});
|
||||||
|
|
||||||
|
p.fulfill(42);
|
||||||
|
all.wait();
|
||||||
|
QCOMPARE(values, QVector<int>({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<int> p;
|
||||||
|
QVector<int> values;
|
||||||
|
auto all = qPromiseAll(QVector<QPromise<int> >{
|
||||||
|
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; })
|
||||||
|
});
|
||||||
|
|
||||||
|
p.reject(8);
|
||||||
|
all.wait();
|
||||||
|
QCOMPARE(values, QVector<int>({9, 10, 11}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_requirements::thenHandlers()
|
||||||
|
{
|
||||||
|
// 2.2.7. then must return a promise: p2 = p1.then(onFulfilled, onRejected);
|
||||||
|
{
|
||||||
|
QPromise<int> p1;
|
||||||
|
auto handler = [](){ return 42; };
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, nullptr)), QPromise<int> >::value));
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(nullptr, handler)), QPromise<int> >::value));
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, handler)), QPromise<int> >::value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.2.7.1. If either onFulfilled or onRejected returns a value x, run the
|
||||||
|
// Promise Resolution Procedure [[Resolve]](p2, x) -> See 2.3.
|
||||||
|
|
||||||
|
// 2.2.7.2. If either onFulfilled or onRejected throws an exception e,
|
||||||
|
// p2 must be rejected with e as the reason.
|
||||||
|
{
|
||||||
|
QString reason;
|
||||||
|
QPromise<int> p1;
|
||||||
|
auto p2 = p1.fulfill(42).then([](){ throw QString("foo"); });
|
||||||
|
p2.then(nullptr, [&reason](const QString& e) { reason = e; }).wait();
|
||||||
|
|
||||||
|
QVERIFY(p2.isRejected());
|
||||||
|
QCOMPARE(reason, QString("foo"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QString reason;
|
||||||
|
QPromise<int> 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();
|
||||||
|
|
||||||
|
QVERIFY(p2.isRejected());
|
||||||
|
QCOMPARE(reason, QString("bar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
|
||||||
|
// p2 must be fulfilled with the same value as promise1
|
||||||
|
{
|
||||||
|
QString value;
|
||||||
|
QPromise<QString> p1;
|
||||||
|
auto p2 = p1.fulfill("42").then(nullptr, [](){ return QString(); });
|
||||||
|
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<QString> >::value));
|
||||||
|
p2.then([&value](const QString& e) { value = e; }).wait();
|
||||||
|
|
||||||
|
QVERIFY(p2.isFulfilled());
|
||||||
|
QCOMPARE(value, QString("42"));
|
||||||
|
}
|
||||||
|
}
|
6
tests/auto/tests.pri
Normal file
6
tests/auto/tests.pri
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
TEMPLATE = app
|
||||||
|
CONFIG += testcase
|
||||||
|
QT += testlib concurrent
|
||||||
|
DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
|
|
||||||
|
include(../../qtpromise.pri)
|
2
tests/tests.pro
Normal file
2
tests/tests.pro
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
TEMPLATE = subdirs
|
||||||
|
SUBDIRS += auto
|
Loading…
Reference in New Issue
Block a user