opal 1.2.0.beta1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -1
- data/CHANGELOG.md +53 -0
- data/UNRELEASED.md +5 -1
- data/lib/opal/parser/patch.rb +10 -0
- data/lib/opal/rewriter.rb +2 -0
- data/lib/opal/rewriters/pattern_matching.rb +287 -0
- data/lib/opal/version.rb +1 -1
- data/opal/corelib/constants.rb +2 -2
- data/opal/corelib/hash.rb +9 -0
- data/opal/corelib/pattern_matching.rb +159 -0
- data/opal/opal/full.rb +1 -0
- data/spec/filters/bugs/array.rb +0 -1
- data/spec/filters/bugs/hash.rb +1 -6
- data/spec/filters/bugs/language.rb +21 -76
- data/spec/filters/bugs/string.rb +1 -0
- data/spec/filters/bugs/struct.rb +0 -10
- data/spec/filters/unsupported/refinements.rb +3 -0
- data/spec/opal/core/language/pattern_matching_spec.rb +124 -0
- data/stdlib/promise/v1.rb +1 -0
- data/stdlib/promise/v2.rb +386 -0
- data/tasks/releasing.rake +1 -1
- data/tasks/testing.rake +0 -1
- data/test/opal/promisev2/test_always.rb +63 -0
- data/test/opal/promisev2/test_error.rb +16 -0
- data/test/opal/promisev2/test_rescue.rb +59 -0
- data/test/opal/promisev2/test_then.rb +90 -0
- data/test/opal/promisev2/test_trace.rb +52 -0
- data/test/opal/promisev2/test_value.rb +16 -0
- data/test/opal/promisev2/test_when.rb +35 -0
- metadata +24 -8
@@ -0,0 +1 @@
|
|
1
|
+
require 'promise'
|
@@ -0,0 +1,386 @@
|
|
1
|
+
# {Promise} is used to help structure asynchronous code.
|
2
|
+
#
|
3
|
+
# It is available in the Opal standard library, and can be required in any Opal
|
4
|
+
# application:
|
5
|
+
#
|
6
|
+
# require 'promise/v2'
|
7
|
+
#
|
8
|
+
# ## Basic Usage
|
9
|
+
#
|
10
|
+
# Promises are created and returned as objects with the assumption that they
|
11
|
+
# will eventually be resolved or rejected, but never both. A {Promise} has
|
12
|
+
# a {#then} and {#fail} method (or one of their aliases) that can be used to
|
13
|
+
# register a block that gets called once resolved or rejected.
|
14
|
+
#
|
15
|
+
# promise = PromiseV2.new
|
16
|
+
#
|
17
|
+
# promise.then {
|
18
|
+
# puts "resolved!"
|
19
|
+
# }.fail {
|
20
|
+
# puts "rejected!"
|
21
|
+
# }
|
22
|
+
#
|
23
|
+
# # some time later
|
24
|
+
# promise.resolve
|
25
|
+
#
|
26
|
+
# # => "resolved!"
|
27
|
+
#
|
28
|
+
# It is important to remember that a promise can only be resolved or rejected
|
29
|
+
# once, so the block will only ever be called once (or not at all).
|
30
|
+
#
|
31
|
+
# ## Resolving Promises
|
32
|
+
#
|
33
|
+
# To resolve a promise, means to inform the {Promise} that it has succeeded
|
34
|
+
# or evaluated to a useful value. {#resolve} can be passed a value which is
|
35
|
+
# then passed into the block handler:
|
36
|
+
#
|
37
|
+
# def get_json
|
38
|
+
# promise = PromiseV2.new
|
39
|
+
#
|
40
|
+
# HTTP.get("some_url") do |req|
|
41
|
+
# promise.resolve req.json
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# promise
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# get_json.then do |json|
|
48
|
+
# puts "got some JSON from server"
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# ## Rejecting Promises
|
52
|
+
#
|
53
|
+
# Promises are also designed to handle error cases, or situations where an
|
54
|
+
# outcome is not as expected. Taking the previous example, we can also pass
|
55
|
+
# a value to a {#reject} call, which passes that object to the registered
|
56
|
+
# {#fail} handler:
|
57
|
+
#
|
58
|
+
# def get_json
|
59
|
+
# promise = PromiseV2.new
|
60
|
+
#
|
61
|
+
# HTTP.get("some_url") do |req|
|
62
|
+
# if req.ok?
|
63
|
+
# promise.resolve req.json
|
64
|
+
# else
|
65
|
+
# promise.reject req
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# promise
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# get_json.then {
|
72
|
+
# # ...
|
73
|
+
# }.fail { |req|
|
74
|
+
# puts "it went wrong: #{req.message}"
|
75
|
+
# }
|
76
|
+
#
|
77
|
+
# ## Chaining Promises
|
78
|
+
#
|
79
|
+
# Promises become even more useful when chained together. Each {#then} or
|
80
|
+
# {#fail} call returns a new {PromiseV2} which can be used to chain more and more
|
81
|
+
# handlers together.
|
82
|
+
#
|
83
|
+
# promise.then { wait_for_something }.then { do_something_else }
|
84
|
+
#
|
85
|
+
# Rejections are propagated through the entire chain, so a "catch all" handler
|
86
|
+
# can be attached at the end of the tail:
|
87
|
+
#
|
88
|
+
# promise.then { ... }.then { ... }.fail { ... }
|
89
|
+
#
|
90
|
+
# ## Composing Promises
|
91
|
+
#
|
92
|
+
# {PromiseV2.when} can be used to wait for more than one promise to resolve (or
|
93
|
+
# reject). Using the previous example, we could request two different json
|
94
|
+
# requests and wait for both to finish:
|
95
|
+
#
|
96
|
+
# PromiseV2.when(get_json, get_json2).then |first, second|
|
97
|
+
# puts "got two json payloads: #{first}, #{second}"
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
|
101
|
+
warn 'PromiseV2 is a technology preview, which means it may change its behavior ' \
|
102
|
+
'in the future until this warning is removed. If you are interested in this part, ' \
|
103
|
+
'please make sure you track the async/await/promises tag on Opal issues: ' \
|
104
|
+
'https://github.com/opal/opal/issues?q=label%3Aasync%2Fawait%2Fpromises'
|
105
|
+
|
106
|
+
class PromiseV2 < `Promise`
|
107
|
+
class << self
|
108
|
+
def allocate
|
109
|
+
ok, fail = nil, nil
|
110
|
+
|
111
|
+
prom = `new self.$$constructor(function(_ok, _fail) { #{ok} = _ok; #{fail} = _fail; })`
|
112
|
+
prom.instance_variable_set(:@type, :opal)
|
113
|
+
prom.instance_variable_set(:@resolve_proc, ok)
|
114
|
+
prom.instance_variable_set(:@reject_proc, fail)
|
115
|
+
prom
|
116
|
+
end
|
117
|
+
|
118
|
+
def when(*promises)
|
119
|
+
promises = Array(promises.length == 1 ? promises.first : promises)
|
120
|
+
`Promise.all(#{promises})`.tap do |prom|
|
121
|
+
prom.instance_variable_set(:@type, :when)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
alias all when
|
126
|
+
|
127
|
+
def all_resolved(*promises)
|
128
|
+
promises = Array(promises.length == 1 ? promises.first : promises)
|
129
|
+
`Promise.allResolved(#{promises})`.tap do |prom|
|
130
|
+
prom.instance_variable_set(:@type, :all_resolved)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def any(*promises)
|
135
|
+
promises = Array(promises.length == 1 ? promises.first : promises)
|
136
|
+
`Promise.any(#{promises})`.tap do |prom|
|
137
|
+
prom.instance_variable_set(:@type, :any)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def race(*promises)
|
142
|
+
promises = Array(promises.length == 1 ? promises.first : promises)
|
143
|
+
`Promise.race(#{promises})`.tap do |prom|
|
144
|
+
prom.instance_variable_set(:@type, :race)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def resolve(value = nil)
|
149
|
+
`Promise.resolve(#{value})`.tap do |prom|
|
150
|
+
prom.instance_variable_set(:@type, :resolve)
|
151
|
+
prom.instance_variable_set(:@realized, :resolve)
|
152
|
+
prom.instance_variable_set(:@value, value)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
alias value resolve
|
156
|
+
|
157
|
+
def reject(value = nil)
|
158
|
+
`Promise.reject(#{value})`.tap do |prom|
|
159
|
+
prom.instance_variable_set(:@type, :reject)
|
160
|
+
prom.instance_variable_set(:@realized, :reject)
|
161
|
+
prom.instance_variable_set(:@value, value)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
alias error reject
|
165
|
+
end
|
166
|
+
|
167
|
+
attr_reader :prev, :next
|
168
|
+
|
169
|
+
# Is this promise native to JavaScript? This means, that methods like resolve
|
170
|
+
# or reject won't be available.
|
171
|
+
def native?
|
172
|
+
@type != :opal
|
173
|
+
end
|
174
|
+
|
175
|
+
# Raise an exception when a non-JS-native method is called on a JS-native promise
|
176
|
+
def nativity_check!
|
177
|
+
raise ArgumentError, 'this promise is native to JavaScript' if native?
|
178
|
+
end
|
179
|
+
|
180
|
+
# Raise an exception when a non-JS-native method is called on a JS-native promise
|
181
|
+
# but permits some typed promises
|
182
|
+
def light_nativity_check!
|
183
|
+
return if %i[reject resolve trace always fail then].include? @type
|
184
|
+
raise ArgumentError, 'this promise is native to JavaScript' if native?
|
185
|
+
end
|
186
|
+
|
187
|
+
# Allow only one chain to be present, as needed by the previous implementation.
|
188
|
+
# This isn't a strict check - it's always possible on the JS side to chain a
|
189
|
+
# given block.
|
190
|
+
def there_can_be_only_one!
|
191
|
+
raise ArgumentError, 'a promise has already been chained' if @next && @next.any?
|
192
|
+
end
|
193
|
+
|
194
|
+
def gen_tracing_proc(passing, &block)
|
195
|
+
proc do |i|
|
196
|
+
res = passing.call(i)
|
197
|
+
yield(res)
|
198
|
+
res
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def resolve(value = nil)
|
203
|
+
nativity_check!
|
204
|
+
raise ArgumentError, 'this promise was already resolved' if @realized
|
205
|
+
@value = value
|
206
|
+
@realized = :resolve
|
207
|
+
@resolve_proc.call(value)
|
208
|
+
self
|
209
|
+
end
|
210
|
+
alias resolve! resolve
|
211
|
+
|
212
|
+
def reject(value = nil)
|
213
|
+
nativity_check!
|
214
|
+
raise ArgumentError, 'this promise was already resolved' if @realized
|
215
|
+
@value = value
|
216
|
+
@realized = :reject
|
217
|
+
@reject_proc.call(value)
|
218
|
+
self
|
219
|
+
end
|
220
|
+
alias reject! reject
|
221
|
+
|
222
|
+
def then(&block)
|
223
|
+
prom = nil
|
224
|
+
blk = gen_tracing_proc(block) do |val|
|
225
|
+
prom.instance_variable_set(:@realized, :resolve)
|
226
|
+
prom.instance_variable_set(:@value, val)
|
227
|
+
end
|
228
|
+
prom = `self.then(#{blk})`
|
229
|
+
prom.instance_variable_set(:@prev, self)
|
230
|
+
prom.instance_variable_set(:@type, :then)
|
231
|
+
(@next ||= []) << prom
|
232
|
+
prom
|
233
|
+
end
|
234
|
+
|
235
|
+
def then!(&block)
|
236
|
+
there_can_be_only_one!
|
237
|
+
self.then(&block)
|
238
|
+
end
|
239
|
+
|
240
|
+
alias do then
|
241
|
+
alias do! then!
|
242
|
+
|
243
|
+
def fail(&block)
|
244
|
+
prom = nil
|
245
|
+
blk = gen_tracing_proc(block) do |val|
|
246
|
+
prom.instance_variable_set(:@realized, :resolve)
|
247
|
+
prom.instance_variable_set(:@value, val)
|
248
|
+
end
|
249
|
+
prom = `self.catch(#{blk})`
|
250
|
+
prom.instance_variable_set(:@prev, self)
|
251
|
+
prom.instance_variable_set(:@type, :fail)
|
252
|
+
(@next ||= []) << prom
|
253
|
+
prom
|
254
|
+
end
|
255
|
+
|
256
|
+
def fail!(&block)
|
257
|
+
there_can_be_only_one!
|
258
|
+
fail(&block)
|
259
|
+
end
|
260
|
+
|
261
|
+
alias rescue fail
|
262
|
+
alias catch fail
|
263
|
+
alias rescue! fail!
|
264
|
+
alias catch! fail!
|
265
|
+
|
266
|
+
def always(&block)
|
267
|
+
prom = nil
|
268
|
+
blk = gen_tracing_proc(block) do |val|
|
269
|
+
prom.instance_variable_set(:@realized, :resolve)
|
270
|
+
prom.instance_variable_set(:@value, val)
|
271
|
+
end
|
272
|
+
prom = `self.finally(#{blk})`
|
273
|
+
prom.instance_variable_set(:@prev, self)
|
274
|
+
prom.instance_variable_set(:@type, :always)
|
275
|
+
(@next ||= []) << prom
|
276
|
+
prom
|
277
|
+
end
|
278
|
+
|
279
|
+
def always!(&block)
|
280
|
+
there_can_be_only_one!
|
281
|
+
always(&block)
|
282
|
+
end
|
283
|
+
|
284
|
+
alias finally always
|
285
|
+
alias ensure always
|
286
|
+
alias finally! always!
|
287
|
+
alias ensure! always!
|
288
|
+
|
289
|
+
def trace(depth = nil, &block)
|
290
|
+
prom = self.then do
|
291
|
+
values = []
|
292
|
+
prom = self
|
293
|
+
while prom && (!depth || depth > 0)
|
294
|
+
val = nil
|
295
|
+
begin
|
296
|
+
val = prom.value
|
297
|
+
rescue ArgumentError
|
298
|
+
val = :native
|
299
|
+
end
|
300
|
+
values.unshift(val)
|
301
|
+
depth -= 1 if depth
|
302
|
+
prom = prom.prev
|
303
|
+
end
|
304
|
+
yield(*values)
|
305
|
+
end
|
306
|
+
|
307
|
+
prom.instance_variable_set(:@type, :trace)
|
308
|
+
prom
|
309
|
+
end
|
310
|
+
|
311
|
+
def trace!(*args, &block)
|
312
|
+
there_can_be_only_one!
|
313
|
+
trace(*args, &block)
|
314
|
+
end
|
315
|
+
|
316
|
+
def resolved?
|
317
|
+
light_nativity_check!
|
318
|
+
@realized == :resolve
|
319
|
+
end
|
320
|
+
|
321
|
+
def rejected?
|
322
|
+
light_nativity_check!
|
323
|
+
@realized == :reject
|
324
|
+
end
|
325
|
+
|
326
|
+
def realized?
|
327
|
+
light_nativity_check!
|
328
|
+
!@realized.nil?
|
329
|
+
end
|
330
|
+
|
331
|
+
def value
|
332
|
+
if resolved?
|
333
|
+
if PromiseV2 === @value
|
334
|
+
@value.value
|
335
|
+
else
|
336
|
+
@value
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def error
|
342
|
+
light_nativity_check!
|
343
|
+
@value if rejected?
|
344
|
+
end
|
345
|
+
|
346
|
+
def and(*promises)
|
347
|
+
promises = promises.map do |i|
|
348
|
+
if PromiseV2 === i
|
349
|
+
i
|
350
|
+
else
|
351
|
+
PromiseV2.value(i)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
PromiseV2.when(self, *promises).then do |a, *b|
|
355
|
+
[*a, *b]
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def initialize(&block)
|
360
|
+
yield self if block_given?
|
361
|
+
end
|
362
|
+
|
363
|
+
alias to_n itself
|
364
|
+
|
365
|
+
def inspect
|
366
|
+
result = "#<#{self.class}"
|
367
|
+
|
368
|
+
if @type
|
369
|
+
result += ":#{@type}" unless %i[opal resolve reject].include? @type
|
370
|
+
else
|
371
|
+
result += ':native'
|
372
|
+
end
|
373
|
+
|
374
|
+
result += ":#{@realized}" if @realized
|
375
|
+
result += "(#{object_id})"
|
376
|
+
|
377
|
+
if @next && @next.any?
|
378
|
+
result += " >> #{@next.inspect}"
|
379
|
+
end
|
380
|
+
|
381
|
+
result += ": #{@value.inspect}" if @value
|
382
|
+
result += '>'
|
383
|
+
|
384
|
+
result
|
385
|
+
end
|
386
|
+
end
|
data/tasks/releasing.rake
CHANGED
@@ -49,7 +49,7 @@ CHANGELOG_HEADING = <<~MARKDOWN
|
|
49
49
|
- **Security** to invite users to upgrade in case of vulnerabilities.
|
50
50
|
MARKDOWN
|
51
51
|
|
52
|
-
desc "Update CHANGELOG.md
|
52
|
+
desc "Update CHANGELOG.md using info from published GitHub releases (the first unreleased section is preserved)"
|
53
53
|
task :changelog do
|
54
54
|
changelog_path = "#{__dir__}/../CHANGELOG.md"
|
55
55
|
unreleased_path = "#{__dir__}/../UNRELEASED.md"
|
data/tasks/testing.rake
CHANGED
@@ -467,4 +467,3 @@ task :test_all => [:rspec, :mspec, :minitest]
|
|
467
467
|
task(:cruby_tests) { warn "The task 'cruby_tests' has been renamed to 'minitest_cruby_nodejs'."; exit 1 }
|
468
468
|
task(:test_cruby) { warn "The task 'test_cruby' has been renamed to 'minitest_cruby_nodejs'."; exit 1 }
|
469
469
|
task(:test_nodejs) { warn "The task 'test_nodejs' has been renamed to 'minitest_node_nodejs'."; exit 1 }
|
470
|
-
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'promise/v2'
|
3
|
+
|
4
|
+
class TestPromiseAlways < Test::Unit::TestCase
|
5
|
+
def test_calls_the_block_when_it_was_resolved
|
6
|
+
x = 42
|
7
|
+
PromiseV2.value(23)
|
8
|
+
.then { |v| x = v }
|
9
|
+
.always { |v| x = 2 }
|
10
|
+
.then { assert_equal(x, 2) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_calls_the_block_when_it_was_rejected
|
14
|
+
x = 42
|
15
|
+
PromiseV2.error(23)
|
16
|
+
.rescue { |v| x = v }
|
17
|
+
.always { |v| x = 2 }
|
18
|
+
.always { assert_equal(x, 2) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_acts_as_resolved
|
22
|
+
x = 42
|
23
|
+
PromiseV2.error(23)
|
24
|
+
.rescue { |v| x = v }
|
25
|
+
.always { x = 2 }
|
26
|
+
.then { x = 3 }
|
27
|
+
.always { assert_equal(x, 3) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_can_be_called_multiple_times_on_resolved_promises
|
31
|
+
p = PromiseV2.value(2)
|
32
|
+
x = 1
|
33
|
+
ps = []
|
34
|
+
ps << p.then { x += 1 }
|
35
|
+
ps << p.fail { x += 2 }
|
36
|
+
ps << p.always { x += 3 }
|
37
|
+
|
38
|
+
PromiseV2.when(ps).always do
|
39
|
+
assert_equal(x, 5)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_can_be_called_multiple_times_on_rejected_promises
|
44
|
+
p = PromiseV2.error(2)
|
45
|
+
x = 1
|
46
|
+
ps = []
|
47
|
+
ps << p.then { x += 1 }.fail{}
|
48
|
+
ps << p.fail { x += 2 }
|
49
|
+
ps << p.always { x += 3 }.fail{}
|
50
|
+
|
51
|
+
PromiseV2.when(ps).then do
|
52
|
+
assert_equal(x, 6)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_raises_with_alwaysB_if_a_promise_has_already_been_chained
|
57
|
+
p = PromiseV2.new
|
58
|
+
|
59
|
+
p.then! {}
|
60
|
+
|
61
|
+
assert_raise(ArgumentError) { p.always! {} }
|
62
|
+
end
|
63
|
+
end
|