@ledsun blog

無味の味は佳境に入らざればすなわち知れず

JS::Object.await ふたたび

JS::Object.await - @ledsun blog に対してフォローをもらいました。

なんかちょっと上手く使えていないようです。 https://github.com/ruby/ruby.wasm/blob/2ef290bca9df55a9c7c64e9c03b8d4dec54f7b44/ext/js/lib/js.rb#L97-L121 のコメントを参考に、小さいコードで試してみます。

JavaScript

まずはJavaScriptにて

<html>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.script.iife.js"></script>
<script>
  function slowFunction() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(123)
      }, 1000)
    })
  }

  async function main() {
    const response = window.rubyVM.evalAsync(`
      puts "step 1"
      JS.global.slowFunction().await
      puts "step 3"
    `)
    console.log("step 2")
    await response
    console.log("step 4")
  }

  const id = setInterval(() => {
    if (window.rubyVM) {
      clearInterval(id)
      main()
    }
  }, 300)
</script>

</html>

実行するとちゃんと、1, 2, 3, 4の順で動きます。 とくに4が最後に動くのが印象的です。 await responseをすると、Promiseを待っているRubyスクリプトの完了を待てるようです。

Ruby

<html>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.script.iife.js"></script>
<script type="text/ruby">
  require 'js'

  async_func = <<-"JS"
  return new Promise((ok) => {
    setTimeout(() => {
      console.log('step 3')
      ok(42)
    }, 1)
  })
  JS

  puts 'step 1'
  promise = JS.eval(async_func)
  puts 'step 2'
  promise.await
  puts 'step 4'

</script>

</html>

これは 1, 2, 4, 3の順で動きます。 Promiseの終了を待っていません。 現在の<script type="text/ruby">は自動的にrubyVM.evalRubyスクリプトを実行します。 もしかしてRubyスクリプトrubyVM.evalAsyncで実行しないといけないのでしょうか?

<html>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/browser.script.iife.js"></script>
<script>
  function slowFunction() {
    return new Promise((ok) => {
      setTimeout(() => {
        console.log('step 3')
        ok(42)
      }, 1)
    })
  }

  async function main() {
    const response = window.rubyVM.evalAsync(`
      puts "step 1"
      puts 'step 2'
      JS.global.slowFunction().await
      puts "step 4"
    `)
  }

  const id = setInterval(() => {
    if (window.rubyVM) {
      clearInterval(id)
      main()
    }
  }, 300)
</script>

</html>

これは 1, 2, 3, 4の順で動きます。 なるほどー。 そらそうですね。 最初の呼び出しを非同期関数にしなかったら、非同期関数を待てるわけないですね。 PromiseやAsync/Awaitはコールバックヘルを縦に並べ直したものです。 最初の呼び出しでコールバック関数が渡せなければ、待ちようがありません。

なるほど、そういうつもりで書き直さないといけないのですね。