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.
@@ -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 usign info from published GitHub releases (the first unreleased section is preserved)"
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