golang-wasm/src/bridge.js

120 lines
3.7 KiB
JavaScript

// Initially, the __go_wasm__ object will be an empty object.
const g = global || window || self;
if (!g.__go_wasm__) {
g.__go_wasm__ = {};
}
/**
* The maximum amount of time that we would expect Wasm to take to initialize.
* If it doesn't initialize after this time, we send a warning to console.
* Most likely something has gone wrong if it takes more than 3 seconds to initialize.
*/
const maxTime = 3 * 1000;
/**
* bridge is an easier way to refer to the Go WASM object.
*/
const bridge = g.__go_wasm__;
/**
* Wrapper is used by Go to run all Go functions in JS.
*
* @param {Function} goFunc a function that is expected to return an object of the following specification:
* {
* result: undefined | any // undefined when error is returned, or function returns undefined
* error: Error | undefined // undefined when no error is present
* }
*
* @returns {Function} returns a function that take arguments which are used to call the Go function.
*/
function wrapper(goFunc) {
return (...args) => {
const result = goFunc.apply(undefined, args);
if (result.error instanceof Error) {
throw result.error;
}
return result.result;
}
}
/**
* Sleep is used when awaiting for Go Wasm to initialize.
* It uses the lowest possible sane delay time (via requestAnimationFrame).
* However, if the window is not focused, requestAnimationFrame never returns.
* A timeout will ensure to be called after 50 ms, regardless of whether or not the tab is in focus.
*
* @returns {Promise} an always-resolving promise when a tick has been completed
*/
function sleep() {
return new Promise((res) => {
requestAnimationFrame(() => res());
setTimeout(() => {
res();
}, 50);
});
}
/**
* @param {ArrayBuffer} getBytes a promise that is bytes of the Go Wasm object.
*
* @returns {Proxy} an object that can be used to call Wasm's objects and properly parse their results.
*
* All values that want to be retrieved from the proxy, regardless of if they are a function or not, must be retrieved
* as if they are from a function call.
*
* If a non-function value is returned however arguments are provided, a warning will be printed.
*/
export default function (getBytes) {
let proxy;
async function init() {
bridge.__wrapper__ = wrapper;
const go = new g.Go();
let bytes = await getBytes;
let result = await WebAssembly.instantiate(bytes, go.importObject);
go.run(result.instance);
}
init();
setTimeout(() => {
if (bridge.__ready__ !== true) {
console.warn("Golang WASM Bridge (__go_wasm__.__ready__) still not true after max time");
}
}, maxTime);
proxy = new Proxy(
{},
{
get: (_, key) => {
return (...args) => {
return new Promise(async (res, rej) => {
while (bridge.__ready__ !== true) {
await sleep();
}
if (typeof bridge[key] !== 'function') {
res(bridge[key]);
if (args.length !== 0) {
console.warn("Retrieved value from WASM returned non-error type, however called with arguments.")
}
return;
}
try {
res(bridge[key].apply(undefined, args));
} catch (e) {
rej(e);
}
})
};
}
}
);
bridge.__proxy__ = proxy;
return proxy;
}