15
15
module JS
16
16
Undefined = JS . eval ( "return undefined" )
17
17
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
18
69
end
19
70
20
71
class JS ::Object
@@ -30,6 +81,14 @@ def respond_to_missing?(sym, include_private)
30
81
return true if super
31
82
self [ sym ] . typeof == "function"
32
83
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
33
92
end
34
93
35
94
# A wrapper class for JavaScript Error to allow the Error to be thrown in Ruby.
0 commit comments