Skip to content

Interoperability

This page documents how interoperability between C++ and JavaScript works

Exposing Functions is a simple as calling expose. The function will then be available from JavaScript with the given name and parameters.

Example: Exposing a multiplication function
webview->expose("multiply", [](double a, double b) {
return a * b;
});

In the example shown above, the function would be callable from JavaScript as follows:

Example: Calling multiply from JavaScript
const result = await saucer.exposed.multiply(5, 10);

Function Parameters are automatically type-checked:

Example: Automatic Type-Checking
> await saucer.exposed.multiply("5", "10")
[Error] Expected parameter 0 to be of type 'double'

It is also possible to call JavaScript code and capture the result:

Example: Evaluating JavaScript
auto random = *co_await webview->evaluate<double>("Math.random()");
auto pow = *co_await webview->evaluate<double>("Math.pow({}, {})", 2, 5);

All calls to evaluate return an std::expected because JavaScript exceptions are caught:

Example: Catching JavaScript Exceptions
> auto res = co_await webview->evaluate<std::vector<int>>("new Array(-1)");
> std::println("Error: {}", res.error());
[Error] Error: RangeError

In case the result is not of importance to you, it is advised to use execute instead.

Example: Executing JavaScript
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_strings. Therefore it can be difficult to pass in code that is not known at compile-time (as strings will be serialized for JavaScript and will thus contain quotes).

To avoid the automatic quoting of strings, you can use saucer::unquoted:

Example: Using 'saucer::unquoted'
webview->execute("{}({})", saucer::unquoted{"console.log"}, "42");

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 three ways to do this, by throwing an exception, returning an std::expected or by taking a saucer::executor as the last parameter.

Example: Throwing an exception
webview->expose("divide", [](double a, double b) {
if (b == 0) {
throw std::runtime_error{"Divisor must not be zero"};
}
return a / b;
});
Example: 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;
});
Example: Promise Rejection
> await saucer.exposed.divide(10, 0)
[Error] Divisor must not be zero

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.

Example: Using 'saucer::executor'
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.

Example: Executor Lifetime
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:

Example: Executor with different Error-Type
webview->expose("error", [](const saucer::executor<void, double>& exec) {
exec.reject(42);
});

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: