swiftwasm/JavaScriptKit

Export function returning a JSValue

AndrewBarba opened this issue · 2 comments

I've followed the basic docs to call an exported function that takes in string/numbers and returns string/numbers but I'm struggling to figure out how to get the JS runtime to await a returned JSPromise from the Swift runtime.

I started with this approach of returning a pointer to the JSPromise:

@_cdecl("fetch")
func fetch() -> UnsafeRawPointer {
    let promise = JSPromise { resolve in
        resolve(.success(5))
    }
    var value = promise.jsValue()
    return withUnsafePointer(to: &value) { UnsafeRawPointer($0) }
}

But it's obviously not so clear how to deserialize this to an actual Promise in JS:

async function jsFetch() {
  let ptr = instance.exports.fetch()
  let promise = /** ??? */
  await promise
}

I'm very new to WASM so I apologize if this is a completely wrong approach, I just needed to get the ball rolling because I can't find anything else online about returning more complex values. I've been trying to understand how its accomplished in Rust but they have fancy annotations from the wasm-bindgen crate which is frankly over my head.

j-f1 commented

I think the best approach here would be to have the Swift side define a global value (by using JSObject.global.myLibrary = JSObject.global.Object.create(JSValue.null), for example), then defining properties such as fetch on that (using JSClosure). Then, from the JS side, you’d be able to pull the myLibrary value off of the global after initializing the SwiftWasm instance and call the fetch method.

I'm stunned... this works perfectly. Can even await the promise off the global scope.

Here's where I ended up:

@_cdecl("fetch")
func fetch() -> Int {
    let globalKey = Int.random(in: Int.min...Int.max)
    let promise = JSPromise { resolve in
        resolve(.success(5))
    }
    JSObject.global["\(globalKey)"] = promise.jsValue()
    return globalKey
}

And then calling in JS:

const key = instance.exports.fetch()
const result = await global[key]
console.log('swift#fetch():', result)

Obviously need to manage cleaning up the global scope but this is an awesome start. Feel free to close this as needed.