Skip to content

Commit 5677059

Browse files
Initial PromiseScheduler implementation
1 parent 0b441f7 commit 5677059

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

ext/js/lib/js.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,57 @@
1515
module JS
1616
Undefined = JS.eval("return undefined")
1717
Null = JS.eval("return null")
18+
19+
def self.promise_scheduler
20+
@promise_scheduler
21+
end
22+
23+
def self.eval_async(code, future)
24+
@promise_scheduler ||= PromiseScheduler.new Fiber.current
25+
Fiber.new do
26+
future.resolve JS::Object.wrap(Kernel.eval(code.to_s, nil, "eval_async"))
27+
rescue => e
28+
future.reject JS::Object.wrap(e)
29+
end.transfer
30+
end
31+
32+
class PromiseScheduler
33+
Task = Struct.new(:fiber, :status, :value)
34+
35+
def initialize(main_fiber)
36+
@tasks = []
37+
@is_spinning = false
38+
@loop_fiber = Fiber.new do
39+
loop do
40+
while task = @tasks.shift
41+
task.fiber.transfer(task.value, task.status)
42+
end
43+
@is_spinning = false
44+
main_fiber.transfer
45+
end
46+
end
47+
end
48+
49+
def await(promise)
50+
current = Fiber.current
51+
promise.call(
52+
:then,
53+
->(value) { enqueue Task.new(current, :success, value) },
54+
->(value) { enqueue Task.new(current, :failure, value) }
55+
)
56+
value, status = @loop_fiber.transfer
57+
raise JS::Error.new(value) if status == :failure
58+
value
59+
end
60+
61+
def enqueue(task)
62+
@tasks << task
63+
unless @is_spinning
64+
@is_spinning = true
65+
JS.global.queueMicrotask -> { @loop_fiber.transfer }
66+
end
67+
end
68+
end
1869
end
1970

2071
class JS::Object
@@ -30,6 +81,14 @@ def respond_to_missing?(sym, include_private)
3081
return true if super
3182
self[sym].typeof == "function"
3283
end
84+
85+
def await
86+
sched = JS.promise_scheduler
87+
unless sched
88+
raise "Please start Ruby evaluation with RubyVM.eval_async to use JS::Object#await"
89+
end
90+
sched.await(self)
91+
end
3392
end
3493

3594
# A wrapper class for JavaScript Error to allow the Error to be thrown in Ruby.

packages/npm-packages/ruby-wasm-wasi/src/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,30 @@ export class RubyVM {
225225
return evalRbCode(this, this.privateObject(), code);
226226
}
227227

228+
evalAsync(code: string): Promise<RbValue> {
229+
const JS = this.eval("require 'js'; JS");
230+
return new Promise((resolve, reject) => {
231+
JS.call(
232+
"eval_async",
233+
this.wrap(code),
234+
this.wrap({
235+
resolve,
236+
reject: (error: RbValue) => {
237+
reject(
238+
new RbError(
239+
this.exceptionFormatter.format(
240+
error,
241+
this,
242+
this.privateObject()
243+
)
244+
)
245+
);
246+
},
247+
})
248+
);
249+
});
250+
}
251+
228252
/**
229253
* Wrap a JavaScript value into a Ruby JS::Object
230254
* @param value The value to convert to RbValue

0 commit comments

Comments
 (0)