ruby/ruby.wasm

`JS::Object#await` is unavailable inside Proc converted into JS closure

Opened this issue · 0 comments

Reproducer

<html>
  <script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.6.0/dist/browser.script.iife.js"></script>
  <script type="text/ruby" data-eval="async">
  require "js"

  JS.global.setTimeout(-> {
    JS.global[:Promise].resolve.await
  }, 0)
  </script>
</html>
browser.script.iife.js:2731 Uncaught Error: /bundle/gems/js-2.6.0/lib/js.rb:86:in `await': JS::Object#await can be called only from RubyVM#evalAsync or RbValue#callAsync JS API
If you are using browser.script.iife.js, please ensure that you specify `data-eval="async"` in your script tag
e.g. <script type="text/ruby" data-eval="async">puts :hello</script>
Or <script type="text/ruby" data-eval="async" src="path/to/script.rb"></script> (RuntimeError)
/bundle/gems/js-2.6.0/lib/js.rb:240:in `await'
eval_async:9:in `block in <main>'
    at checkStatusTag (browser.script.iife.js:2731:21)
    at browser.script.iife.js:2766:11
    at wrapRbOperation (browser.script.iife.js:2738:18)
    at callRbMethod (browser.script.iife.js:2764:14)
    at RbValue.call (browser.script.iife.js:2547:30)
    at browser.script.iife.js:2322:38

Workaround

Wrap the block with Fiber.new do ... end.transfer.

JS.global.setTimeout(-> {
  Fiber.new do
    JS.global[:Promise].resolve.await
  end.transfer
}, 0)

Solutions

We have several options to solve the issue

  1. Always enter the body of Proc in Ruby with callAsync instead of call
  2. Add conversion method to create an async version of JS function from proc.
  • e.g.
JS.global.setTimeout(JS.async do
  JS.global[:Promise].resolve.await
end, 0)