opal 1.2.0.beta1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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