GithubHelp home page GithubHelp logo

simonbrunel / qtpromise Goto Github PK

View Code? Open in Web Editor NEW
250.0 26.0 57.0 1008 KB

Promises/A+ implementation for Qt/C++

Home Page: https://qtpromise.netlify.com

License: MIT License

QMake 0.03% C++ 97.78% CMake 2.19%
qt asynchronous future concurrent promise cpp11

qtpromise's Introduction

qtpromise's People

Contributors

dpurgin avatar pwuertz avatar simonbrunel avatar ssproessig avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

qtpromise's Issues

Awaiting QtPromises of different type

Hey there,

would it be possible to add API or an example on how to wrap promises of different types? E.g. to handle a scenario such as:

  • do A returning a_t
  • do B returning b_t
  • await for A and B then do C taking a_t and b_t.

Ideally something like this:

auto a = qPromise(QtConcurrent::run([](){return QStringLiteral("foo");}));
auto b = qPromise(QtConcurrent::run([](){return 42;}));
qPromiseAll(a, b).then([](const QString &str, int num) {});

I believe one could handle this manually by building a promise that stores a tuple of return args (here QString and int) as well as a counter to check how many of the child promises have been fulfilled. Then when a child promise is fulfilled store its return type and decrement the counter, and if that reaches zero resolve the parent promise? And if any child promise gets rejected, also reject the parent promise?

How to connect to a signal with specific argument?

We can connect to a Qt signal by using

// [signal] Object::finished(const QString&)
auto output = QtPromise::connect(obj, &Object::finished)
    .then([](const QString& data) {
        // use the data here to do further work
    });

How to implement a promise only resolved when not only the signal emitted but also meet a specific condition?

// task with int task_id
QPromise<int> task = {...}

// [signal] Object::finished(const int&)
task.then([](const int& task_id){
  QObject* context = new QObject(this);
  return QtPromise::QPromise<Qstring>{[=](const auto& resolve, const auto& reject) {
      QObject::connect(this, &Object::finished, context, [=](const int& finshed_id) {
          if (task_id == finished_id)
          {
              resolve("task{} is finished, we can proceed to next", task_id);
              delete context;
          }
      });
  })

Would this work? Any elegant solution to handle the context deletion (for disconnect the lambda expression)?

Add optional setting of QtPromise and .then() context

Hi,
I'm playing around with qtpromise and have situation where I'm launching a function using QtConcurrent:run() from an non-qt thread which is owned by an 3rdparty API. Using qtpromise in this situation appears not to be working, as the watcher in PromiseFulfill can not do its job without an eventloop and qtpromise_defer() would be using QThread::currentThread() which in my situation is the API thread also without an event loop.
I started to implement support for that, by adding a parameter to QtPromise which takes a pointer to a QThread which is passed down to PromiseFulfill::call(), where I move the watcher to that thread. In addition I added a parameter to .then() to also take a thread which is passed to the handlers and catchers which allow qtpromise_defer() to have a valid thread to post the event to.

Is that kind of functionality of general interest?
If so, what do you think would be better to use as a context, a thread or an object?

Promise from Qt Signals helper

(Small recap of #6 (comment)): When setting up promises from Qt signals, the signal connections used for resolving are tied to the lifetime of the signal source only, i.e. not to the state of the QPromise they resolve/reject. This is fine for signal sources that are already designed as promises to be deleted after resolve (like QNetworkReply).
However, if a signal source is a persistent object there is no intrinsic mechanism for tearing down signal connections after resolve/reject. This is an easy to exploit memory leak and may cause other problems if the lifetime of other captured resources is crucial to an applications logic (RAII).

Unfortunately, keeping track of signal connections and disconnecting them all at once is kind of an ugly task. I ended up creating ConnectionGuard for conveniently collecting and disconnecting multiple signals in the context of QPromise construction (pwuertz@09a709e):

// -- In promise-returning method --
auto connections = ConnectionGuard::create();
return QPromise<void>([&](const auto& resolve, const auto& reject) {
    // Resolve on signal1
    *connections << connect(this, &Class::signal1, this, [=]() {
        resolve();
    });
    // Reject on signal2
    *connections << connect(this, &Class::signal2, this, [=]() {
        reject();
    });
    // Reject on signal3 if condition is met
    *connections << connect(this, &Class::signal3, this, [=]() {
        if (condition) reject();
    });
}).finally([=]() {
    connections->disconnect();
});

The next step would be a high level API for creating a QPromise from resolve and reject signals that automatically connects to and disconnects from signal sources like your suggestion:

qPromise(QObject* fulfiller, F fsignal, QObject* rejecter, R rsignal)

I think this could cover a lot of use cases.

However, in some scenarios I still ended up with stale situations where none of the signals would emit. Possible problems are:

  • Signal source might be destroyed before resolve. Could be handled by automatically adding the destroyed signal as rejection.
  • Signal source might not emit at all and a timeout is desired. Could be handled by an optional timeout argument or signal.

So it would seem to me that there is a high chance of requiring more than one reason for rejection, which is why I'd be keen on the idea of allowing multiple rejection signals.

I know you prefer the idea of handling such cases by race, but that implies that there must be a cancellation feature as well. This is of course a powerful tool but certainly more difficult to implement and from what I've read cancellation is still a highly discussed topic without general consensus (unlike the A+ pattern).

The QObject::deleteLater() in .then doesn't work.

Here is my test code:

QObject *testObj = new QObject();
QObject::connect(testObj,&QObject::destroyed,[]{
    qInfo() << "obj deleted.";
});
auto promsie = ...
promise.then([=]{
    testObj->deleteLater();
});

using QtPromise from QtConcurrent background task

Hey all,

I stumbled upon this library, and it's looking very good - many thanks for working on it! I'm so far playing around with the code only, and wanted to solve a hypothetical problem which mixes QtConcurrent and Signal/Slot handling. E.g. I want to solve something like:

  • do A in background thread
  • then do B using event loop

So A could be a one of the QtConcurrent examples from https://qtpromise.netlify.com/qtpromise/qtconcurrent.html#convert, and B would be the download example from https://qtpromise.netlify.com/qtpromise/getting-started.html#example

What I fail to see is: How would I chain the two things together? qPromise obviously doesn't forward the resolver to the QtConcurrent task. I apparently also cannot return a QtPromise from a QtConcurrent task either, i.e. this doesn't compile for me:

#include <QCoreApplication>
#include <QTimer>
#include <QtConcurrent/QtConcurrent>
#include <QtPromise>
#include <QDebug>

using namespace QtPromise;

QPromise<QString> foo(const QString& foo, const QString& bar)
{
    return QPromise<QString>([foo, bar](const QPromiseResolve<QString>& resolve,
                                                               const QPromiseReject<QString>& reject) {
        // simulate async signal handling
        QTimer::singleShot(500, [foo, bar, resolve]() {
            resolve(foo + bar);
        });
    });
}

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);
    auto p = qPromise(QtConcurrent::run(foo, QStringLiteral("foo"), QStringLiteral("bar")))
        .then([&app](const QString& msg) { qDebug() << "got msg:" << msg; app.quit(); });
    return app.exec();
}

The errors I get are:

In file included from /home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.inl:2,
                 from /home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.h:131,
                 from /home/milian/projects/bugs/async/qtpromise/include/QtPromise:4,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:6:
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromisehelpers.h: In instantiation of 'typename QtPromisePrivate::PromiseDeduce<T>::Type QtPromise::qPromise(T&&) [with T = QFuture<QtPromise::QPromise<QString> >; typename QtPromisePrivate::PromiseDeduce<T>::Type = QtPromise::QPromise<QString>]':
/home/milian/projects/bugs/async/qpromise_kitchensink.cpp:77:89:   required from here
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromisehelpers.h:17:32: error: no matching function for call to 'QtPromisePrivate::PromiseFulfill<QFuture<QtPromise::QPromise<QString> > >::call(QFuture<QtPromise::QPromise<QString> >, const QtPromise::QPromiseResolve<QString>&, const QtPromise::QPromiseReject<QString>&)'
         PromiseFulfill<T>::call(std::forward<T>(value), resolve, reject);
         ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /home/milian/projects/bugs/async/qtpromise/include/QtPromise:5,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:6:
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromisefuture.h:32:17: note: candidate: 'static void QtPromisePrivate::PromiseFulfill<QFuture<T> >::call(const QFuture<T>&, const QtPromise::QPromiseResolve<T>&, const QtPromise::QPromiseReject<T>&) [with T = QtPromise::QPromise<QString>]'
     static void call(
                 ^~~~
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromisefuture.h:32:17: note:   no known conversion for argument 2 from 'const QtPromise::QPromiseResolve<QString>' to 'const QtPromise::QPromiseResolve<QtPromise::QPromise<QString> >&'
In file included from /usr/include/qt/QtConcurrent/qtconcurrentrun.h:49,
                 from /usr/include/qt/QtConcurrent/QtConcurrent:16,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:5:
/usr/include/qt/QtConcurrent/qtconcurrentstoredfunctioncall.h: In instantiation of 'QtConcurrent::StoredFunctorCall2<T, FunctionPointer, Arg1, Arg2>::StoredFunctorCall2(FunctionPointer, const Arg1&, const Arg2&) [with T = QtPromise::QPromise<QString>; FunctionPointer = QtPromise::QPromise<QString> (*)(const QString&, const QString&); Arg1 = QString; Arg2 = QString]':
/usr/include/qt/QtConcurrent/qtconcurrentrun.h:84:13:   required from 'QFuture<T> QtConcurrent::run(T (*)(Param1, Param2), const Arg1&, const Arg2&) [with T = QtPromise::QPromise<QString>; Param1 = const QString&; Arg1 = QString; Param2 = const QString&; Arg2 = QString]'
/home/milian/projects/bugs/async/qpromise_kitchensink.cpp:77:88:   required from here
/usr/include/qt/QtConcurrent/qtconcurrentstoredfunctioncall.h:783:53: error: use of deleted function 'QtConcurrent::RunFunctionTask<QtPromise::QPromise<QString> >::RunFunctionTask()'
       : function(_function), arg1(_arg1), arg2(_arg2) {}
                                                     ^
In file included from /usr/include/qt/QtConcurrent/qtconcurrentrun.h:48,
                 from /usr/include/qt/QtConcurrent/QtConcurrent:16,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:5:
/usr/include/qt/QtConcurrent/qtconcurrentrunbase.h:96:7: note: 'QtConcurrent::RunFunctionTask<QtPromise::QPromise<QString> >::RunFunctionTask()' is implicitly deleted because the default definition would be ill-formed:
 class RunFunctionTask : public RunFunctionTaskBase<T>
       ^~~~~~~~~~~~~~~
/usr/include/qt/QtConcurrent/qtconcurrentrunbase.h:96:7: error: no matching function for call to 'QtPromise::QPromise<QString>::QPromise()'
In file included from /home/milian/projects/bugs/async/qtpromise/include/QtPromise:4,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:6:
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.h:89:5: note: candidate: 'template<class F> QtPromise::QPromise<T>::QPromise(F&&)'
     QPromise(F&& resolver): QPromiseBase<T>(std::forward<F>(resolver)) { }
     ^~~~~~~~
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.h:89:5: note:   template argument deduction/substitution failed:
In file included from /usr/include/qt/QtConcurrent/qtconcurrentrun.h:48,
                 from /usr/include/qt/QtConcurrent/QtConcurrent:16,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:5:
/usr/include/qt/QtConcurrent/qtconcurrentrunbase.h:96:7: note:   candidate expects 1 argument, 0 provided
 class RunFunctionTask : public RunFunctionTaskBase<T>
       ^~~~~~~~~~~~~~~
In file included from /home/milian/projects/bugs/async/qtpromise/include/QtPromise:4,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:6:
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.h:85:7: note: candidate: 'QtPromise::QPromise<QString>::QPromise(const QtPromise::QPromise<QString>&)'
 class QPromise : public QPromiseBase<T>
       ^~~~~~~~
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.h:85:7: note:   candidate expects 1 argument, 0 provided
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.h:85:7: note: candidate: 'QtPromise::QPromise<QString>::QPromise(QtPromise::QPromise<QString>&&)'
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.h:85:7: note:   candidate expects 1 argument, 0 provided
In file included from /home/milian/projects/bugs/async/qtpromise/include/QtPromise:5,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:6:
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromisefuture.h: In instantiation of 'static void QtPromisePrivate::PromiseFulfill<QFuture<T> >::call(const QFuture<T>&, const QtPromise::QPromiseResolve<T>&, const QtPromise::QPromiseReject<T>&) [with T = QtPromise::QPromise<QString>]':
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromisehelpers.h:17:32:   required from 'typename QtPromisePrivate::PromiseDeduce<T>::Type QtPromise::qPromise(T&&) [with T = QFuture<QtPromise::QPromise<QString> >; typename QtPromisePrivate::PromiseDeduce<T>::Type = QtPromise::QPromise<QString>]'
/home/milian/projects/bugs/async/qpromise_kitchensink.cpp:77:89:   required from here
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromisefuture.h:51:44: error: no matching function for call to 'QtPromisePrivate::PromiseFulfill<QtPromise::QPromise<QString> >::call(QtPromise::QPromise<QString>, const QtPromise::QPromiseResolve<QtPromise::QPromise<QString> >&, const QtPromise::QPromiseReject<QtPromise::QPromise<QString> >&)'
                     PromiseFulfill<T>::call(watcher->result(), resolve, reject);
                     ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.h:5,
                 from /home/milian/projects/bugs/async/qtpromise/include/QtPromise:4,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:6:
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise_p.h:144:17: note: candidate: 'static void QtPromisePrivate::PromiseFulfill<QtPromise::QPromise<T> >::call(const QtPromise::QPromise<T>&, const QtPromise::QPromiseResolve<T>&, const QtPromise::QPromiseReject<T>&) [with T = QString]'
     static void call(
                 ^~~~
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise_p.h:144:17: note:   no known conversion for argument 2 from 'const QtPromise::QPromiseResolve<QtPromise::QPromise<QString> >' to 'const QtPromise::QPromiseResolve<QString>&'
In file included from /home/milian/projects/bugs/async/qtpromise/include/QtPromise:4,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:6:
/home/milian/projects/bugs/async/qtpromise/include/../src/qtpromise/qpromise.h:89:5: error: 'QtPromise::QPromise<T>::QPromise(F&&) [with F = QtPromise::qPromise(T&&) [with T = QFuture<QtPromise::QPromise<QString> >; typename QtPromisePrivate::PromiseDeduce<T>::Type = QtPromise::QPromise<QString>]::<lambda(const QtPromise::QPromiseResolve<QString>&, const QtPromise::QPromiseReject<QString>&)>; T = QString]', declared using local type 'QtPromise::qPromise(T&&) [with T = QFuture<QtPromise::QPromise<QString> >; typename QtPromisePrivate::PromiseDeduce<T>::Type = QtPromise::QPromise<QString>]::<lambda(const QtPromise::QPromiseResolve<QString>&, const QtPromise::QPromiseReject<QString>&)>', is used but never defined [-fpermissive]
     QPromise(F&& resolver): QPromiseBase<T>(std::forward<F>(resolver)) { }
     ^~~~~~~~
In file included from /usr/include/qt/QtCore/qcoreapplication.h:46,
                 from /usr/include/qt/QtCore/QCoreApplication:1,
                 from /home/milian/projects/bugs/async/qpromise_kitchensink.cpp:1:
/usr/include/qt/QtCore/qobject.h:300:13: error: 'static typename std::enable_if<(QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1), QMetaObject::Connection>::type QObject::connect(const typename QtPrivate::FunctionPointer<Func>::Object*, Func1, Func2) [with Func1 = void (QFutureWatcherBase::*)(); Func2 = QtPromisePrivate::PromiseFulfill<QFuture<T> >::call(const QFuture<T>&, const QtPromise::QPromiseResolve<T>&, const QtPromise::QPromiseReject<T>&) [with T = QtPromise::QPromise<QString>]::<lambda()>; typename std::enable_if<(QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1), QMetaObject::Connection>::type = QMetaObject::Connection; typename QtPrivate::FunctionPointer<Func>::Object = QFutureWatcherBase]', declared using local type 'QtPromisePrivate::PromiseFulfill<QFuture<T> >::call(const QFuture<T>&, const QtPromise::QPromiseResolve<T>&, const QtPromise::QPromiseReject<T>&) [with T = QtPromise::QPromise<QString>]::<lambda()>', is used but never defined [-fpermissive]
             connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot)
             ^~~~~~~
make[2]: *** [CMakeFiles/qpromise_kitchensink.dir/build.make:63: CMakeFiles/qpromise_kitchensink.dir/qpromise_kitchensink.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:73: CMakeFiles/qpromise_kitchensink.dir/all] Error 2

The only way I could see to make it work, is by never using QtPromise from within the QtConcurrent callback, but only from within the .then() continuation. So basically something like this will work:

    qPromise(QtConcurrent::run([](){return QStringLiteral("foo");}))
        .then([](const QString &arg) { return d(arg, arg); })
        .then([](const QString &msg){ qDebug() << msg << "YEAH"; });

So, tl;dr: It's impossible to use QtPromise-enabled API from a QtConcurrent background task. Is this by design or could it be enabled?

It would be great if qPromise could somehow (un)wrap the QFuture<QtPromise::QPromise<T>> into a QtPromise::QPromise<T>. Then we could run QPromise-enabled API directly with QtConcurrent.

API question: Is there value_or(...) equivalent?

I wanted to know if there exists an API similar to value_or(...) that would allow me to do something like:

struct Something { int interesting{0}; };

int getter() {
    QtPromise::QPromise<Something> promise = ...;  // Obtained somehow

    return promise.wait().value_or(Something{}).interesting;
}

I often find myself doing something similar to:

int getter() {
    QtPromise::QPromise<Something> promise = ...;

    int outerInteresting = 0;
    promise.then([&](const Something& s) { outerInteresting = s.interesting; }.wait();
 
    return outerInteresting;
}

Would an addition like value_or() be useful for the library?

Btw. I was also thinking that to_optional() could also fit my needs but that would require C++17 (and AFAIK the library aims to support C++11). What is the authors' current attitude towards conditional compilation and feature testing (if the compiler supports C++17)?

Clarify "order guarantee" for QPromise::all(...)

This is more of a question/clarification for the documentation, but is there an order of the results in a QPromise::all(...)? Is it ordered as promises are resolved, or is it ordered as they are in the original sequence passed into QPromise::all(...)?

Different .then behavior than expected

Perhaps this is my misunderstanding of how the library works, but it seems there's a subtle difference of how .then() statements can be chained together:

Take the following example:

QtPromise::QPromise<QString> runShellCommand(const QString& command, const QStringList& arguments = {})
{
    QProcess *process = new QProcess();
    return QtPromise::QPromise<ProcessResult>([=](const auto& resolve, const auto& reject){
        QObject::connect(process, &QProcess::finished, process, &QProcess::deleteLater);
        QObject::connect(process, &QProcess::finished, [=](int exitCode, QProcess::ExitStatus exitStatus){
            if( exitStatus != QProcess::NormalExit ) {
                reject(exitStatus);
            }
            resolve(process->readAllStandardOutput());
        });
        qDebug() << "Running:" << command << arguments;
        process->start(command, arguments);
    });
}

QtPromise::QPromise<void> runScriptVersion1()
{
    QtPromise::QPromise<void> p = QtPromise::attempt([this](){
        return runShellCommand("ls")
        .then([](const QString& output){
            qDebug() << output;
        });
    });
    
    p.then([](){
        return runShellCommand("ls")
        .then([](const QString& output){
            qDebug() << output;
        });
    });
    
    p.then([](){
        return runShellCommand("pwd")
        .then([](const QString& output){
            qDebug() << output;
        });
    });
    
    p.then([](){
        return runShellCommand("ls")
        .then([](const QString& output){
            qDebug() << output;
        });
    });
    
    return p;
}

QtPromise::QPromise<void> runScriptVersion2()
{
    return QtPromise::QPromise<void> p = QtPromise::attempt([this](){
        return runShellCommand("ls")
        .then([](const QString& output){
            qDebug() << "ls" << output;
        });
    })
    .then([](){
        return runShellCommand("ls")
        .then([](const QString& output){
            qDebug() << "pwd" << output;
        });
    })
    .then([](){
        return runShellCommand("pwd")
        .then([](const QString& output){
            qDebug() << "ls" << output;
        });
    })
    .then([](){
        return runShellCommand("ls")
        .then([](const QString& output){
            qDebug() << output;
        });
    });
}

Notice the different of how I chained together the .then() statements with and without ending the statement with ;

Version 1 I get the behavior of:

Running command: ls
Running command: pwd
Running command: ls
ls: <output>
pwd: <output>
ls: <output>

Version 2 I get the behavior of:

Running command: ls
ls: <output>
Running command: pwd
pwd: <output>
Running command: ls
ls: <output>

I would expect behavior 2 on both versions, but in the first version 3 processes get spawned, then all of the output. I'd like the second version as it's more script-like behavior.

The reason this matters to me is version 1 is actually contained within a loop, and I've effectively loop unrolled it. I figured I could "build" the .then() statements each loop iteration. Here's an example:

QtPromise::QPromise<void> runScript(const QStringList& commands)
{
    QtPromise::QPromise<void> p = QtPromise::attempt([](){}); // Need this because there's no default constructor? Probably a better way to do this
    for(const QString& command: commands) { // Assume commands = QStringList{"ls", "pwd", "ls"}
        p.then([](){
            return runShellCommand(command)
            .then([](const QString& output){
                qDebug() << output;
            });
        });
    }
    return p;
}

Let me know if there's just an overall better way to do this. Perhaps all I need is a .wait() at the end of the .then() statement in the loop? Also I wouldn't mind some guidance on how to do the promise structure better in the function without having to do the .attempt()

Thanks so much! This library has been so helpful already in other contexts for years now. :)

Add map/filter/reduce methods for sequences of promises

This library is a lifesaver. With JavaScript I use bluebirdjs for promises and it has some very handy helpers for containers. When dealing with Promise::all(...) post processing the returned sequence is a bit combersome. Example for filter:

QVector<QPromise<QByteArray> > promises{
    download(QUrl("http://a...")),
    download(QUrl("http://b...")),
    download(QUrl("http://c..."))
};

QPromise<QByteArray>::all(promises)
    .then([](const QVector<QByteArray>& res) {
       QVector<QByteArray> out;
       std::copy_if (res.begin(), res.end(), std::back_inserter(out), [](QByteArray a){
           // return true/false based on some rule
       });
       return out;
    })
    .then([](const QVector<QByteArray>& res) {
       // do something with it
    });

What I would like to have is something like this:

QVector<QPromise<QByteArray>> promises{
    download(QUrl("http://a...")),
    download(QUrl("http://b...")),
    download(QUrl("http://c..."))
};

QPromise<QByteArray>::all(promises)
    .filter([](QByteArray a){
           // return true/false based on some rule
       }))
    .then([](const QVector<QByteArray>& res) {
       // do something with it
    });

What would really be cool is to be able to do something like this:

QVector<QPromise<QByteArray>> promises{
    download(QUrl("http://a...")),
    download(QUrl("http://b...")),
    download(QUrl("http://c..."))
};

QPromise<QByteArray>::all(promises)
    .map([](QByteArray a) {
        return QJsonDocument::fromJson(a).object();
    })
    .filter([](QJsonObject o){
           return o['result'].toInt() == 200;
     })
    .then([](const QVector<QJsonObject >& res) {
       // do something with it
    });

How can I flatten nested promises?

I have a task that can be splitted into two steps, the first step is:

QPromise<Result1> step1(const QString &);

And the next step depends on the output of step1, something like:

QPromise<void> step2(const Result1 &);

But the task cannot be implemented like this:

QPromise<void> task(const QString &s) {
    // this is incorrect, return type is QPromise<QPromise<void>>
    return step1(s).then(step2);
    // also incorrect, you lose control of step2's promise
    return step1(s).then([](const Result1 &r) {
        step2(r);
    });
}

So is there something can flatten nested promises?

Does not work in a Qt dll loaded by non-Qt app

I load my qt dll in a MFC app, and tried to use this library. But it kept reporting the warning as follows:

QCoreApplication::postEvent: Unexpected null receiver

Then I find it was triggered in qpromise_p.h:

template<typename F>
static void qtpromise_defer(F&& f, const QPointer<QThread>& thread)
{
    using FType = typename std::decay<F>::type;

    struct Event : public QEvent
    {
        Event(FType&& f) : QEvent{QEvent::None}, m_f{std::move(f)} { }
        Event(const FType& f) : QEvent{QEvent::None}, m_f{f} { }
        ~Event() override { m_f(); }
        FType m_f;
    };

    if (!thread || thread->isFinished()) {
        return;
    }

    QObject* target = QAbstractEventDispatcher::instance(thread);
    if (!target && QCoreApplication::closingDown()) {
        return;
    }

    Q_ASSERT_X(target, "postMetaCall", "Target thread must have an event loop");
    QCoreApplication::postEvent(target, new Event{std::forward<F>(f)});
}

I guess the reason is I called the method in a non-qt thread, then QAbstractEventDispatcher::instance(thread) return nullptr.
Any suggestions?

Assignment operator for QPromise

So QList<T> appears to be more demanding than expected and requires <T> to be assignable as well, oddly enough only for certain build configurations, but still. Although the std containers work perfectly fine, for full compatibility with the Qt ecosystem QPromise requires operator=().

I actually think that's a good thing because it also allows for building promise chains at runtime without being forced into recursive patterns. E.g:

QPromise<void> p = getFirstPromise();
for (int i=0; i < nPromises; ++i)
    p = p.then([]() { /* chain element */ });
return p;

So, copy and move assignment support for QPromise? If move assignment is supported, shouldn't there be an "invalid/empty" state for QPromise too? If there is an empty state, could there be a default constructor creating such a state and curiously bring us back to your other idea of solving the QVector issue?

invalid use of incomplete type: QPromiseConversionException in qpromise_p.h

I get a compile error with GCC 9.1.0

qpromise_p.h:586:58: error: invalid use of incomplete type 'class QtPromise::QPromiseConversionException'
throw QtPromise::QPromiseConversionException{};
^
qpromise_p.h:34:7: note: forward declaration of 'class QtPromise::QPromiseConversionException'
class QPromiseConversionException;

qpromise_p.h is included by qpromise_exceptions.h which contains the full definition of QPromiseConversionException, so qpromise_p.h will always be read first.

Invalid thread pointer crash in qtpromise_defer()

if (!thread || thread->isFinished()) {

Above first you check if thread is nullptr, then call it's isFinished() method.

But according to Qt docs:

"QPointer<T>, behaves like a normal C++ pointer T *, except that it is automatically set to 0 when the referenced object is destroyed"

Meaning you hold only a weak reference,
and even if the thread was not null just moments ago, it can suddenly be an invalid-pointer while isFinished() did not yet return
(which hopefully causes a crash, instead of causing more damage).

Memory accumulation with QtPromise timeouts and QNetworkAccessManager

What is the correct implementation to ensure network resources are freed when using QtPromise timeouts with QNetworkAccessManager? With my current code if a QPromiseTimeoutException happens, a QNetworkReply::finished signal is never generated so the network resources are not cleaned up.

I am using Qt 5.14.2 and cannot upgrade to QT 5.15 yet to take advantage of the new QNetworkRequest setTransferTimeout api call.

In my example code below, I attempt a network request to a nonexistent local url which triggers the QtPromise timeout.

Thanks!

main.cpp.txt
memoryprofiletest.cpp.txt
memoryprofiletest.h.txt

Support for class member functions in then()

Hi,

first of all, thanks for the great library!

In the documentations there is an example of using a function pointer as an argument to then:

download(url).then(&uncompress).then(
    // lambda {...} 
)

So I tried to apply the similar concept but using a member function instead:

    download()
        .then(std::bind(&MyAsyncClass::compress, this, std::placeholders::_1))
        .then([](int result) { qDebug() << "Resolved:" << result; });

This can't be compiled with the following errors (MinGW 7.3.0):

In file included from C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/src/qtpromise/qpromise.h:25:0,
                 from C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/include/QtPromise:25,
                 from C:\devel\qtpromise_test\main.cpp:2:
C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/src/qtpromise/qpromise_p.h: In instantiation of 'struct QtPromisePrivate::PromiseHandler<QString, std::_Bind<QtPromise::QPromise<int> (MyAsyncClass::*(MyAsyncClass*, std::_Placeholder<1>))(QString)>, void>':
C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/src/qtpromise/qpromise.inl:85:1:   required by substitution of 'template<class TFulfilled> typename QtPromisePrivate::PromiseHandler<QString, TFulfilled, typename QtPromisePrivate::ArgsOf<T>::first>::Promise QtPromise::QPromiseBase<QString>::then<TFulfilled>(TFulfilled&&) const [with TFulfilled = std::_Bind<QtPromise::QPromise<int> (MyAsyncClass::*(MyAsyncClass*, std::_Placeholder<1>))(QString)>]'
C:\devel\qtpromise_test\main.cpp:18:76:   required from here
C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/src/qtpromise/qpromise_p.h:273:62: error: no type named 'type' in 'class std::result_of<std::_Bind<QtPromise::QPromise<int> (MyAsyncClass::*(MyAsyncClass*, std::_Placeholder<1>))(QString)>()>'
     using ResType = typename std::result_of<THandler()>::type;
                                                              ^
C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/src/qtpromise/qpromise_p.h:274:58: error: no type named 'type' in 'class std::result_of<std::_Bind<QtPromise::QPromise<int> (MyAsyncClass::*(MyAsyncClass*, std::_Placeholder<1>))(QString)>()>'
     using Promise = typename PromiseDeduce<ResType>::Type;
                                                          ^
C:\devel\qtpromise_test\main.cpp: In member function 'void MyAsyncClass::run()':
C:\devel\qtpromise_test\main.cpp:18:76: error: no matching function for call to 'QtPromise::QPromise<QString>::then(std::_Bind_helper<false, QtPromise::QPromise<int> (MyAsyncClass::*)(QString), MyAsyncClass*, const std::_Placeholder<1>&>::type)'
         .then(std::bind(&MyAsyncClass::compress, this, std::placeholders::_1))
                                                                            ^
In file included from C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/include/QtPromise:25:0,
                 from C:\devel\qtpromise_test\main.cpp:2:
C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/src/qtpromise/qpromise.h:68:5: note: candidate: template<class TFulfilled, class TRejected> typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise QtPromise::QPromiseBase<T>::then(const TFulfilled&, const TRejected&) const [with TFulfilled = TFulfilled; TRejected = TRejected; T = QString]
     then(const TFulfilled& fulfilled, const TRejected& rejected) const;
     ^~~~
C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/src/qtpromise/qpromise.h:68:5: note:   template argument deduction/substitution failed:
C:\devel\qtpromise_test\main.cpp:18:76: note:   candidate expects 2 arguments, 1 provided
         .then(std::bind(&MyAsyncClass::compress, this, std::placeholders::_1))
                                                                            ^
In file included from C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/include/QtPromise:25:0,
                 from C:\devel\qtpromise_test\main.cpp:2:
C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/src/qtpromise/qpromise.h:72:5: note: candidate: template<class TFulfilled> typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise QtPromise::QPromiseBase<T>::then(TFulfilled&&) const [with TFulfilled = TFulfilled; T = QString]
     then(TFulfilled&& fulfilled) const;
     ^~~~
C:/devel/builds/build-qtpromise_test-Desktop_Qt_5_13_2_MinGW_64_bit-Debug/_deps/qtpromise-src/src/qtpromise/qpromise.h:72:5: note:   substitution of deduced template arguments resulted in errors seen above
mingw32-make.exe[2]: *** [CMakeFiles\qtpromise_test.dir\build.make:77: CMakeFiles/qtpromise_test.dir/main.cpp.obj] Error 1
mingw32-make.exe[1]: *** [CMakeFiles\Makefile2:96: CMakeFiles/qtpromise_test.dir/all] Error 2
mingw32-make.exe: *** [Makefile:83: all] Error 2
14:01:48: The process "C:\Qt\Tools\CMake_64\bin\cmake.exe" exited with code 2.
Error while building/deploying project qtpromise_test (kit: Desktop Qt 5.13.2 MinGW 64-bit)
When executing step "CMake Build"

Assigning the std::bind result to an std::function doesn't work as well.

I know that I can use a lambda:

    download()
        .then([this](const QString &input) { return compress(input); })
        .then([](int result) { qDebug() << "Resolved:" << result; });

But it would be also nice to use a std::bind result or a std::function.

I'm attaching a small .cpp file for you to test.

main.zip

promise handler does not forward unique_ptr

Environment:

  • Qt 5.13.1, QtPromise 0.50
  • compiled with c++17, compiler is clang 9.0.0
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QDebug>
#include <QVector>
#include <QtPromise>

#include <memory>

using namespace QtPromise;
using namespace std;

struct LaterDeleter {
    template <class T>
    void operator()(T *t) {
        t->deleteLater();
    }
};

using QNetworkReplyPtr = unique_ptr<QNetworkReply, LaterDeleter>;

QPromise<QNetworkReplyPtr> completed(QNetworkReply *reply) {
    return QPromise<QNetworkReplyPtr>{ [reply](const auto &resolve, const auto &reject) {
        QObject::connect(reply, &QNetworkReply::finished, [=]() {
            QNetworkReplyPtr ptr{ reply };
            if(ptr->error() == QNetworkReply::NoError)
                resolve(move(ptr));
            else
                reject(ptr->error());
        });
    } };
}

int main() {
    QNetworkAccessManager man;

    QNetworkRequest example1(QUrl{"http://example1.com"});
    auto promise1 = completed(man.get(example1)).then([](QNetworkReplyPtr reply) {
        qDebug() << reply->header(QNetworkRequest::LocationHeader).toString();
    });

    QNetworkRequest example2(QUrl{"http://example2.com"});
    auto promise2 = completed(man.get(example2)).then([](QNetworkReplyPtr reply) {
        auto content = reply->readAll();
        qDebug() << content;
    });

    QVector<QPromise<void>> promises{ move(promise1), move(promise2) };
    QtPromise::all(promises).wait();
}

Because I need the Location header of one of my http request, so I have to preserve QNetworkReply*, and wrap it in unique_ptr to automatically deleteLater(). But promise's resolver is calling unique_ptr's copy constructor, am I doing it wrong (like I should switch to shared_ptr)?

Here is the build output:

[ 25%] Automatic MOC and UIC for target test
[ 25%] Built target test_autogen
[ 50%] Building CXX object CMakeFiles/test.dir/src/test.cpp.o
In file included from /home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise.h:4,
                 from /home/h/qa-qt/3rdparty/QtPromise/include/QtPromise:4,
                 from /home/h/qa-qt/src/test.cpp:7:
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise_p.h: In instantiation of 'static void QtPromisePrivate::PromiseDispatch<void>::call(const Resolve&, const Reject&, Functor, Args&& ...) [with Resolve = QtPromise::QPromiseResolve<void>; Reject = QtPromise::QPromiseReject<void>; Functor = main()::<lambda(QNetworkReplyPtr)>; Args = {const std::unique_ptr<QNetworkReply, LaterDeleter>&}]':
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise_p.h:244:43:   required from 'static std::function<void(const T&)> QtPromisePrivate::PromiseHandler<T, THandler, TArg>::create(const THandler&, const TResolve&, const TReject&) [with TResolve = QtPromise::QPromiseResolve<void>; TReject = QtPromise::QPromiseReject<void>; T = std::unique_ptr<QNetworkReply, LaterDeleter>; THandler = main()::<lambda(QNetworkReplyPtr)>; TArg = std::unique_ptr<QNetworkReply, LaterDeleter>]'
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise.inl:50:62:   required from 'typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise QtPromise::QPromiseBase<T>::then(const TFulfilled&, const TRejected&) const [with TFulfilled = main()::<lambda(QNetworkReplyPtr)>; TRejected = std::nullptr_t; T = std::unique_ptr<QNetworkReply, LaterDeleter>; typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise = QtPromise::QPromise<void>]'
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise.inl:66:61:   required from 'typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise QtPromise::QPromiseBase<T>::then(TFulfilled&&) const [with TFulfilled = main()::<lambda(QNetworkReplyPtr)>; T = std::unique_ptr<QNetworkReply, LaterDeleter>; typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise = QtPromise::QPromise<void>]'
/home/h/qa-qt/src/test.cpp:41:6:   required from here
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise_p.h:223:15: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = QNetworkReply; _Dp = LaterDeleter]'
  223 |             fn(std::forward<Args>(args)...);
      |             ~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/9.2.0/memory:80,
                 from /usr/include/c++/9.2.0/thread:39,
                 from /usr/include/c++/9.2.0/future:39,
                 from /usr/include/qt/QtCore/qthread.h:50,
                 from /usr/include/qt/QtCore/QThread:1,
                 from /home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise_p.h:9,
                 from /home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise.h:4,
                 from /home/h/qa-qt/3rdparty/QtPromise/include/QtPromise:4,
                 from /home/h/qa-qt/src/test.cpp:7:
/usr/include/c++/9.2.0/bits/unique_ptr.h:406:7: note: declared here
  406 |       unique_ptr(const unique_ptr&) = delete;
      |       ^~~~~~~~~~
/home/h/qa-qt/src/test.cpp:39:55: note:   initializing argument 1 of 'main()::<lambda(QNetworkReplyPtr)>'
   39 |     auto promise1 = completed(man.get(example1)).then([](QNetworkReplyPtr reply) {
      |                                                       ^
In file included from /home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise.h:4,
                 from /home/h/qa-qt/3rdparty/QtPromise/include/QtPromise:4,
                 from /home/h/qa-qt/src/test.cpp:7:
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise_p.h: In instantiation of 'static void QtPromisePrivate::PromiseDispatch<void>::call(const Resolve&, const Reject&, Functor, Args&& ...) [with Resolve = QtPromise::QPromiseResolve<void>; Reject = QtPromise::QPromiseReject<void>; Functor = main()::<lambda(QNetworkReplyPtr)>; Args = {const std::unique_ptr<QNetworkReply, LaterDeleter>&}]':
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise_p.h:244:43:   required from 'static std::function<void(const T&)> QtPromisePrivate::PromiseHandler<T, THandler, TArg>::create(const THandler&, const TResolve&, const TReject&) [with TResolve = QtPromise::QPromiseResolve<void>; TReject = QtPromise::QPromiseReject<void>; T = std::unique_ptr<QNetworkReply, LaterDeleter>; THandler = main()::<lambda(QNetworkReplyPtr)>; TArg = std::unique_ptr<QNetworkReply, LaterDeleter>]'
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise.inl:50:62:   required from 'typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise QtPromise::QPromiseBase<T>::then(const TFulfilled&, const TRejected&) const [with TFulfilled = main()::<lambda(QNetworkReplyPtr)>; TRejected = std::nullptr_t; T = std::unique_ptr<QNetworkReply, LaterDeleter>; typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise = QtPromise::QPromise<void>]'
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise.inl:66:61:   required from 'typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise QtPromise::QPromiseBase<T>::then(TFulfilled&&) const [with TFulfilled = main()::<lambda(QNetworkReplyPtr)>; T = std::unique_ptr<QNetworkReply, LaterDeleter>; typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise = QtPromise::QPromise<void>]'
/home/h/qa-qt/src/test.cpp:47:6:   required from here
/home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise_p.h:223:15: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = QNetworkReply; _Dp = LaterDeleter]'
  223 |             fn(std::forward<Args>(args)...);
      |             ~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/9.2.0/memory:80,
                 from /usr/include/c++/9.2.0/thread:39,
                 from /usr/include/c++/9.2.0/future:39,
                 from /usr/include/qt/QtCore/qthread.h:50,
                 from /usr/include/qt/QtCore/QThread:1,
                 from /home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise_p.h:9,
                 from /home/h/qa-qt/3rdparty/QtPromise/include/../src/qtpromise/qpromise.h:4,
                 from /home/h/qa-qt/3rdparty/QtPromise/include/QtPromise:4,
                 from /home/h/qa-qt/src/test.cpp:7:
/usr/include/c++/9.2.0/bits/unique_ptr.h:406:7: note: declared here
  406 |       unique_ptr(const unique_ptr&) = delete;
      |       ^~~~~~~~~~
/home/h/qa-qt/src/test.cpp:44:55: note:   initializing argument 1 of 'main()::<lambda(QNetworkReplyPtr)>'
   44 |     auto promise2 = completed(man.get(example2)).then([](QNetworkReplyPtr reply) {
      |                                                       ^
make[2]: *** [CMakeFiles/test.dir/build.make:76: CMakeFiles/test.dir/src/test.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:78: CMakeFiles/test.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

Expose QPromise to QML

Hi !
First: thanks for your beautiful work on this.
Is there any plan to support QPromise exposition to QML ?

QPromise::all with dynamic list of promises

I'm having trouble using QPromise::all on a list of promises that is not determined at compile time. If I'm getting this right, this isn't possible at the moment due to the following:

  • QPromise::all only accepts QVector<QPromise> as input
  • QVector<T> does not support adding a T or converting from std::vector<T> if T has no default constructor
  • QPromise has no default constructor

So it seems that there is no way to build a QVector<QPromise> other than using static compile-time initializer lists (like documentation and tests are doing it). I can create a std::vector<QPromise> using std::vector::emplace just fine, but Qt can't even convert from that without a default constructor.

Could this limitation be solved in some way? Would it be better to make QPromise default constructible or to support std::vector in QPromise::all?

"Getting Started" section for CMake could be improved

"Getting Started" section, for the CMake (Fetch Content) part (here) indicates that the tag to be used is origin/0.6.0.

This version, however, was not yet released.

So, my next step was to use the one last released (origin/0.5.0). This didn't work because the origin prefix was unnecessary. Standalone 0.5.0 turned out not to have proper CMake support. Only after switching to master it finally worked.

I'd suggest to improve the documentation to reflect the usage.

Otherwise, great work! Thanks!

Qt 6 name conflict

Hello, I recently discovered this project and I'm amazed by it. I'm planning to start using QPromise in my own project. But Qt 6 is coming and has a new class named QPromise. Do you plan to change the class name? Do you plan to support Qt 6 at all?

Question about executing context

I use qtpromise a lot in my project and I am constantly running into next situation

static QtPromise::QPromise<void> DoSomething()
{
    return QtPromise::resolve().delay(5000);
}
////
struct Foo : public QObject {
    void doSomethingAndDoSomethingElse()
    {
        QPointer<Foo> self(this);
        DoSomething().then([self] {
            if(self) {
                // do something with self.
                qDebug() << "Self is ok";
            }
            else {
                qDebug() << "Foo already deleted";
            }
        });
    }
};

Foo *foo = new Foo;
foo->doSomethingAndDoSomethingElse();
// here foo gets deleted(by deleteLater for instance)

So it can happen that Foo gets deleted before promise is resolved, and since function is called as callback I need to check if this is still valid in some way otherwise I get segfault.
I was thinking a lot about it and it seems like a pretty common thing.
I see 3 ways how to resolve this situation.

  1. Check this as I am doing it right now.
  2. Store started promises and wait for them in destructor.
  3. Try to support same behavior as Qt signal & slots system in terms of executing slot in receivers context.
    I like 3rd case the most but it requires changes to the library(which I can do). I want to discuss here if it makes sense at all and if you see any problems with it.
    So the idea is to provide executing context to continuations, introduce new call like, thenVia(QObject *, callback) or something like then(callback).via(QObject*) which will be used as context for delivering callback. If such functionality exists then my use case will be resolved just by using context for continuations since if context is already deleted, callback won't be executed(same behavior with slots). Implementation wise it shouldn't be that hard to do it, since it requires just some modifications to qtpromise_defer.

Looking forward to discussing it

No .fail without .then?

In contrast to the QPromise::reject promise that is being tested in tst_fail, regular non-void promises fail to compile .fail methods being attached directly to p:

auto p = QPromise<int>([&](
    const QPromiseResolve<int>& resolve,
    const QPromiseReject<int>& reject)
{
    resolve(42);
    reject(43);
});
p.fail([](int x) {
    Q_UNUSED(x);
});
...qtpromise/src/qtpromise/qpromise_p.h:604: error: no matching function for call to ‘QtPromisePrivate::PromiseData<int>::resolve()’
             promise->m_d->resolve();
             ^~~~~~~

If .fail is preceded by a dummy .then method, the fail works:

p.then([](){}).fail([](int x) {
    Q_UNUSED(x);
});

want to unpack and assign qtpromise value to a C++ private member

Hi...
I'm using QtPromise but encountered a problem...I need a way to unpack QPromise and then assign the value to a private member of a class...But I can't do that because THE this pointer can't be accessed in the then of qtpromise facility...Is there anyone can't tell me how to do that...Thank you...

Suppress warnings

After including the headers, a lot of QtPromise-specific warnings are shown on compilation.

I've been able to suppress them by adding the following pragmas to "QtPromise":

#ifndef QTPROMISE_MODULE_H
#define QTPROMISE_MODULE_H

#pragma GCC diagnostic push

#pragma GCC diagnostic ignored "-Wunused-local-typedef"
#pragma GCC diagnostic ignored "-Wold-style-cast"

#include "../src/qtpromise/qpromise.h"
#include "../src/qtpromise/qpromisefuture.h"
#include "../src/qtpromise/qpromisehelpers.h"

#pragma GCC diagnostic pop

#endif // ifndef QTPROMISE_MODULE_H

It would be nice to not have to do this.

Thanks for the great library.

Problems using smart pointers

#include <QtPromise>
using namespace QtPromise;

typedef QSharedPointer< QNetworkReply > NetworkReplyPtr;
QPromise< NetworkReplyPtr > download(const QUrl& url)
{
	return QPromise<NetworkReplyPtr>( [=](
		const QPromiseResolve< NetworkReplyPtr >& resolve,
		const QPromiseReject< NetworkReplyPtr >& reject) {

		QNetworkReply* reply = NVHttpManager::ptr()->get( QNetworkRequest(url) );
		QObject::connect( reply, &QNetworkReply::destroyed, []() {
			qDebug() << "reply destroyed";
		} );

		QObject::connect( reply, &QNetworkReply::finished, [=]() {
			if( reply->error() == QNetworkReply::NoError ) {
				resolve( NetworkReplyPtr( reply, &QNetworkReply::deleteLater ) );
			} else {
				reject( NetworkReplyPtr( reply, &QNetworkReply::deleteLater ) );
			}
		});
	});
}

int main() {
	download( QUrl( "http://www.google.com" ) )
			  .then( []( NetworkReplyPtr result ) {
				  qDebug() << result->readAll();
				  return result;
			  } )
}

its has error in vs2015

C2668: ambiguous call to overloaded function [duplicate]

So I switched to shared_ptr.
typedef std::shared_ptr< QNetworkReply > NetworkReplyPtr;

its compile is done. but not delete QNetworkReply.

resolve( NetworkReplyPtr( reply, std::bind( &QNetworkReply::deleteLater, reply ) ) );
reject( NetworkReplyPtr( reply, std::bind( &QNetworkReply::deleteLater, reply ) ) );
////
	download( QUrl( "http://www.google.com" ) )
			  .then( []( NetworkReplyPtr result ) {
				  qDebug() << result->readAll();
				  return result;
			  } )
	.finally( []( NetworkReplyPtr result ) {
		qDebug() << result.use_count(); // output 3. not destroy.
	} );

I want to use QSharedPointer to destroy it when QPromise is done.
Please help me.

Provide something like BlubirdJS ".prop"

Since we are talking "Bluebird" I wonder how hard it would be to implement Promise.prop from bluebird. I would envision this to operate similarly to QPromise::all but it would take a map instead of a sequence. I am not sure how this would translate since we cannot construct classes in C++, but what if we just used a QMap<QString, T>? An example of how this might look would be:

QMap<QString, QPromise<QByteArray>> servers({
    {"a", download(QUrl("http://a..."))}, 
    {"b", download(QUrl("http://b..."))}, 
    {"c", download(QUrl("http://c..."))}
});

QPromise::prop(servers)
    .then([](QMap<QString, QByteArray> res) {
         qDebug() << res["a"] << res["b"] << res["c"];
    });

Release Candidate for v0.7.0

Hello, I noticed in the documentation there's references to a v0.7.0, but no such tag exists currently. Could we get a semi-stable release of v0.7.0 that has the Qt6 support within it, or are there more features to be planned for the official v0.7.0 release?

Thanks!

When using QtPromise::all a following fail and finally are not called

In the small test programm below, neither fail nor finally is executed.

#include <QByteArray>
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QtPromise>
#include <iostream>

struct Consumer
{
	Consumer( QNetworkAccessManager* manager )
	    : m_manager( manager )
	{
	}

	QtPromise::QPromise<QByteArray> get( const QUrl& url )
	{
		return QtPromise::QPromise<QByteArray>{ [&]( const QtPromise::QPromiseResolve<QByteArray>& resolve,
			                                         const QtPromise::QPromiseReject<QByteArray>&  reject ) {
			QNetworkReply* reply = m_manager->get( QNetworkRequest{ url } );
			QObject::connect( reply, &QNetworkReply::finished, [=]() {
				if( reply->networkError() == QNetworkReply::NoError )
				{
					qDebug() << "ONE DOWNLOADED: " << url;
					resolve( reply->readAll() );
				}
				else
				{
					qDebug() << "ONE FAILED: " << url << reply->networkError();
					reject( reply->errorString() );
				}

				reply->deleteLater();
			} );
		} };
	}

	QNetworkAccessManager* m_manager;
};

int main( int argc, char** argv )
{
	QCoreApplication      app{ argc, argv };
	QNetworkAccessManager manager;

	QVector<QtPromise::QPromise<QByteArray>> filesToDownload{
		Consumer( &manager ).get( QUrl( "https://github.com/simonbrunel/qtpromise/blob/master/README.md" ) ),
		Consumer( &manager ).get( QUrl( "https://github.com/simonbrunel/qtpromise/blob/master/LICENSE" ) ),
		Consumer( &manager ).get( QUrl( "https://github.com/simonbrunel/qtpromise/blob/master/HURZ" ) ),
	};

	auto allDownloads = QtPromise::all( filesToDownload );
	allDownloads
	    .then( []( const QVector<QByteArray>& downloadedFiles ) {
		    for( const auto& data : downloadedFiles )
		    {
			    qDebug() << data.size() << ": " << data.left( 25 );
		    }
	    } )
	    .fail( []( std::pair<QUrl, QNetworkReply::NetworkError> error ) {
		    qDebug() << "FAILED: " << error.second << "(" << error.first << ")";
	    } )
	    .fail( []( QNetworkReply::NetworkError error ) { qDebug() << "FAILED: " << error; } )
	    .fail( []( QString error ) { qDebug() << "FAILED: " << error; } )
	    .fail( []() { std::cerr << "FAILED" << std::endl; } )
	    .finally( []() { qDebug() << "FINALLY"; } );
	allDownloads.wait();
	qDebug() << "WAIT";
}

Raising the minimum requirement for Qt to Qt 5.6 LTS

@simonbrunel Hi! How attached are you to the minimum required Qt version? Any particular reason for choosing 5.4 instead of, say version 5.6 (LTS and oldest version still supported by Qt)? The reason I'm asking is because I'm having trouble making my unit tests pass on Travis due to Qt bugs present in 5.4, which are fixed in 5.6. Appveyor tests already use 5.6 as minimum version and are doing fine.

Other than that I also had to upgrade the GCC on Travis from 4.8 to 4.9, also due to a bug in the old version. The tests still comply to the minimum requirement of using C++11, so this is fine I guess?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.