From 7c62fbe3f227eb87c21295efddbb7dcf101a4bab Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 29 Mar 2024 23:07:39 +1100 Subject: [PATCH] webassembly/proxy_js: Promote Python thenable to a Promise. Signed-off-by: Damien George --- ports/webassembly/proxy_js.js | 17 +++++++++- .../ports/webassembly/fun_py_callback_js.mjs | 31 +++++++++++++++++++ .../webassembly/fun_py_callback_js.mjs.exp | 8 +++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/ports/webassembly/fun_py_callback_js.mjs create mode 100644 tests/ports/webassembly/fun_py_callback_js.mjs.exp diff --git a/ports/webassembly/proxy_js.js b/ports/webassembly/proxy_js.js index 7a0a1bbe89..1f60bf41e8 100644 --- a/ports/webassembly/proxy_js.js +++ b/ports/webassembly/proxy_js.js @@ -89,7 +89,22 @@ function proxy_call_python(target, argumentsList) { if (argumentsList.length > 0) { Module._free(args); } - return proxy_convert_mp_to_js_obj_jsside_with_free(value); + const ret = proxy_convert_mp_to_js_obj_jsside_with_free(value); + if (ret instanceof PyProxyThenable) { + // In Python when an async function is called it creates the + // corresponding "generator", which must then be executed at + // the top level by an asyncio-like scheduler. In JavaScript + // the semantics for async functions is that they are started + // immediately (their non-async prefix code is executed immediately) + // and only if they await do they return a Promise to delay the + // execution of the remainder of the function. + // + // Emulate the JavaScript behaviour here by resolving the Python + // async function. We assume that the caller who gets this + // return is JavaScript. + return Promise.resolve(ret); + } + return ret; } function proxy_convert_js_to_mp_obj_jsside(js_obj, out) { diff --git a/tests/ports/webassembly/fun_py_callback_js.mjs b/tests/ports/webassembly/fun_py_callback_js.mjs new file mode 100644 index 0000000000..5541e7a461 --- /dev/null +++ b/tests/ports/webassembly/fun_py_callback_js.mjs @@ -0,0 +1,31 @@ +// Test using Python functions as JS callbacks. + +const mp = await (await import(process.argv[2])).loadMicroPython(); + +globalThis.asyncTimeout = (ms) => + new Promise((resolve) => setTimeout(resolve, ms)); + +mp.runPython(` +import js + +def f0(): + print("f0 run") + +async def f1(): + print("f1 run") + +async def f2(): + print("f2 start") + await js.asyncTimeout(0) + print("f2 end") + +async def f3(): + print("f3 start") + await f2() + print("f3 end") + +js.setTimeout(f0, 0) +js.setTimeout(f1, 50) +js.setTimeout(f2, 100) +js.setTimeout(f3, 150) +`); diff --git a/tests/ports/webassembly/fun_py_callback_js.mjs.exp b/tests/ports/webassembly/fun_py_callback_js.mjs.exp new file mode 100644 index 0000000000..732062c840 --- /dev/null +++ b/tests/ports/webassembly/fun_py_callback_js.mjs.exp @@ -0,0 +1,8 @@ +f0 run +f1 run +f2 start +f2 end +f3 start +f2 start +f2 end +f3 end