Getting return value from JS callback

Godot Version

Godot 4

Question

Good day. I’m very new to Godot and trying to figure out Godot ↔ JavaScript communication. I got to the point, where I can call gdscript functions from JS scripts, but struggle to figure out straightforward way to get the returned value.

I have the following code:

extends Node2D

var window = JavaScriptBridge.get_interface("window")
var console = JavaScriptBridge.get_interface("console")

func _ready():
	window.test_cb = test_cb
	var testCbRes = _gdCallback([1,2,3])
	prints("testCb result: ", testCbRes)

func _gdCallback(args):
	prints("_gdCallback args: ", args)
	return "_gdCallback result"

var test_cb = JavaScriptBridge.create_callback(_gdCallback)

func _process(delta):
	pass

When I run it the browser, I see print from _ready(), which prints expected result returned from _gdCallback(args) function:

testCb result:  _gdCallback result

But when I go into browser console and call window.test_cb("a", "b"), I see print _gdCallback(args) being printed out as _gdCallback args: ["a", "b"], but returned value is null:

image

How do I get the result of the _gdCallback(args) from the JS side?

Thank you for help!

1 Like

I got the excact same issue.
In my instance, the problem is, that godot directly returns null, without anything waiting for the function to finish, which leads to my JS code working on incomplete information (my JS code is supposed to provide data for an updated scene, but after the call the scene is not updated.

I’ve found some workaround for how to get result of functions with await, but wouldn’t mind to know how to do it right.

The workaround is to pass JS callback into Godot callback. E.g. define gdscript part like this

var get_something = JavaScriptBridge.create_callback(_getSomething)

func _getSomething(args):
	var something: Something = await get_something()
	var responseResult = something.to_string()
	var jsCallback: JavaScriptObject = args[0]
	var callRes = jsCallback.call("call", jsCallback.this, responseResult) # call JS callback
	console.log("callRes", callRes)  # can even get returned value
	return responseResult # no idea how to get value from `return` in the browser

Then add this callback to window, e.g.:

func _ready():
	window.get_something = get_something

Then include script like this into the export template:

<script> 
	function testGetSomething() { 
		let { promise, resolve, reject } = Promise.withResolvers();
		window.get_something(resolve);
		return promise; 
	}
</script>

And then from the browser it can be called like

testGetSomething().then((r) => console.log("Got something:", r))

I’m doing something similar now.
Essentially, instead of having JS await the result, I’m using a second callback and wait in the JS side till that callback returns.

Something along the lines of:
Godot side:

var _get_data_callback = JavaScriptBridge.create_callback(_get_state_data)
var window = JavaScriptBridge.get_interface("window")

func _ready():	
	window.registerUpdateDataCallback(_get_data_callback)

func _get_state_data(args):	
	state_json = JSON.stringify(state)	
	window.setData(state_json)	
	return state_json

Javascript side (all in a vue component (that’s where this comes from):

methods: {
async fetchData() {
        this.getData() // this is the function godot binds 
    },
updateDataCallback(dataCallBack: Function) {
      this.getData = dataCallBack
    },
setTangramData(data) {
      // this is called after fetchData is called in the callback from godot.
      this.tangramMessage = data
      this.submitData()
    },
},
mounted() {
const iframeContentNew = this.iframe.contentWindow
iframeContentNew.registerUpdateDataCallback = this.updateDataCallback.bind(this)
iframeContentNew.setData = this.setTangramData.bind(this)
}

Overall this feels very ugly and I would much rather have some await in the js code to do this properly, but haven’t found a way to do this