1515module 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
1869end
1970
2071class 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
3392end
3493
3594# A wrapper class for JavaScript Error to allow the Error to be thrown in Ruby.
0 commit comments