Create a promise wrapper for C++ and port existing uses

Currently, to use a promise from C++ we either have to use an ASM block
(which does not work well with dynamic linking) or declare exports in
the EMSCRIPTEN_BINDINGS block, which is cumbersome and cannot be chained.
This solution makes it easy to use js promises by introducing the
WebPromiseManager which dispatches callbacks to appropriate callers when
available.
This is a preliminary patch for FileSystem support, which will heavily
use async APIs.

Task-number: QTBUG-99611
Change-Id: I368a8f173027eaa883a9ca18d0ea6a3e99b86071
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Mikolaj Boc 2022-06-21 17:50:04 +02:00
parent 97665c9615
commit d0eba2449a
7 changed files with 977 additions and 99 deletions

View File

@ -1359,3 +1359,18 @@ if(APPLE AND QT_FEATURE_framework AND QT_FEATURE_separate_debug_info)
DESTINATION "${dsym_script_install_dir}"
)
endif()
if(WASM)
set(wasm_injections
"${CMAKE_CURRENT_SOURCE_DIR}/platform/wasm/qtcontextfulpromise_injection.js"
)
qt_internal_add_resource(Core "wasminjections"
PREFIX
"/injections"
BASE
"${CMAKE_CURRENT_SOURCE_DIR}/platform/wasm"
FILES
${wasm_injections}
)
endif()

View File

@ -3,7 +3,10 @@
#include "qstdweb_p.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qfile.h>
#include <emscripten/bind.h>
#include <emscripten/emscripten.h>
#include <cstdint>
#include <iostream>
@ -11,7 +14,187 @@ QT_BEGIN_NAMESPACE
namespace qstdweb {
const char makeContextfulPromiseFunctionName[] = "makePromise";
typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
namespace {
enum class CallbackType {
Then,
Catch,
Finally,
};
void validateCallbacks(const PromiseCallbacks& callbacks) {
Q_ASSERT(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc);
}
void injectScript(const std::string& source, const std::string& injectionName)
{
using namespace emscripten;
auto script = val::global("document").call<val>("createElement", val("script"));
auto head = val::global("document").call<val>("getElementsByTagName", val("head"));
script.call<void>("setAttribute", val("qtinjection"), val(injectionName));
script.set("innerText", val(source));
head[0].call<void>("appendChild", std::move(script));
}
using PromiseContext = int;
class WebPromiseManager
{
public:
static const char contextfulPromiseSupportObjectName[];
static const char webPromiseManagerCallbackThunkExportName[];
WebPromiseManager();
~WebPromiseManager();
WebPromiseManager(const WebPromiseManager& other) = delete;
WebPromiseManager(WebPromiseManager&& other) = delete;
WebPromiseManager& operator=(const WebPromiseManager& other) = delete;
WebPromiseManager& operator=(WebPromiseManager&& other) = delete;
void adoptPromise(emscripten::val target, PromiseCallbacks callbacks);
static WebPromiseManager* get();
static void callbackThunk(emscripten::val callbackType, emscripten::val context, emscripten::val result);
private:
static std::optional<CallbackType> parseCallbackType(emscripten::val callbackType);
void subscribeToJsPromiseCallbacks(const PromiseCallbacks& callbacks, emscripten::val jsContextfulPromise);
void callback(CallbackType type, emscripten::val context, emscripten::val result);
void registerPromise(PromiseContext context, PromiseCallbacks promise);
void unregisterPromise(PromiseContext context);
QHash<PromiseContext, PromiseCallbacks> m_promiseRegistry;
int m_nextContextId = 0;
};
static void qStdWebCleanup()
{
auto window = emscripten::val::global("window");
auto contextfulPromiseSupport = window[WebPromiseManager::contextfulPromiseSupportObjectName];
if (contextfulPromiseSupport.isUndefined())
return;
contextfulPromiseSupport.call<void>("removeRef");
}
const char WebPromiseManager::webPromiseManagerCallbackThunkExportName[] = "qtStdWebWebPromiseManagerCallbackThunk";
const char WebPromiseManager::contextfulPromiseSupportObjectName[] = "qtContextfulPromiseSupport";
Q_GLOBAL_STATIC(WebPromiseManager, webPromiseManager)
WebPromiseManager::WebPromiseManager()
{
QFile injection(QStringLiteral(":/injections/qtcontextfulpromise_injection.js"));
if (!injection.open(QIODevice::ReadOnly))
qFatal("Missing resource");
injectScript(injection.readAll().toStdString(), "contextfulpromise");
qAddPostRoutine(&qStdWebCleanup);
}
std::optional<CallbackType>
WebPromiseManager::parseCallbackType(emscripten::val callbackType)
{
if (!callbackType.isString())
return std::nullopt;
const std::string data = callbackType.as<std::string>();
if (data == "then")
return CallbackType::Then;
if (data == "catch")
return CallbackType::Catch;
if (data == "finally")
return CallbackType::Finally;
return std::nullopt;
}
WebPromiseManager::~WebPromiseManager() = default;
WebPromiseManager *WebPromiseManager::get()
{
return webPromiseManager();
}
void WebPromiseManager::callbackThunk(emscripten::val callbackType,
emscripten::val context,
emscripten::val result)
{
auto parsedCallbackType = parseCallbackType(callbackType);
if (!parsedCallbackType) {
qFatal("Bad callback type");
}
WebPromiseManager::get()->callback(*parsedCallbackType, context, std::move(result));
}
void WebPromiseManager::subscribeToJsPromiseCallbacks(const PromiseCallbacks& callbacks, emscripten::val jsContextfulPromiseObject) {
using namespace emscripten;
if (Q_LIKELY(callbacks.thenFunc))
jsContextfulPromiseObject = jsContextfulPromiseObject.call<val>("then");
if (callbacks.catchFunc)
jsContextfulPromiseObject = jsContextfulPromiseObject.call<val>("catch");
if (callbacks.finallyFunc)
jsContextfulPromiseObject = jsContextfulPromiseObject.call<val>("finally");
}
void WebPromiseManager::callback(CallbackType type, emscripten::val context, emscripten::val result)
{
auto found = m_promiseRegistry.find(context.as<PromiseContext>());
if (found == m_promiseRegistry.end()) {
return;
}
bool expectingOtherCallbacks;
switch (type) {
case CallbackType::Then:
found->thenFunc(result);
// At this point, if there is no finally function, we are sure that the Catch callback won't be issued.
expectingOtherCallbacks = !!found->finallyFunc;
break;
case CallbackType::Catch:
found->catchFunc(result);
expectingOtherCallbacks = !!found->finallyFunc;
break;
case CallbackType::Finally:
found->finallyFunc();
expectingOtherCallbacks = false;
break;
}
if (!expectingOtherCallbacks)
unregisterPromise(context.as<int>());
}
void WebPromiseManager::registerPromise(PromiseContext context, PromiseCallbacks callbacks)
{
m_promiseRegistry.emplace(context, std::move(callbacks));
}
void WebPromiseManager::unregisterPromise(PromiseContext context)
{
m_promiseRegistry.remove(context);
}
void WebPromiseManager::adoptPromise(emscripten::val target, PromiseCallbacks callbacks) {
emscripten::val context(m_nextContextId++);
auto jsContextfulPromise = emscripten::val::global("window")
[contextfulPromiseSupportObjectName].call<emscripten::val>(
makeContextfulPromiseFunctionName, target, context,
emscripten::val::module_property(webPromiseManagerCallbackThunkExportName));
subscribeToJsPromiseCallbacks(callbacks, jsContextfulPromise);
registerPromise(context.as<int>(), std::move(callbacks));
}
} // namespace
ArrayBuffer::ArrayBuffer(uint32_t size)
{
@ -167,6 +350,10 @@ File FileList::operator[](int index) const
return item(index);
}
emscripten::val FileList::val() {
return m_fileList;
}
ArrayBuffer FileReader::result() const
{
return ArrayBuffer(m_fileReader["result"]);
@ -322,6 +509,61 @@ std::string EventCallback::contextPropertyName(const std::string &eventName)
EMSCRIPTEN_BINDINGS(qtStdwebCalback) {
emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate);
emscripten::function(WebPromiseManager::webPromiseManagerCallbackThunkExportName, &WebPromiseManager::callbackThunk);
}
namespace Promise {
void adoptPromise(emscripten::val promiseObject, PromiseCallbacks callbacks) {
validateCallbacks(callbacks);
WebPromiseManager::get()->adoptPromise(
std::move(promiseObject), std::move(callbacks));
}
void all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks) {
struct State {
std::map<int, emscripten::val> results;
int remainingThenCallbacks;
int remainingFinallyCallbacks;
};
validateCallbacks(callbacks);
auto state = std::make_shared<State>();
state->remainingThenCallbacks = state->remainingFinallyCallbacks = promises.size();
for (size_t i = 0; i < promises.size(); ++i) {
PromiseCallbacks individualPromiseCallback;
if (callbacks.thenFunc) {
individualPromiseCallback.thenFunc = [i, state, callbacks](emscripten::val partialResult) mutable {
state->results.emplace(i, std::move(partialResult));
if (!--(state->remainingThenCallbacks)) {
std::vector<emscripten::val> transformed;
for (auto& data : state->results) {
transformed.push_back(std::move(data.second));
}
callbacks.thenFunc(emscripten::val::array(std::move(transformed)));
}
};
}
if (callbacks.catchFunc) {
individualPromiseCallback.catchFunc = [state, callbacks](emscripten::val error) mutable {
callbacks.catchFunc(error);
};
}
individualPromiseCallback.finallyFunc = [state, callbacks]() mutable {
if (!--(state->remainingFinallyCallbacks)) {
if (callbacks.finallyFunc)
callbacks.finallyFunc();
// Explicitly reset here for verbosity, this would have been done automatically with the
// destruction of the adopted promise in WebPromiseManager.
state.reset();
}
};
adoptPromise(std::move(promises.at(i)), std::move(individualPromiseCallback));
}
}
}
} // namespace qstdweb

View File

@ -19,12 +19,14 @@
#include <emscripten/val.h>
#include <cstdint>
#include <functional>
#include "initializer_list"
#include <QtCore/qglobal.h>
#include "QtCore/qhash.h"
QT_BEGIN_NAMESPACE
namespace qstdweb {
extern const char makeContextfulPromiseFunctionName[];
// DOM API in C++, implemented using emscripten val.h and bind.h.
// This is private API and can be extended and changed as needed.
@ -153,6 +155,34 @@ namespace qstdweb {
std::string m_eventName;
std::function<void(emscripten::val)> m_fn;
};
struct PromiseCallbacks
{
std::function<void(emscripten::val)> thenFunc;
std::function<void(emscripten::val)> catchFunc;
std::function<void()> finallyFunc;
};
namespace Promise {
void adoptPromise(emscripten::val promise, PromiseCallbacks callbacks);
template<typename... Args>
void make(emscripten::val target,
QString methodName,
PromiseCallbacks callbacks,
Args... args)
{
emscripten::val promiseObject = target.call<emscripten::val>(
methodName.toStdString().c_str(), std::forward<Args>(args)...);
if (promiseObject.isUndefined() || promiseObject["constructor"]["name"].as<std::string>() != "Promise") {
qFatal("This function did not return a promise");
}
adoptPromise(std::move(promiseObject), std::move(callbacks));
}
void all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks);
};
}
QT_END_NAMESPACE

View File

@ -0,0 +1,32 @@
`Copyright (C) 2022 The Qt Company Ltd.
Copyright (C) 2016 Intel Corporation.
SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0`;
if (window.qtContextfulPromiseSupport) {
++window.qtContextfulPromiseSupport.refs;
} else {
window.qtContextfulPromiseSupport = {
refs: 1,
removeRef: () => {
--window.qtContextfulPromiseSupport.refs, 0 === window.qtContextfulPromiseSupport.refs && delete window.qtContextfulPromiseSupport;
},
makePromise: (a, b, c) => new window.qtContextfulPromiseSupport.ContextfulPromise(a, b, c),
};
window.qtContextfulPromiseSupport.ContextfulPromise = class {
constructor(a, b, c) {
(this.wrappedPromise = a), (this.context = b), (this.callbackThunk = c);
}
then() {
return (this.wrappedPromise = this.wrappedPromise.then((a) => { this.callbackThunk("then", this.context, a); })), this;
}
catch() {
return (this.wrappedPromise = this.wrappedPromise.catch((a) => { this.callbackThunk("catch", this.context, a); })), this;
}
finally() {
return (this.wrappedPromise = this.wrappedPromise.finally(() => this.callbackThunk("finally", this.context, undefined))), this;
}
};
}
document.querySelector("[qtinjection=contextfulpromise]")?.remove();

View File

@ -19,83 +19,6 @@
QT_BEGIN_NAMESPACE
using namespace emscripten;
static void pasteClipboardData(emscripten::val format, emscripten::val dataPtr)
{
QString formatString = QWasmString::toQString(format);
QByteArray dataArray = QByteArray::fromStdString(dataPtr.as<std::string>());
QMimeData *mMimeData = new QMimeData;
mMimeData->setData(formatString, dataArray);
QWasmClipboard::qWasmClipboardPaste(mMimeData);
// QWasmIntegration::get()->getWasmClipboard()->isPaste = false;
}
static void qClipboardPasteResolve(emscripten::val blob)
{
// read Blob here
auto fileReader = std::make_shared<qstdweb::FileReader>();
auto _blob = qstdweb::Blob(blob);
QString formatString = QString::fromStdString(_blob.type());
fileReader->readAsArrayBuffer(_blob);
char *chunkBuffer = nullptr;
qstdweb::ArrayBuffer result = fileReader->result();
qstdweb::Uint8Array(result).copyTo(chunkBuffer);
QMimeData *mMimeData = new QMimeData;
mMimeData->setData(formatString, chunkBuffer);
QWasmClipboard::qWasmClipboardPaste(mMimeData);
}
static void qClipboardPromiseResolve(emscripten::val clipboardItems)
{
int itemsCount = clipboardItems["length"].as<int>();
for (int i = 0; i < itemsCount; i++) {
int typesCount = clipboardItems[i]["types"]["length"].as<int>(); // ClipboardItem
std::string mimeFormat = clipboardItems[i]["types"][0].as<std::string>();
if (mimeFormat.find(std::string("text")) != std::string::npos) {
// simple val object, no further processing
val navigator = val::global("navigator");
val textPromise = navigator["clipboard"].call<val>("readText");
val readTextResolve = val::global("Module")["qtClipboardTextPromiseResolve"];
textPromise.call<val>("then", readTextResolve);
} else {
// binary types require additional processing
for (int j = 0; j < typesCount; j++) {
val pasteResolve = emscripten::val::module_property("qtClipboardPasteResolve");
val pasteException = emscripten::val::module_property("qtClipboardPromiseException");
// get the blob
clipboardItems[i]
.call<val>("getType", clipboardItems[i]["types"][j])
.call<val>("then", pasteResolve)
.call<val>("catch", pasteException);
}
}
}
}
static void qClipboardCopyPromiseResolve(emscripten::val something)
{
Q_UNUSED(something)
qWarning() << "copy succeeeded";
}
static emscripten::val qClipboardPromiseException(emscripten::val something)
{
qWarning() << "clipboard error"
<< QString::fromStdString(something["name"].as<std::string>())
<< QString::fromStdString(something["message"].as<std::string>());
return something;
}
static void commonCopyEvent(val event)
{
QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
@ -215,24 +138,10 @@ static void qClipboardPasteTo(val dataTransfer)
QWasmIntegration::get()->getWasmClipboard()->m_isListener = false;
}
static void qClipboardTextPromiseResolve(emscripten::val clipdata)
{
pasteClipboardData(emscripten::val("text/plain"), clipdata);
}
EMSCRIPTEN_BINDINGS(qtClipboardModule) {
function("qtPasteClipboardData", &pasteClipboardData);
function("qtClipboardTextPromiseResolve", &qClipboardTextPromiseResolve);
function("qtClipboardPromiseResolve", &qClipboardPromiseResolve);
function("qtClipboardCopyPromiseResolve", &qClipboardCopyPromiseResolve);
function("qtClipboardPromiseException", &qClipboardPromiseException);
function("qtClipboardCutTo", &qClipboardCutTo);
function("qtClipboardCopyTo", &qClipboardCopyTo);
function("qtClipboardPasteTo", &qClipboardPasteTo);
function("qtClipboardPasteResolve", &qClipboardPasteResolve);
}
QWasmClipboard::QWasmClipboard() :
@ -419,14 +328,18 @@ void QWasmClipboard::writeToClipboardApi()
// break;
}
val copyResolve = emscripten::val::module_property("qtClipboardCopyPromiseResolve");
val copyException = emscripten::val::module_property("qtClipboardPromiseException");
val navigator = val::global("navigator");
navigator["clipboard"]
.call<val>("write", clipboardWriteArray)
.call<val>("then", copyResolve)
.call<val>("catch", copyException);
qstdweb::Promise::make(
navigator["clipboard"], "write",
{
.catchFunc = [](emscripten::val error) {
qWarning() << "clipboard error"
<< QString::fromStdString(error["name"].as<std::string>())
<< QString::fromStdString(error["message"].as<std::string>());
}
},
clipboardWriteArray);
}
void QWasmClipboard::writeToClipboard(const QMimeData *data)

View File

@ -0,0 +1,17 @@
#####################################################################
## tst_wasm Test:
#####################################################################
qt_internal_add_test(tst_qstdweb
SOURCES
tst_qstdweb.cpp
DEFINES
QT_NO_FOREACH
QT_NO_KEYWORDS
LIBRARIES
Qt::GuiPrivate
PUBLIC_LIBRARIES
Qt::Core
Qt::Gui
Qt::Widgets
)

View File

@ -0,0 +1,629 @@
// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtCore/qtimer.h>
#include <QtCore/private/qstdweb_p.h>
#include <QTest>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
#if defined(QT_HAVE_EMSCRIPTEN_ASYNCIFY)
#define SKIP_IF_NO_ASYNCIFY()
#else
#define SKIP_IF_NO_ASYNCIFY() QSKIP("Needs QT_HAVE_EMSCRIPTEN_ASYNCIFY")
#endif
using namespace emscripten;
class tst_QStdWeb : public QObject
{
Q_OBJECT
public:
tst_QStdWeb() : m_window(val::global("window")), m_testSupport(val::object()) {
instance = this;
m_window.set("testSupport", m_testSupport);
}
~tst_QStdWeb() noexcept {}
private:
static tst_QStdWeb* instance;
void init() {
EM_ASM({
testSupport.resolve = {};
testSupport.reject = {};
testSupport.promises = {};
testSupport.waitConditionPromise = new Promise((resolve, reject) => {
testSupport.finishWaiting = resolve;
});
testSupport.makeTestPromise = (param) => {
testSupport.promises[param] = new Promise((resolve, reject) => {
testSupport.resolve[param] = resolve;
testSupport.reject[param] = reject;
});
return testSupport.promises[param];
};
});
}
val m_window;
val m_testSupport;
private Q_SLOTS:
void simpleResolve();
void multipleResolve();
void simpleReject();
void multipleReject();
void throwInThen();
void bareFinally();
void finallyWithThen();
void finallyWithThrow();
void finallyWithThrowInThen();
void nested();
void all();
void allWithThrow();
void allWithFinally();
void allWithFinallyAndThrow();
};
tst_QStdWeb* tst_QStdWeb::instance = nullptr;
EM_ASYNC_JS(void, awaitCondition, (), {
await testSupport.waitConditionPromise;
});
void tst_QStdWeb::simpleResolve()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
QVERIFY(result.isString());
QCOMPARE("Some lovely data", result.as<std::string>());
EM_ASM({
testSupport.finishWaiting();
});
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QFAIL("Unexpected catch");
}
}, std::string("simpleResolve"));
EM_ASM({
testSupport.resolve["simpleResolve"]("Some lovely data");
});
awaitCondition();
}
void tst_QStdWeb::multipleResolve()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
QVERIFY(result.isString());
QCOMPARE("Data 1", result.as<std::string>());
EM_ASM({
if (!--testSupport.promisesLeft) {
testSupport.finishWaiting();
}
});
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QFAIL("Unexpected catch");
}
}, std::string("1"));
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
QVERIFY(result.isString());
QCOMPARE("Data 2", result.as<std::string>());
EM_ASM({
if (!--testSupport.promisesLeft) {
testSupport.finishWaiting();
}
});
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QFAIL("Unexpected catch");
}
}, std::string("2"));
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
QVERIFY(result.isString());
QCOMPARE("Data 3", result.as<std::string>());
EM_ASM({
if (!--testSupport.promisesLeft) {
testSupport.finishWaiting();
}
});
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QFAIL("Unexpected catch");
}
}, std::string("3"));
EM_ASM({
testSupport.resolve["3"]("Data 3");
testSupport.resolve["1"]("Data 1");
testSupport.resolve["2"]("Data 2");
});
awaitCondition();
}
void tst_QStdWeb::simpleReject()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
Q_UNUSED(result);
QFAIL("Unexpected then");
},
.catchFunc = [](val result) {
QVERIFY(result.isString());
QCOMPARE("Evil error", result.as<std::string>());
EM_ASM({
testSupport.finishWaiting();
});
}
}, std::string("simpleReject"));
EM_ASM({
testSupport.reject["simpleReject"]("Evil error");
});
awaitCondition();
}
void tst_QStdWeb::multipleReject()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
Q_UNUSED(result);
QFAIL("Unexpected then");
},
.catchFunc = [](val error) {
QVERIFY(error.isString());
QCOMPARE("Error 1", error.as<std::string>());
EM_ASM({
if (!--testSupport.promisesLeft) {
testSupport.finishWaiting();
}
});
}
}, std::string("1"));
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
Q_UNUSED(result);
QFAIL("Unexpected then");
},
.catchFunc = [](val error) {
QVERIFY(error.isString());
QCOMPARE("Error 2", error.as<std::string>());
EM_ASM({
if (!--testSupport.promisesLeft) {
testSupport.finishWaiting();
}
});
}
}, std::string("2"));
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
Q_UNUSED(result);
QFAIL("Unexpected then");
},
.catchFunc = [](val error) {
QVERIFY(error.isString());
QCOMPARE("Error 3", error.as<std::string>());
EM_ASM({
if (!--testSupport.promisesLeft) {
testSupport.finishWaiting();
}
});
}
}, std::string("3"));
EM_ASM({
testSupport.reject["3"]("Error 3");
testSupport.reject["1"]("Error 1");
testSupport.reject["2"]("Error 2");
});
awaitCondition();
}
void tst_QStdWeb::throwInThen()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
Q_UNUSED(result);
EM_ASM({
throw "Expected error";
});
},
.catchFunc = [](val error) {
QCOMPARE("Expected error", error.as<std::string>());
EM_ASM({
testSupport.finishWaiting();
});
}
}, std::string("throwInThen"));
EM_ASM({
testSupport.resolve["throwInThen"]();
});
awaitCondition();
}
void tst_QStdWeb::bareFinally()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.finallyFunc = []() {
EM_ASM({
testSupport.finishWaiting();
});
}
}, std::string("bareFinally"));
EM_ASM({
testSupport.resolve["bareFinally"]();
});
awaitCondition();
}
void tst_QStdWeb::finallyWithThen()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [] (val result) {
Q_UNUSED(result);
},
.finallyFunc = []() {
EM_ASM({
testSupport.finishWaiting();
});
}
}, std::string("finallyWithThen"));
EM_ASM({
testSupport.resolve["finallyWithThen"]();
});
awaitCondition();
}
void tst_QStdWeb::finallyWithThrow()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.catchFunc = [](val error) {
Q_UNUSED(error);
},
.finallyFunc = []() {
EM_ASM({
testSupport.finishWaiting();
});
}
}, std::string("finallyWithThrow"));
EM_ASM({
testSupport.reject["finallyWithThrow"]();
});
awaitCondition();
}
void tst_QStdWeb::finallyWithThrowInThen()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
Q_UNUSED(result);
EM_ASM({
throw "Expected error";
});
},
.catchFunc = [](val result) {
QVERIFY(result.isString());
QCOMPARE("Expected error", result.as<std::string>());
},
.finallyFunc = []() {
EM_ASM({
testSupport.finishWaiting();
});
}
}, std::string("bareFinallyWithThen"));
EM_ASM({
testSupport.resolve["bareFinallyWithThen"]();
});
awaitCondition();
}
void tst_QStdWeb::nested()
{
SKIP_IF_NO_ASYNCIFY();
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [this](val result) {
QVERIFY(result.isString());
QCOMPARE("Outer data", result.as<std::string>());
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [this](val innerResult) {
QVERIFY(innerResult.isString());
QCOMPARE("Inner data", innerResult.as<std::string>());
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val innerResult) {
QVERIFY(innerResult.isString());
QCOMPARE("Innermost data", innerResult.as<std::string>());
EM_ASM({
testSupport.finishWaiting();
});
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QFAIL("Unexpected catch");
}
}, std::string("innermost"));
EM_ASM({
testSupport.finishWaiting();
});
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QFAIL("Unexpected catch");
}
}, std::string("inner"));
EM_ASM({
testSupport.finishWaiting();
});
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QFAIL("Unexpected catch");
}
}, std::string("outer"));
EM_ASM({
testSupport.resolve["outer"]("Outer data");
});
awaitCondition();
EM_ASM({
testSupport.waitConditionPromise = new Promise((resolve, reject) => {
testSupport.finishWaiting = resolve;
});
});
EM_ASM({
testSupport.resolve["inner"]("Inner data");
});
awaitCondition();
EM_ASM({
testSupport.waitConditionPromise = new Promise((resolve, reject) => {
testSupport.finishWaiting = resolve;
});
});
EM_ASM({
testSupport.resolve["innermost"]("Innermost data");
});
awaitCondition();
}
void tst_QStdWeb::all()
{
SKIP_IF_NO_ASYNCIFY();
init();
val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
auto thenCalledOnce = std::shared_ptr<bool>();
*thenCalledOnce = true;
qstdweb::Promise::all({promise1, promise2, promise3}, {
.thenFunc = [thenCalledOnce](val result) {
QVERIFY(*thenCalledOnce);
*thenCalledOnce = false;
QVERIFY(result.isArray());
QCOMPARE(3, result["length"].as<int>());
QCOMPARE("Data 1", result[0].as<std::string>());
QCOMPARE("Data 2", result[1].as<std::string>());
QCOMPARE("Data 3", result[2].as<std::string>());
EM_ASM({
testSupport.finishWaiting();
});
},
.catchFunc = [](val result) {
Q_UNUSED(result);
EM_ASM({
throw new Error("Unexpected error");
});
}
});
EM_ASM({
testSupport.resolve["promise3"]("Data 3");
testSupport.resolve["promise1"]("Data 1");
testSupport.resolve["promise2"]("Data 2");
});
awaitCondition();
}
void tst_QStdWeb::allWithThrow()
{
SKIP_IF_NO_ASYNCIFY();
init();
val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
auto catchCalledOnce = std::shared_ptr<bool>();
*catchCalledOnce = true;
qstdweb::Promise::all({promise1, promise2, promise3}, {
.thenFunc = [](val result) {
Q_UNUSED(result);
QFAIL("Unexpected then");
},
.catchFunc = [catchCalledOnce](val result) {
QVERIFY(*catchCalledOnce);
*catchCalledOnce = false;
QVERIFY(result.isString());
QCOMPARE("Error 2", result.as<std::string>());
EM_ASM({
testSupport.finishWaiting();
});
}
});
EM_ASM({
testSupport.resolve["promise3"]("Data 3");
testSupport.resolve["promise1"]("Data 1");
testSupport.reject["promise2"]("Error 2");
});
awaitCondition();
}
void tst_QStdWeb::allWithFinally()
{
SKIP_IF_NO_ASYNCIFY();
init();
val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
auto finallyCalledOnce = std::shared_ptr<bool>();
*finallyCalledOnce = true;
qstdweb::Promise::all({promise1, promise2, promise3}, {
.thenFunc = [](val result) {
Q_UNUSED(result);
},
.finallyFunc = [finallyCalledOnce]() {
QVERIFY(*finallyCalledOnce);
*finallyCalledOnce = false;
EM_ASM({
testSupport.finishWaiting();
});
}
});
EM_ASM({
testSupport.resolve["promise3"]("Data 3");
testSupport.resolve["promise1"]("Data 1");
testSupport.resolve["promise2"]("Data 2");
});
awaitCondition();
}
void tst_QStdWeb::allWithFinallyAndThrow()
{
SKIP_IF_NO_ASYNCIFY();
init();
val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
auto finallyCalledOnce = std::shared_ptr<bool>();
*finallyCalledOnce = true;
qstdweb::Promise::all({promise1, promise2, promise3}, {
.thenFunc = [](val result) {
Q_UNUSED(result);
EM_ASM({
throw "This breaks it all!!!";
});
},
.finallyFunc = [finallyCalledOnce]() {
QVERIFY(*finallyCalledOnce);
*finallyCalledOnce = false;
EM_ASM({
testSupport.finishWaiting();
});
}
});
EM_ASM({
testSupport.resolve["promise3"]("Data 3");
testSupport.resolve["promise1"]("Data 1");
testSupport.resolve["promise2"]("Data 2");
});
awaitCondition();
}
QTEST_APPLESS_MAIN(tst_QStdWeb)
#include "tst_qstdweb.moc"