ruby.wasmのKernel#sleepをどう実装したものか? - @ledsun blog の続きです。
次のようにruby.wasmで動くKernel#sleep
を実装できます。
<html> <head> <title>Kernel#sleep</title> <script src="https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.4.1-2024-01-26-a/dist/browser.script.iife.js"></script> <script type="text/ruby" data-eval="async"> require 'js' module Kernel def sleep(time) JS.eval("return new Promise((resolve) => setTimeout(resolve, time * 1000))").await end end start = JS.global[:Date].now.to_i sleep(1) puts "#{JS.global[:Date].now.to_i - start}ms passed" </script> </head> </html>
Promiseの初期化を次のように書いています。
JS.eval("return new Promise((resolve) => setTimeout(resolve, time * 1000))").await
次のようにnew
メソッドにコールバック関数をブロックで渡したいです。
JS.global[:Promise].new do |resolve| JS.global.setTimeout(resolve, time * 1000) end.await
これは次のエラーがでます。
TypeError: Promise resolver undefined is not a function
しかしnew
メソッドの定義はブロックを考慮していません。
def new(*args) JS.global[:Reflect].construct(self, args.to_js) end
つまり次のJavaScriptを実行しているのと同じです。
new Promise(undefined)
ですので、resolver
引数がundefined
であると怒られます。
では、次のようにしてはどうでしょうか?
class JS::Object def new(*args, &block) JS.global[:Reflect].construct(self, block) end end module Kernel def sleep(time) JS.global[:Promise].new do |resolve| JS.global.setTimeout(resolve, time * 1000) end.await end end
new
メソッドでブロックを引数として渡します。
上手く行きそうですが、やはり同様のエラーが起きます。
TypeError: Promise resolver undefined is not a function
これはJavaScriptでかくと、次のようになります。
Reflect.construct(Promise, ()=>{})
Reflect.constructの第二引数には、配列風オブジェクトを渡す必要があります。 次のようにすると良さそうです。
def new(*args, &block) JS.global[:Reflect].construct(self, [block]) end
ひとまず動きます。
つぎのようにすると、new
メソッドにブロックを渡したときも、引数を渡したときもJavaScriptのコンストラクターを呼び出せるようになります。
<html> <head> <title>Kernel#sleep</title> <script src="https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.4.1-2024-01-26-a/dist/browser.script.iife.js"></script> <script type="text/ruby" data-eval="async"> require 'js' class JS::Object def new(*args, &block) if block JS.global[:Reflect].construct(self, [block]) else JS.global[:Reflect].construct(self, args) end end end module Kernel def sleep(time) JS.global[:Promise].new do |resolve| JS.global.setTimeout(resolve, time * 1000) end.await end end start = JS.global[:Date].now.to_i sleep(1) puts "#{JS.global[:Date].now.to_i - start}ms passed" p JS.global[:Date].new("2024-01-27T00:00:00Z") </script> </head> </html>