Interoperability
This page documents how interoperability between C++ and JavaScript works
Exposing Functions
Section titled “Exposing Functions”Exposing Functions is a simple as calling expose
.
The function will then be available from JavaScript with the given name and parameters.
webview->expose("multiply", [](double a, double b) { return a * b;});
In the example shown above, the function would be callable from JavaScript as follows:
const result = await saucer.exposed.multiply(5, 10);
Function Parameters are automatically type-checked:
> await saucer.exposed.multiply("5", "10")[Error] Expected parameter 0 to be of type 'double'
Executing JavaScript
Section titled “Executing JavaScript”It is also possible to call JavaScript code and capture the result:
auto random = co_await webview->evaluate<double>("Math.random()");auto pow = co_await webview->evaluate<double>("Math.pow({}, {})", 2, 5);
In case the result is not of importance to you, it is advised to use execute
instead.
webview->execute("console.log({})", saucer::make_args(1, "Test", std::vector<int>{4, 2}));
You may have noticed, that evaluate
as well as execute
take format_string
s.
Therefore it can be difficult to pass in code that is not known at compile-time (as strings are serialized to JavaScript and will thus contain quotes).
To avoid the automatic quoting of strings, you can use saucer::unquoted
:
webview->execute("{}({})", saucer::unquoted{"console.log"}, "42");
Advanced Usage
Section titled “Advanced Usage”All exposed functions return Promises in JavaScript. As seen above, in most use-cases a simple result is returned. However, it is also possible to reject the JavaScript Promise if desired.
There are two ways to do this, by returning a std::expected
or by taking a saucer::executor
.
Returning std::expected
Section titled “Returning std::expected”webview->expose("divide", [](double a, double b) -> std::expected<double, std::string> { if (b == 0) { return std::unexpected{"Divisor must not be zero"}; } return a / b;});
> await saucer.exposed.divide(10, 0)[Error] Divisor must not be zero
Using saucer::executor
Section titled “Using saucer::executor”Instead of resolving/rejecting the JavaScript Promise by returning a value, you can also take a saucer::executor
as the last parameter of your exposed function to manually handle the promise.
webview->expose("divide", [](double a, double b, const saucer::executor<double>& exec) { const auto& [resolve, reject] = exec;
if (b == 0) { return reject("Divisor must not be zero"); }
return resolve(a / b);});
Another advantage of using an executor is that you are in control of the Promises’ lifetime.
This means that you can spawn a thread and resolve the Promise at a later time.
webview->expose("wait", [](saucer::executor<double> exec) { std::thread t{[exec = std::move(exec)] { std::this_thread::sleep_for(std::chrono::seconds(5)); exec.resolve(42); }}; t.detach();});
In the examples above, saucer::executor
was only used with its default error type (std::string_view
).
It should be noted, that executors (as well as std::expected
) can also use different error-types:
webview->expose("error", [](const saucer::executor<void, double>& exec) { exec.reject(42);});
User Defined Types
Section titled “User Defined Types”The default serializer(s) support automatic serialization of aggregates, primitives as well as various STL types by default.
In case you need to add support for types that are not supported out of the box, please refer to the documentation of your respective serializer:
- glaze (Default): Documentation
- reflect-cpp: Documentation