Let's take an example to show why the rule/design principle is important.
Requirements: Read a file by a thread, then upload to a server. It should show a loading spinner until it is finished.
QFuture<void> ExampleClass::readAndUpload(QString file) {
auto read = [](QString file) -> QByteArray {
// ...
};
auto upload = [](QByteArray data) {
// ...
return observe(reply, &QNetworkReply::finished)
};
auto cleanup = []() {
qDebug() << "Done"; // Now it only print a done on finished, but it could be changed to do validation / state changes
};
return observe(QtConcurrent::run(read, file)).subscribe(upload).subscribe(cleanup).future()
}
That is a typical example of an asynchronous flow with multiple steps. That may look complicated, but this kind of problem is complicated anyway. Alternative solutions (except async/await) are usually more troublesome to manage.
AsyncFuture solves this by chaining the callback functions. A callback function may take input from the previous step and return another QFuture for next step to wait for (optional).
Therefore, it is very important that a chain function (subscribe) should return a new object to represent a new step. As it doesn't know how many steps are needed, user terminate the chain when he/she think that is enough.
observe(f1).combine(f2).combine(f3) and observe(future).onCompleted().onCanceled() are bad because they returns an existing object instead of create a new (No matter that is QFuture or custom Promise). The new API are inconsistent with subscribe . But subscribe is more important because it solves the most troublesome problem in asynchronous programming.
p.s That should be fine.
auto observer = observe(future);
observer.onCompleted(callback1);
observer.onCanceled(callback2);
// observer.onCompleted(callback1).onCanceled(callback2) are not equivalent to above code