opal-browser 0.1.0.beta1 → 0.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +60 -0
  3. data/.yardopts +1 -1
  4. data/Gemfile +7 -2
  5. data/LICENSE +19 -0
  6. data/README.md +74 -10
  7. data/config.ru +2 -1
  8. data/index.html.erb +22 -0
  9. data/opal-browser.gemspec +9 -11
  10. data/opal/browser.rb +1 -1
  11. data/opal/browser/animation_frame.rb +66 -9
  12. data/opal/browser/canvas.rb +72 -18
  13. data/opal/browser/canvas/data.rb +1 -1
  14. data/opal/browser/console.rb +3 -37
  15. data/opal/browser/cookies.rb +80 -24
  16. data/opal/browser/css/declaration.rb +0 -5
  17. data/opal/browser/{timeout.rb → delay.rb} +13 -13
  18. data/opal/browser/dom.rb +0 -2
  19. data/opal/browser/dom/attribute.rb +6 -0
  20. data/opal/browser/dom/builder.rb +4 -8
  21. data/opal/browser/dom/character_data.rb +43 -7
  22. data/opal/browser/dom/document.rb +13 -11
  23. data/opal/browser/dom/element.rb +127 -29
  24. data/opal/browser/dom/element/image.rb +23 -0
  25. data/opal/browser/dom/element/offset.rb +27 -10
  26. data/opal/browser/dom/element/scroll.rb +32 -12
  27. data/opal/browser/dom/element/size.rb +29 -0
  28. data/opal/browser/dom/event.rb +88 -75
  29. data/opal/browser/dom/event/animation.rb +16 -4
  30. data/opal/browser/dom/event/audio_processing.rb +6 -4
  31. data/opal/browser/dom/event/base.rb +229 -64
  32. data/opal/browser/dom/event/before_unload.rb +6 -4
  33. data/opal/browser/dom/event/clipboard.rb +6 -4
  34. data/opal/browser/dom/event/close.rb +16 -4
  35. data/opal/browser/dom/event/composition.rb +16 -4
  36. data/opal/browser/dom/event/custom.rb +43 -8
  37. data/opal/browser/dom/event/device_light.rb +6 -4
  38. data/opal/browser/dom/event/device_motion.rb +17 -4
  39. data/opal/browser/dom/event/device_orientation.rb +16 -4
  40. data/opal/browser/dom/event/device_proximity.rb +6 -4
  41. data/opal/browser/dom/event/drag.rb +34 -28
  42. data/opal/browser/dom/event/focus.rb +21 -5
  43. data/opal/browser/dom/event/gamepad.rb +33 -20
  44. data/opal/browser/dom/event/hash_change.rb +6 -4
  45. data/opal/browser/dom/event/keyboard.rb +45 -23
  46. data/opal/browser/dom/event/message.rb +28 -8
  47. data/opal/browser/dom/event/mouse.rb +26 -25
  48. data/opal/browser/dom/event/page_transition.rb +6 -4
  49. data/opal/browser/dom/event/pop_state.rb +16 -4
  50. data/opal/browser/dom/event/progress.rb +16 -4
  51. data/opal/browser/dom/event/sensor.rb +6 -4
  52. data/opal/browser/dom/event/storage.rb +6 -4
  53. data/opal/browser/dom/event/touch.rb +10 -19
  54. data/opal/browser/dom/event/ui.rb +19 -3
  55. data/opal/browser/dom/event/wheel.rb +2 -2
  56. data/opal/browser/dom/mutation_observer.rb +65 -5
  57. data/opal/browser/dom/node.rb +164 -59
  58. data/opal/browser/dom/node_set.rb +4 -0
  59. data/opal/browser/dom/text.rb +16 -1
  60. data/opal/browser/event_source.rb +5 -2
  61. data/opal/browser/history.rb +51 -15
  62. data/opal/browser/http.rb +22 -7
  63. data/opal/browser/http/headers.rb +5 -0
  64. data/opal/browser/http/request.rb +40 -10
  65. data/opal/browser/immediate.rb +123 -9
  66. data/opal/browser/interval.rb +8 -13
  67. data/opal/browser/location.rb +13 -3
  68. data/opal/browser/navigator.rb +9 -6
  69. data/opal/browser/screen.rb +31 -5
  70. data/opal/browser/socket.rb +8 -4
  71. data/opal/browser/storage.rb +118 -33
  72. data/opal/browser/support.rb +232 -0
  73. data/opal/browser/utils.rb +24 -6
  74. data/opal/browser/version.rb +1 -1
  75. data/opal/browser/window.rb +1 -2
  76. data/opal/browser/window/scroll.rb +21 -11
  77. data/opal/browser/window/size.rb +16 -6
  78. data/opal/browser/window/view.rb +23 -5
  79. data/spec/dom/builder_spec.rb +19 -19
  80. data/spec/dom/document_spec.rb +6 -6
  81. data/spec/dom/element_spec.rb +5 -5
  82. data/spec/dom/event_spec.rb +20 -20
  83. data/spec/dom/mutation_observer_spec.rb +5 -5
  84. data/spec/dom/node_spec.rb +39 -27
  85. data/spec/dom_spec.rb +10 -8
  86. data/spec/event_source_spec.rb +12 -12
  87. data/spec/history_spec.rb +24 -15
  88. data/spec/http_spec.rb +18 -17
  89. data/spec/immediate_spec.rb +9 -7
  90. data/spec/runner.rb +114 -0
  91. data/spec/socket_spec.rb +8 -8
  92. data/spec/spec_helper.rb +1 -0
  93. data/spec/storage_spec.rb +6 -6
  94. data/spec/wgxpath.install.js +49 -0
  95. data/spec/window_spec.rb +2 -2
  96. metadata +21 -54
  97. data/opal/browser/compatibility.rb +0 -59
  98. data/opal/browser/compatibility/animation_frame.rb +0 -93
  99. data/opal/browser/compatibility/dom/document/window.rb +0 -15
  100. data/opal/browser/compatibility/dom/element/css.rb +0 -15
  101. data/opal/browser/compatibility/dom/element/matches.rb +0 -31
  102. data/opal/browser/compatibility/dom/element/offset.rb +0 -20
  103. data/opal/browser/compatibility/dom/element/scroll.rb +0 -25
  104. data/opal/browser/compatibility/dom/element/style.rb +0 -15
  105. data/opal/browser/compatibility/dom/mutation_observer.rb +0 -47
  106. data/opal/browser/compatibility/http/request.rb +0 -15
  107. data/opal/browser/compatibility/immediate.rb +0 -107
  108. data/opal/browser/compatibility/window/scroll.rb +0 -27
  109. data/opal/browser/compatibility/window/size.rb +0 -13
  110. data/opal/browser/compatibility/window/view.rb +0 -13
  111. data/opal/browser/dom/compatibility.rb +0 -8
  112. data/opal/browser/http/compatibility.rb +0 -1
  113. data/opal/browser/window/compatibility.rb +0 -3
@@ -16,6 +16,10 @@ class NodeSet
16
16
  }
17
17
  end
18
18
 
19
+ def respond_to_missing?(name)
20
+ @literal.respond_to?(name)
21
+ end
22
+
19
23
  def method_missing(name, *args, &block)
20
24
  unless @literal.respond_to? name
21
25
  each {|el|
@@ -1,16 +1,31 @@
1
1
  module Browser; module DOM
2
2
 
3
+ # Encapsulates a text node.
4
+ #
5
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/Text
3
6
  class Text < CharacterData
7
+ # (see Document#create_text)
4
8
  def self.create(*args)
5
9
  $document.create_text(*args)
6
10
  end
7
11
 
12
+ # @!attribute [r] whole
13
+ # @return [String] the whole text
14
+ #
15
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/Text.wholeText
8
16
  def whole
9
17
  `#@native.wholeText`
10
18
  end
11
19
 
20
+ # Split the text node at a given offset.
21
+ #
22
+ # @param offset [Integer] the offset where to split the text node
23
+ #
24
+ # @return [Text] the newly created text node
25
+ #
26
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/Text.splitText
12
27
  def split(offset)
13
- `#@native.splitText(offset)`
28
+ DOM(`#@native.splitText(offset)`)
14
29
  end
15
30
 
16
31
  def inspect
@@ -1,9 +1,12 @@
1
1
  module Browser
2
2
 
3
- # This class wraps `EventSource`.
3
+ # An {EventSource} allows you to receive events from a server in real-time,
4
+ # similar to long-polling but not exactly.
5
+ #
6
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/EventSource
4
7
  class EventSource
5
8
  def self.supported?
6
- defined? `window.EventSource`
9
+ Browser.supports? :EventSource
7
10
  end
8
11
 
9
12
  include Native
@@ -2,50 +2,86 @@ require 'browser/location'
2
2
 
3
3
  module Browser
4
4
 
5
+ # {History} allows manipulation of the session history.
6
+ #
7
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/History
5
8
  class History
9
+ # Check if HTML5 history is supported.
10
+ def self.supported?
11
+ Browser.supports? 'History'
12
+ end
13
+
6
14
  include Native
7
15
 
16
+ # @!attribute [r] length
17
+ # @return [Integer] how many items are in the history
8
18
  alias_native :length
9
19
 
20
+ # Go back in the history.
21
+ #
22
+ # @param number [Integer] how many items to go back
10
23
  def back(number = 1)
11
24
  `#@native.go(-number)`
12
-
13
- self
14
25
  end
15
26
 
27
+ # Go forward in the history.
28
+ #
29
+ # @param number [Integer] how many items to go forward
16
30
  def forward(number = 1)
17
31
  `#@native.go(number)`
18
-
19
- self
20
32
  end
21
33
 
22
- def push(url, data = nil)
34
+ # Push an item in the history.
35
+ #
36
+ # @param item [String] the item to push in the history
37
+ # @param data [Object] additional state to push
38
+ def push(item, data = nil)
23
39
  data = `null` if data.nil?
24
40
 
25
- `#@native.pushState(data, null, url)`
26
-
27
- self
41
+ `#@native.pushState(data, null, item)`
28
42
  end
29
43
 
30
- def replace(url, data = nil)
44
+ # Replace the current history item with another.
45
+ #
46
+ # @param item [String] the item to replace with
47
+ # @param data [Object] additional state to replace
48
+ def replace(item, data = nil)
31
49
  data = `null` if data.nil?
32
50
 
33
- `#@native.replaceState(data, null, url)`
51
+ `#@native.replaceState(data, null, item)`
34
52
  end
35
53
 
54
+ # @!attribute [r] current
55
+ # @return [String] the current item
36
56
  def current
37
57
  $window.location.path
38
58
  end
39
59
 
40
- def state
41
- `#@native.state`
60
+ # @!attribute [r] state
61
+ # @return [Object] the current state
62
+ if Browser.supports? 'History.state'
63
+ def state
64
+ %x{
65
+ var state = #@native.state;
66
+
67
+ if (state == null) {
68
+ return nil;
69
+ }
70
+ else {
71
+ return state;
72
+ }
73
+ }
74
+ end
75
+ else
76
+ def state
77
+ raise NotImplementedError, 'history state unsupported'
78
+ end
42
79
  end
43
80
  end
44
81
 
45
82
  class Window
46
- # Get the {History} object for this window.
47
- #
48
- # @return [History]
83
+ # @!attribute [r] history
84
+ # @return [History] the history for this window
49
85
  def history
50
86
  History.new(`#@native.history`) if `#@native.history`
51
87
  end
@@ -4,17 +4,21 @@ require 'browser/http/binary'
4
4
  require 'browser/http/headers'
5
5
  require 'browser/http/request'
6
6
  require 'browser/http/response'
7
- require 'browser/http/compatibility'
8
7
 
9
8
  module Browser
10
9
 
11
10
  module HTTP
11
+ def self.supported?
12
+ Browser.supports?('XHR') || Browser.supports?('ActiveXObject')
13
+ end
14
+
12
15
  # Send an asynchronous request.
13
16
  #
14
17
  # @param method [Symbol] the HTTP method to use
15
18
  # @param url [String] the URL to request
16
19
  # @param data [String, Hash] the data to send
17
- # @return [Response] the response
20
+ #
21
+ # @return [Promise] a promise that will be resolved with the response
18
22
  def self.send(method, url, data = nil, &block)
19
23
  Promise.new.tap {|promise|
20
24
  Request.new(&block).tap {|req|
@@ -32,7 +36,8 @@ module HTTP
32
36
  # Send an asynchronous GET request.
33
37
  #
34
38
  # @param url [String] the URL to request
35
- # @return [Response] the response
39
+ #
40
+ # @return [Promise] a promise that will be resolved with the response
36
41
  def self.get(url, &block)
37
42
  send(:get, url, &block)
38
43
  end
@@ -40,7 +45,8 @@ module HTTP
40
45
  # Send an asynchronous HEAD request.
41
46
  #
42
47
  # @param url [String] the URL to request
43
- # @return [Response] the response
48
+ #
49
+ # @return [Promise] a promise that will be resolved with the response
44
50
  def self.head(url, &block)
45
51
  send(:head, url, &block)
46
52
  end
@@ -49,7 +55,8 @@ module HTTP
49
55
  #
50
56
  # @param url [String] the URL to request
51
57
  # @param data [String, Hash] the data to send
52
- # @return [Response] the response
58
+ #
59
+ # @return [Promise] a promise that will be resolved with the response
53
60
  def self.post(url, data = nil, &block)
54
61
  send(:post, url, data, &block)
55
62
  end
@@ -58,7 +65,8 @@ module HTTP
58
65
  #
59
66
  # @param url [String] the URL to request
60
67
  # @param data [String, Hash] the data to send
61
- # @return [Response] the response
68
+ #
69
+ # @return [Promise] a promise that will be resolved with the response
62
70
  def self.put(url, data = nil, &block)
63
71
  send(:put, url, data, &block)
64
72
  end
@@ -67,7 +75,8 @@ module HTTP
67
75
  #
68
76
  # @param url [String] the URL to request
69
77
  # @param data [String, Hash] the data to send
70
- # @return [Response] the response
78
+ #
79
+ # @return [Promise] a promise that will be resolved with the response
71
80
  def self.delete(url, data = nil, &block)
72
81
  send(:delete, url, data, &block)
73
82
  end
@@ -77,6 +86,7 @@ module HTTP
77
86
  # @param method [Symbol] the HTTP method to use
78
87
  # @param url [String] the URL to request
79
88
  # @param data [String, Hash] the data to send
89
+ #
80
90
  # @return [Response] the response
81
91
  def self.send!(method, url, data = nil, &block)
82
92
  Request.new(&block).open(method, url, false).send(data)
@@ -85,6 +95,7 @@ module HTTP
85
95
  # Send a synchronous GET request.
86
96
  #
87
97
  # @param url [String] the URL to request
98
+ #
88
99
  # @return [Response] the response
89
100
  def self.get!(url, &block)
90
101
  send!(:get, url, &block)
@@ -93,6 +104,7 @@ module HTTP
93
104
  # Send a synchronous HEAD request.
94
105
  #
95
106
  # @param url [String] the URL to request
107
+ #
96
108
  # @return [Response] the response
97
109
  def self.head!(url, &block)
98
110
  send!(:head, url, &block)
@@ -102,6 +114,7 @@ module HTTP
102
114
  #
103
115
  # @param url [String] the URL to request
104
116
  # @param data [String, Hash] the data to send
117
+ #
105
118
  # @return [Response] the response
106
119
  def self.post!(url, data = nil, &block)
107
120
  send!(:post, url, data, &block)
@@ -111,6 +124,7 @@ module HTTP
111
124
  #
112
125
  # @param url [String] the URL to request
113
126
  # @param data [String, Hash] the data to send
127
+ #
114
128
  # @return [Response] the response
115
129
  def self.put!(url, data = nil, &block)
116
130
  send!(:put, url, data, &block)
@@ -120,6 +134,7 @@ module HTTP
120
134
  #
121
135
  # @param url [String] the URL to request
122
136
  # @param data [String, Hash] the data to send
137
+ #
123
138
  # @return [Response] the response
124
139
  def self.delete!(url, data = nil, &block)
125
140
  send!(:delete, url, data, &block)
@@ -31,6 +31,11 @@ class Headers
31
31
  @hash = Hash.new
32
32
  end
33
33
 
34
+ # Clear the {Headers}.
35
+ def clear
36
+ @hash.clear
37
+ end
38
+
34
39
  # Iterate over the headers.
35
40
  #
36
41
  # @yield [name, value] the header name and value
@@ -8,7 +8,6 @@ class Request
8
8
  # @param method [Symbol] the HTTP method to use
9
9
  # @param url [String, #to_s] the URL to request
10
10
  # @param parameters [String, Hash] the parameters to send
11
- #
12
11
  def self.open(method, url, parameters = nil, &block)
13
12
  request = new(&block)
14
13
  request.open(method, url)
@@ -44,11 +43,13 @@ class Request
44
43
  def initialize(&block)
45
44
  super(transport)
46
45
 
46
+ @parameters = {}
47
+ @query = {}
47
48
  @headers = Headers[DEFAULT_HEADERS]
48
49
  @method = :get
49
50
  @asynchronous = true
50
51
  @binary = false
51
- @cacheable = true
52
+ @cacheable = true
52
53
  @opened = false
53
54
  @sent = false
54
55
  @completed = false
@@ -61,8 +62,20 @@ class Request
61
62
  end if block
62
63
  end
63
64
 
64
- def transport
65
- `new XMLHttpRequest()`
65
+ # @!method transport
66
+ # @private
67
+ if Browser.supports? :XHR
68
+ def transport
69
+ `new XMLHttpRequest()`
70
+ end
71
+ elsif Browser.supports? :ActiveX
72
+ def transport
73
+ `new ActiveXObject("MSXML2.XMLHTTP.3.0")`
74
+ end
75
+ else
76
+ def transport
77
+ raise NotImplementedError
78
+ end
66
79
  end
67
80
 
68
81
  # Check if the request has been opened.
@@ -183,11 +196,18 @@ class Request
183
196
  #
184
197
  # @param hash [Hash] the parameters
185
198
  #
186
- # @return [self]
187
- def parameters(hash)
188
- @parameters = hash
199
+ # @return [Hash]
200
+ def parameters(hash = nil)
201
+ hash ? @parameters = hash : @parameters
202
+ end
189
203
 
190
- self
204
+ # Set the URI query.
205
+ #
206
+ # @param hash [Hash] the query
207
+ #
208
+ # @return [Hash]
209
+ def query(hash = nil)
210
+ hash ? @query = hash : @query
191
211
  end
192
212
 
193
213
  # Register an event on the request.
@@ -223,7 +243,17 @@ class Request
223
243
  url = @url
224
244
 
225
245
  unless cacheable?
226
- url += (url.include?(??) ? ?& : ??) + "_=#{rand}"
246
+ @query[:_] = rand
247
+ end
248
+
249
+ unless @query.empty?
250
+ if url.include???
251
+ url += ?&
252
+ else
253
+ url += ??
254
+ end
255
+
256
+ url += @query.encode_uri
227
257
  end
228
258
 
229
259
  `#@native.open(#{@method.to_s.upcase}, #{url.to_s}, #{@asynchronous}, #{@user.to_n}, #{@password.to_n})`
@@ -279,7 +309,7 @@ class Request
279
309
 
280
310
  if String === parameters
281
311
  data = parameters
282
- elsif Hash === parameters
312
+ elsif Hash === parameters && !parameters.empty?
283
313
  data = parameters.map {|vals|
284
314
  vals.map(&:encode_uri_component).join(?=)
285
315
  }.join(?&)
@@ -1,9 +1,30 @@
1
- require 'browser/compatibility/immediate'
1
+ require 'promise'
2
2
 
3
3
  module Browser
4
4
 
5
- # FIXME: drop the method_defined? checks when require order is fixed
5
+ # Class to easily create and dispatch an immediate call.
6
+ #
7
+ # Immediate calls are deferred function calls that happen as soon as they can
8
+ # be scheduled.
9
+ #
10
+ # Compatibility
11
+ # -------------
12
+ # The compatibility layer will try various implementations in the following
13
+ # order.
14
+ #
15
+ # + [setImmediate](https://developer.mozilla.org/en-US/docs/Web/API/Window.setImmediate)
16
+ # + [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage)
17
+ # + [readystatechange](https://developer.mozilla.org/en-US/docs/Web/Reference/Events/readystatechange)
18
+ # + [setTimeout](https://developer.mozilla.org/en/docs/Web/API/window.setTimeout)
19
+ #
20
+ # The order has been chosen from best to worst for both performance and
21
+ # preemptiveness.
6
22
  class Immediate
23
+ # Create an immediate for the given function which will be called with the
24
+ # arguments and block.
25
+ #
26
+ # @param func [Proc] the function to call
27
+ # @param args [Array] the arguments to call it with
7
28
  def initialize(func, args, &block)
8
29
  @aborted = false
9
30
  @function = func
@@ -11,14 +32,89 @@ class Immediate
11
32
  @block = block
12
33
  end
13
34
 
14
- def dispatch
15
- raise NotImplementedError
16
- end unless method_defined? :dispatch
35
+ # @!method dispatch
36
+ # Dispatch the immediate.
17
37
 
18
- def prevent
19
- raise NotImplementedError
20
- end unless method_defined? :prevent
38
+ # @!method prevent
39
+ # Prevent the immediate from being called once scheduled.
21
40
 
41
+ if Browser.supports? 'Immediate'
42
+ def dispatch
43
+ @id = `window.setImmediate(function() {
44
+ #{@function.call(*@arguments, &@block)};
45
+ })`
46
+ end
47
+
48
+ def prevent
49
+ `window.clearImmediate(#@id)`
50
+ end
51
+ elsif Browser.supports? 'Immediate (Internet Explorer)'
52
+ def dispatch
53
+ @id = `window.msSetImmediate(function() {
54
+ #{@function.call(*@arguments, &@block)};
55
+ })`
56
+ end
57
+
58
+ def prevent
59
+ `window.msClearImmediate(#@id)`
60
+ end
61
+ elsif Browser.supports? 'Window.send'
62
+ # @private
63
+ @@tasks = {}
64
+
65
+ # @private
66
+ @@prefix = "opal.browser.immediate.#{rand(1_000_000)}."
67
+
68
+ $window.on :message do |e|
69
+ if String === e.data && e.data.start_with?(@@prefix)
70
+ if task = @@tasks.delete(e.data[@@prefix.length .. -1])
71
+ task[0].call(*task[1], &task[2])
72
+ end
73
+ end
74
+ end
75
+
76
+ def dispatch
77
+ @id = rand(1_000_000).to_s
78
+ @@tasks[@id] = [@function, @arguments, @block]
79
+
80
+ $window.send! "#{@@prefix}#{@id}"
81
+ end
82
+
83
+ def prevent
84
+ @@tasks.delete(@id)
85
+ end
86
+ elsif Browser.supports? 'Event.readystatechange'
87
+ def dispatch
88
+ %x{
89
+ var script = document.createElement("script");
90
+
91
+ script.onreadystatechange = function() {
92
+ if (!#{aborted?}) {
93
+ #{@function.call(*@arguments, &@block)};
94
+ }
95
+
96
+ script.onreadystatechange = null;
97
+ script.parentNode.removeChild(script);
98
+ };
99
+
100
+ document.documentElement.appendChild(script);
101
+ }
102
+ end
103
+
104
+ def prevent; end
105
+ else
106
+ def dispatch
107
+ @id = `window.setTimeout(function() {
108
+ #{@function.call(*@arguments, &@block)};
109
+ }, 0)`
110
+ end
111
+
112
+ def prevent
113
+ `window.clearTimeout(#@id)`
114
+ end
115
+ end
116
+
117
+ # Abort the immediate.
22
118
  def abort
23
119
  return if aborted?
24
120
 
@@ -28,6 +124,7 @@ class Immediate
28
124
  self
29
125
  end
30
126
 
127
+ # Check if the immediate has been aborted.
31
128
  def aborted?
32
129
  @aborted
33
130
  end
@@ -36,8 +133,25 @@ end
36
133
  end
37
134
 
38
135
  class Proc
39
- # Defer the function to be called as soon as possible.
136
+ # (see Immediate.new)
40
137
  def defer(*args, &block)
41
138
  Browser::Immediate.new(self, args, &block).tap(&:dispatch)
42
139
  end
43
140
  end
141
+
142
+ class Promise
143
+ # Create a promise which will be resolved with the result of the immediate.
144
+ #
145
+ # @param args [Array] the arguments the block will be called with
146
+ def self.defer(*args, &block)
147
+ new.tap {|promise|
148
+ proc {
149
+ begin
150
+ promise.resolve(block.call(*args))
151
+ rescue Exception => e
152
+ promise.reject(e)
153
+ end
154
+ }.defer
155
+ }
156
+ end
157
+ end