delayer-deferred 1.1.1 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37e4d01b997cbcc9e878f5743755883123843d4f
4
- data.tar.gz: f43c013e223a6f30da88a500237fbb1920473e26
3
+ metadata.gz: 5fe3336ed51fed19dfd37766cf6b1144a6a8de45
4
+ data.tar.gz: ceb10cb5738de56ccd937ef6e8ee253ca6c0756e
5
5
  SHA512:
6
- metadata.gz: e53e3785d68e9c69431742d81efaa936ee80a93cf726c0c42f022732e120cbb7b5a08036743081b780e9548d7fbe4d797b1688c2a4ec68fd8a4e0a072770a860
7
- data.tar.gz: dd6d334922b6e999fd2f2eb90fc3fccd656d038d76cfa10b6742961246c1feaca452862837a57c8a3dc33d2ed4257df38fbc401f15e5a40f505f8b8fdab53c6f
6
+ metadata.gz: f7e52f1cf451cfc12e61d9712d55113e7d42a8f68b57459b7b9c55a9b04e93c6fad6a171a0de157f40583b5fac29f350ab1204513ddd100801da468baf47e87c
7
+ data.tar.gz: a72e6d3f75728f7b93296acc387207ec2aaa4e3ce809cd564bff82ef298934168b769af87d2ead3b22752bcd2ab29e6f7de0d15924b25aec83114e609fa143ed
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Delayer::Deferred
2
2
 
3
- Delayerを使って、jsdeferredをRubyに移植したものです。
4
- jsdeferredでできること以外に、Thread、Enumeratorを拡張します。
3
+ Delayerの、ブロックを実行キューにキューイングする機能を利用し、エラー処理やasync/awaitのような機能をサポートするライブラリです。
5
4
 
6
5
  ## Installation
7
6
 
@@ -101,7 +100,7 @@ Error occured!
101
100
  ```
102
101
 
103
102
  ### Thread
104
- Threadには、Delayer::Deferred::Deferredableモジュールがincludeされていて、nextやtrapメソッドが使えます。
103
+ Threadには、nextやtrapメソッドが実装されているので、Deferredのように扱うことができます。
105
104
 
106
105
  ```ruby
107
106
  Delayer.default = Delayer.generate_class # Delayerの準備
@@ -117,6 +116,8 @@ Delayer.run
117
116
  2
118
117
  ```
119
118
 
119
+ この場合、nextやtrapのブロックは、全て `Delayer.run` メソッドが実行された側のThreadで実行されます。
120
+
120
121
  ### Automatically Divide a Long Loop
121
122
  `Enumerable#deach`, `Enumerator#deach`はeachの変種で、Delayerのexpireの値よりループに時間がかかったら一旦処理を中断して、続きを実行するDeferredを新たに作ります。
122
123
 
@@ -156,6 +157,79 @@ divided
156
157
 
157
158
  また、このメソッドはDeferredを返すので、ループが終わった後に処理をしたり、エラーを受け取ったりできます。
158
159
 
160
+ ### Pass to another Delayer
161
+
162
+ Deferredのコンテキストの中で `Deferred.pass` を呼ぶと、そこで一旦処理が中断し、キューの最後に並び直します。
163
+ 他のDelayerが処理され終わると `Deferred.pass` から処理が戻ってきて、再度そこから実行が再開されます。
164
+
165
+ `Deferred.pass` は常に処理を中断するわけではなく、Delayerの時間制限を過ぎている場合にのみ処理をブレークします。
166
+ 用途としては `Enumerator#deach` が使えないようなループの中で毎回呼び出して、長時間処理をブロックしないようにするといった用途が考えられます。
167
+
168
+ `Enumerator#deach` は `Deferred.pass` を用いて作られています。
169
+
170
+ ### Combine Deferred
171
+
172
+ `Deferred.when` は、引数に2つ以上のDeferredを受け取り、新たなDeferredを一つ返します。
173
+
174
+ 引数のDeferredすべてが正常に終了したら、戻り値のDeferredのnextブロックが呼ばれ、whenの引数の順番通りに戻り値が渡されます。
175
+ 複数のDeferredがあって、それらすべてが終了するのを待ち合わせる時に使うと良いでしょう。
176
+
177
+ ```ruby
178
+ web_a = Thread.new{ open("http://example.com/a.html") }
179
+ web_b = Thread.new{ open("http://example.com/b.html") }
180
+ web_c = Thread.new{ open("http://example.com/c.html") }
181
+
182
+ # 引数の順番は対応している
183
+ Deferred.when(web_a, web_b, web_c).next do |a, b, c|
184
+ ...
185
+ end
186
+
187
+ # 配列を渡すことも出来る
188
+ Deferred.when([web_a, web_b, web_c]).next do |a, b, c|
189
+ ...
190
+ end
191
+
192
+ ```
193
+
194
+ 引数のDeferredのうち、どれか一つでも失敗すると、直ちに `Deferred.when` の戻り値のtrapブロックが呼ばれます。trapの引数は、失敗したDeferredのそれです。
195
+
196
+ どれか一つでも失敗すると、他のDeferredが成功していたとしてもその結果は破棄されるということに気をつけてください。より細かく制御したい場合は、Async/Awaitを利用しましょう。
197
+
198
+ ```ruby
199
+ divzero = Delayer::Deferred.new {
200
+ 1 / 0
201
+ }
202
+ web_a = Thread.new{ open("http://example.com/a.html") }
203
+
204
+ Deferred.when(divzero, web_a).next{
205
+ puts 'success'
206
+ }.trap{|err|
207
+ p err
208
+ }
209
+ ```
210
+
211
+ ```
212
+ \#<ZeroDivisionError: divided by 0>
213
+ ```
214
+
215
+ ### Async/Await
216
+
217
+ Deferred#next や Deferred#trap のブロック内では、Deferredable#+@ が使えます。非同期な処理を同期処理のように書くことができます。
218
+
219
+ +@を呼び出すと、呼び出し元のDeferredの処理が一時停止し、+@のレシーバになっているDeferredableが完了した後に処理が再開されます。また、戻り値はレシーバのDeferredableのそれになります。
220
+
221
+ ```
222
+ request = Thread.new{ open("http://mikutter.hachune.net/download/unstable.json").read }
223
+ Deferred.next{
224
+ puts "最新の不安定版mikutterのバージョンは"
225
+ response = JSON.parse(+request)
226
+ puts response.first["version_string"]
227
+ puts "です"
228
+ }
229
+ ```
230
+
231
+ `+request` が呼ばれた時、リクエスト完了まで処理は一時止まりますが、他にDelayerキューにジョブが溜まっていたら、そちらが実行されます。この機能を使わない場合は、HTTPレスポンスを受け取るまでDelayerの他のジョブは停止してしまいます。
232
+
159
233
  ## Contributing
160
234
 
161
235
  1. Fork it ( https://github.com/toshia/delayer-deferred/fork )
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "bundler", "~> 1.7"
26
26
  spec.add_development_dependency "rake", "~> 10.0"
27
27
  spec.add_development_dependency "minitest", "~> 5.7"
28
+ spec.add_development_dependency "simplecov"
28
29
  spec.add_development_dependency "ruby-prof"
29
30
  spec.add_development_dependency 'guard'
30
31
  spec.add_development_dependency 'guard-shell'
@@ -10,25 +10,26 @@ require "delayer/deferred/version"
10
10
 
11
11
  module Delayer
12
12
  module Deferred
13
- extend Delayer::Deferred::Tools
14
-
15
13
  class << self
16
14
  #真ならデバッグ情報を集める
17
15
  attr_accessor :debug
18
16
 
19
- def new(*rest, &proc)
20
- Delayer::Deferred::Deferred.new(*rest, &proc) end
17
+ def method_missing(*rest, &block)
18
+ Delayer::Deferred::Deferred.__send__(*rest, &block)
19
+ end
21
20
  end
22
21
  end
23
22
 
24
23
  module Extend
25
- def Deferred
26
- @deferred ||= begin
27
- the_delayer = self
28
- Class.new(::Delayer::Deferred::Deferred) {
29
- define_singleton_method(:delayer) {
30
- the_delayer } } end
24
+ def Promise
25
+ @promise ||= begin
26
+ the_delayer = self
27
+ Class.new(::Delayer::Deferred::Promise) {
28
+ define_singleton_method(:delayer) {
29
+ the_delayer } } end
31
30
  end
31
+ alias :Deferred :Promise
32
+ #deprecate :Deferred, "Promise", 2018, 03
32
33
  end
33
34
  end
34
35
 
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Delayer::Deferred
4
+ module Chain; end
5
+ end
6
+
7
+ require "delayer/deferred/chain/await"
8
+ require "delayer/deferred/chain/base"
9
+ require "delayer/deferred/chain/next"
10
+ require "delayer/deferred/chain/trap"
@@ -0,0 +1,43 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "delayer/deferred/chain/base"
3
+
4
+ module Delayer::Deferred::Chain
5
+ class Await < Base
6
+ def initialize(worker:, deferred:)
7
+ super()
8
+ @worker, @awaiting_deferred = worker, deferred
9
+ deferred.add_awaited(self)
10
+ end
11
+
12
+ def activate(response)
13
+ change_sequence(:activate)
14
+ @worker.give_response(response, @awaiting_deferred)
15
+ # TODO: 即座にspoilさせてよさそう
16
+ ensure
17
+ change_sequence(:complete)
18
+ end
19
+
20
+ def graph_child(output:)
21
+ output << graph_mynode
22
+ if has_child?
23
+ @child.graph_child(output: output)
24
+ @awaiting_deferred.graph_child(output: output)
25
+ output << "#{__id__} -> #{@child.__id__}"
26
+ end
27
+ nil
28
+ end
29
+
30
+ def node_name
31
+ "Await"
32
+ end
33
+
34
+ def graph_shape
35
+ 'circle'.freeze
36
+ end
37
+
38
+ def graph_mynode
39
+ label = "#{node_name}\n(#{sequence.name})"
40
+ "#{__id__} [shape=#{graph_shape},label=#{label.inspect}]"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "delayer/deferred/deferredable/chainable"
3
+ require "delayer/deferred/deferredable/node_sequence"
4
+
5
+ module Delayer::Deferred::Chain
6
+ class Base
7
+ include Delayer::Deferred::Deferredable::NodeSequence
8
+ include Delayer::Deferred::Deferredable::Chainable
9
+
10
+ def initialize(&proc)
11
+ fail Error, "Delayer::Deferred::Chain can't create instance." if self.class == Delayer::Deferred::Chain::Base
12
+ @proc = proc
13
+ end
14
+
15
+ def activate(response)
16
+ change_sequence(:activate)
17
+ if evaluate?(response)
18
+ @proc.(response.value)
19
+ else
20
+ response
21
+ end
22
+ ensure
23
+ change_sequence(:complete)
24
+ end
25
+
26
+ def inspect
27
+ "#<#{self.class} seq:#{sequence.name} child:#{has_child?}>"
28
+ end
29
+
30
+ def node_name
31
+ @proc.source_location.join(':'.freeze)
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "delayer/deferred/chain/base"
3
+
4
+ module Delayer::Deferred::Chain
5
+ class Next < Base
6
+ def evaluate?(response)
7
+ response.ok?
8
+ end
9
+
10
+ private
11
+
12
+ def graph_shape
13
+ 'box'.freeze
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "delayer/deferred/chain/base"
3
+
4
+ module Delayer::Deferred::Chain
5
+ class Trap < Base
6
+ def evaluate?(response)
7
+ response.ng?
8
+ end
9
+
10
+ private
11
+
12
+ def graph_shape
13
+ 'diamond'.freeze
14
+ end
15
+ end
16
+ end
@@ -1,78 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
+ require "delayer/deferred/promise"
3
+ require "delayer/deferred/chain"
2
4
  require "delayer/deferred/deferredable"
3
- require "delayer/deferred/tools"
5
+ require "delayer/deferred/worker"
4
6
  require "delayer/deferred/version"
5
7
 
6
8
  module Delayer::Deferred
7
- class Deferred
8
- extend Delayer::Deferred::Tools
9
- include Deferredable
10
-
11
- def self.inherited(subclass)
12
- subclass.extend(::Delayer::Deferred)
13
- end
14
-
15
- def self.Thread
16
- @thread_class ||= gen_thread_class end
17
-
18
- def self.gen_thread_class
19
- the_delayer = delayer
20
- Class.new(Thread) do
21
- define_singleton_method(:delayer) do
22
- the_delayer end end end
23
-
24
- def self.delayer
25
- ::Delayer end
26
-
27
- def self.new(*args)
28
- deferred = super(*args)
29
- if block_given?
30
- deferred.next(&Proc.new)
31
- else
32
- deferred end
33
- end
34
-
35
- def initialize(follow = nil)
36
- super()
37
- @follow = follow
38
- @backtrace = caller if ::Delayer::Deferred.debug end
39
-
40
- alias :deferredable_cancel :cancel
41
- def cancel
42
- deferredable_cancel
43
- @follow.cancel if @follow.is_a? Deferredable end
44
-
45
- def inspect
46
- if ::Delayer::Deferred.debug
47
- sprintf("#<%s: %p %s follow:%p stat:%s value:%s>".freeze, self.class, object_id, @backtrace.find{|n|not n.include?("delayer/deferred".freeze)}, @follow ? @follow.object_id : 0, @next_call_stat.inspect, @next_call_value.inspect)
48
- else
49
- sprintf("#<%s: %p stat:%s value:%s>".freeze, self.class, object_id, @next_call_stat.inspect, @next_call_value.inspect)
50
- end
51
- end
52
-
53
- def self.fiber(&block)
54
- @_fiber ||= _gen_new_fiber
55
- begin
56
- result = @_fiber.resume(block)
57
- rescue FiberError
58
- @_fiber = _gen_new_fiber
59
- result = @_fiber.resume(block)
60
- end
61
- if result.is_a?(Delayer::Deferred::ResultContainer)
62
- result
63
- else
64
- _fiber = @_fiber
65
- @_fiber = nil
66
- return result, _fiber
67
- end
68
- end
69
-
70
- def self._gen_new_fiber
71
- Fiber.new do |b|
72
- loop do
73
- b = Fiber.yield(b.())
74
- end
75
- end
76
- end
77
- end
9
+ Deferred = Promise
78
10
  end
@@ -1,162 +1,12 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  require "delayer/deferred/version"
3
- require "delayer/deferred/result_container"
4
-
5
- # なんでもDeferred
6
- module Delayer::Deferred::Deferredable
7
- Callback = Struct.new(*%i<ok ng backtrace>)
8
- CallbackDefaultOK = lambda{ |x| x }
9
- CallbackDefaultNG = lambda{ |err| Delayer::Deferred.fail(err) }
10
-
11
- # このDeferredが成功した場合の処理を追加する。
12
- # 新しいDeferredのインスタンスを返す
13
- def next(&proc)
14
- _post(:ok, &proc) end
15
- alias deferred next
16
-
17
- # このDeferredが失敗した場合の処理を追加する。
18
- # 新しいDeferredのインスタンスを返す
19
- def trap(&proc)
20
- _post(:ng, &proc) end
21
- alias error trap
22
-
23
- # Deferredを直ちに実行する
24
- def call(value = nil)
25
- _call(:ok, value) end
26
-
27
- # Deferredを直ちに失敗させる
28
- def fail(exception = nil)
29
- _call(:ng, exception) end
30
-
31
- # _self_ が終了して結果が出るまで呼び出し側のDeferredを停止し、 _self_ の結果を返す。
32
- # 呼び出し側はDeferredブロック内でなければならないが、 _Deferred#next_ を使わずに
33
- # 直接戻り値を得ることが出来る。
34
- # _self_ が失敗した場合は、呼び出し側のDeferredの直近の _trap_ ブロックが呼ばれる。
35
- def +@
36
- interrupt = Fiber.yield(self)
37
- if interrupt.ok?
38
- interrupt.value
39
- else
40
- Delayer::Deferred.fail(interrupt.value)
41
- end
42
- end
43
-
44
- # この一連のDeferredをこれ以上実行しない
45
- def cancel
46
- @callback = Callback.new(CallbackDefaultOK,
47
- CallbackDefaultNG).freeze end
48
-
49
- def callback
50
- @callback ||= Callback.new(CallbackDefaultOK,
51
- CallbackDefaultNG) end
52
-
53
- # second 秒待って次を実行する
54
- # ==== Args
55
- # [second] 待つ秒数(second)
56
- # ==== Return
57
- # Deferred
58
- def wait(second)
59
- self.next{ Thread.new{ sleep(second) } } end
60
-
61
- private
62
-
63
- def delayer
64
- self.class.delayer
65
- end
66
-
67
- def _call(stat = :ok, value = nil)
68
- delayer.new do
69
- result, fiber = delayer.Deferred.fiber do
70
- begin
71
- result = catch(:__deferredable_fail) do
72
- Delayer::Deferred::ResultContainer.new(true, _execute(stat, value))
73
- end
74
- if result.is_a?(Delayer::Deferred::ResultContainer)
75
- result
76
- else
77
- Delayer::Deferred::ResultContainer.new(false, result)
78
- end
79
- rescue Exception => exception
80
- Delayer::Deferred::ResultContainer.new(false, exception)
81
- end
82
- end
83
- #_wait_fiber(fiber, nil)
84
- if fiber
85
- _fiber_stopped(result){|i| _wait_fiber(fiber, i) }
86
- else
87
- _fiber_completed(result)
88
- end
89
- end
90
- end
91
-
92
-
93
- def _execute(stat, value)
94
- callback[stat].call(value)
95
- end
96
-
97
- def _wait_fiber(fiber, resume_value)
98
- result = fiber.resume(resume_value)
99
- if result.is_a?(Delayer::Deferred::ResultContainer)
100
- _fiber_completed(result)
101
- else
102
- _fiber_stopped(result){|i| _wait_fiber(fiber, i) }
103
- end
104
- end
105
-
106
- # Deferredブロックが最後まで終わり、これ以上やることがない時に呼ばれる
107
- def _fiber_completed(result)
108
- result_value = result.value
109
- if result.ok?
110
- if result_value.is_a?(Delayer::Deferred::Deferredable)
111
- result_value.next{|v|
112
- _success_action(v)
113
- }.trap{|v|
114
- _fail_action(v)
115
- }
116
- else
117
- _success_action(result_value)
118
- end
119
- else
120
- _fail_action(result_value)
121
- end
122
- end
123
-
124
- # Deferredable#@+によって停止され、 _defer_ の完了次第処理を再開する必要がある時に呼ばれる
125
- def _fiber_stopped(defer, &cont)
126
- defer.next{|v|
127
- cont.(Delayer::Deferred::ResultContainer.new(true, v))
128
- }.trap{|v|
129
- cont.(Delayer::Deferred::ResultContainer.new(false, v))
130
- }
131
- end
132
-
133
- def _post(kind, &proc)
134
- @next = delayer.Deferred.new(self)
135
- @next.callback[kind] = proc
136
- if defined?(@next_call_stat) and defined?(@next_call_value)
137
- @next.__send__({ok: :call, ng: :fail}[@next_call_stat], @next_call_value)
138
- elsif defined?(@follow) and @follow.nil?
139
- call end
140
- @next end
141
-
142
- def register_next_call(stat, value)
143
- @next_call_stat, @next_call_value = stat, value
144
- self end
145
-
146
- def _success_action(obj)
147
- if defined?(@next)
148
- @next.call(obj)
149
- else
150
- register_next_call(:ok, obj)
151
- end
152
- end
153
-
154
- def _fail_action(err_obj)
155
- if defined?(@next)
156
- @next.fail(err_obj)
157
- else
158
- register_next_call(:ng, err_obj)
159
- end
160
- end
161
3
 
4
+ module Delayer::Deferred
5
+ module Deferredable; end
162
6
  end
7
+
8
+ require "delayer/deferred/deferredable/awaitable"
9
+ require "delayer/deferred/deferredable/chainable"
10
+ require "delayer/deferred/deferredable/graph"
11
+ require "delayer/deferred/deferredable/node_sequence"
12
+ require "delayer/deferred/deferredable/trigger"