GithubHelp home page GithubHelp logo

delayer-deferred's Introduction

Delayer::Deferred

Delayerの、ブロックを実行キューにキューイングする機能を利用し、エラー処理やasync/awaitのような機能をサポートするライブラリです。

toshia

Installation

Add this line to your application's Gemfile:

gem 'delayer-deferred'

And then execute:

$ bundle

Or install it yourself as:

$ gem install delayer-deferred

Usage

The first step

rubygemでインストールしたあと、requireします。

require "delayer/deferred"

Delayer::Deferred.new が使えるようになります。ブロックを渡すと、Delayerのように後から(Delayer.runが呼ばれた時に)実行されます。

Delayer.default = Delayer.generate_class  # Delayerの準備
Delayer::Deferred.new {
  p "defer"
}
Delayer.run
defer

.next メソッドで、前のブロックの実行が終わったら、その結果を受け取って次を実行することができます。

Delayer.default = Delayer.generate_class  # Delayerの準備
Delayer::Deferred.new {
  1 + 1
}.next{ |sum|
  p sum
}
Delayer.run
2

Error handling

nextブロックの中で例外が発生した場合、次のtrapブロックまで処理が飛ばされます。 trapブロックは、その例外オブジェクトを引数として受け取ります。

Delayer.default = Delayer.generate_class  # Delayerの準備
Delayer::Deferred.new {
  1 / 0
}.next{ |sum|
  p sum
}.trap{ |exception|
  puts "Error occured!"
  p exception
}
Delayer.run
Error occured!
\#<ZeroDivisionError: divided by 0>

例外が発生すると、以降のnextブロックは無視され、例外が起こったブロック以降の最初のtrapブロックが実行されます。trapブロックの後にnextブロックがあればそれが実行されます。

Delayer::Deferred.fail() を使えば、例外以外のオブジェクトをtrapの引数に渡すこともできます。

Delayer.default = Delayer.generate_class  # Delayerの準備
Delayer::Deferred.new {
  Delayer::Deferred.fail("test error message")
}.trap{ |exception|
  puts "Error occured!"
  p exception
}
Delayer.run
Error occured!
"test error message"

Thread

Threadには、nextやtrapメソッドが実装されているので、Deferredのように扱うことができます。

Delayer.default = Delayer.generate_class  # Delayerの準備
Thread.new {
  1 + 1
}.next{ |sum|
  p sum
}
Delayer.run
2

この場合、nextやtrapのブロックは、全て Delayer.run メソッドが実行された側のThreadで実行されます。

Automatically Divide a Long Loop

Enumerable#deach, Enumerator#deachはeachの変種で、Delayerのexpireの値よりループに時間がかかったら一旦処理を中断して、続きを実行するDeferredを新たに作ります。

complete = false
Delayer.default = Delayer.generate_class(expire: 0.1)  # Delayerの準備
(1..100000).deach{ |digit|
  p digit
}.next{
  puts "complete"
  complete = true
}.trap{ |exception|
  p exception
  complete = true
}
while !complete
  Delayer.run
  puts "divided"
end
1
2
3
(中略)
25398
divided
25399
(中略)
100000
complete
divided

開発している環境では、25398までループした後、0.1秒経過したので一度処理が分断され、Delayer.runから処理が帰ってきています。

また、このメソッドはDeferredを返すので、ループが終わった後に処理をしたり、エラーを受け取ったりできます。

Pass to another Delayer

Deferredのコンテキストの中で Deferred.pass を呼ぶと、そこで一旦処理が中断し、キューの最後に並び直します。 他のDelayerが処理され終わると Deferred.pass から処理が戻ってきて、再度そこから実行が再開されます。

Deferred.pass は常に処理を中断するわけではなく、Delayerの時間制限を過ぎている場合にのみ処理をブレークします。 用途としては Enumerator#deach が使えないようなループの中で毎回呼び出して、長時間処理をブロックしないようにするといった用途が考えられます。

Enumerator#deachDeferred.pass を用いて作られています。

Combine Deferred

Deferred.when は、引数に2つ以上のDeferredを受け取り、新たなDeferredを一つ返します。

引数のDeferredすべてが正常に終了したら、戻り値のDeferredのnextブロックが呼ばれ、whenの引数の順番通りに戻り値が渡されます。 複数のDeferredがあって、それらすべてが終了するのを待ち合わせる時に使うと良いでしょう。

web_a = Thread.new{ open("http://example.com/a.html") }
web_b = Thread.new{ open("http://example.com/b.html") }
web_c = Thread.new{ open("http://example.com/c.html") }

# 引数の順番は対応している
Deferred.when(web_a, web_b, web_c).next do |a, b, c|
  ...
end

# 配列を渡すことも出来る
Deferred.when([web_a, web_b, web_c]).next do |a, b, c|
  ...
end

引数のDeferredのうち、どれか一つでも失敗すると、直ちに Deferred.when の戻り値のtrapブロックが呼ばれます。trapの引数は、失敗したDeferredのそれです。

どれか一つでも失敗すると、他のDeferredが成功していたとしてもその結果は破棄されるということに気をつけてください。より細かく制御したい場合は、Async/Awaitを利用しましょう。

divzero = Delayer::Deferred.new {
  1 / 0
}
web_a = Thread.new{ open("http://example.com/a.html") }

Deferred.when(divzero, web_a).next{
  puts 'success'
}.trap{|err|
  p err
}
\#<ZeroDivisionError: divided by 0>

Async/Await

Deferred#next や Deferred#trap のブロック内では、Deferredable#+@ が使えます。非同期な処理を同期処理のように書くことができます。

+@を呼び出すと、呼び出し元のDeferredの処理が一時停止し、+@のレシーバになっているDeferredableが完了した後に処理が再開されます。また、戻り値はレシーバのDeferredableのそれになります。

request = Thread.new{ open("http://mikutter.hachune.net/download/unstable.json").read }
Deferred.next{
  puts "最新の不安定版mikutterのバージョンは"
  response = JSON.parse(+request)
  puts response.first["version_string"]
  puts "です"
}

+request が呼ばれた時、リクエスト完了まで処理は一時止まりますが、他にDelayerキューにジョブが溜まっていたら、そちらが実行されます。この機能を使わない場合は、HTTPレスポンスを受け取るまでDelayerの他のジョブは停止してしまいます。

Contributing

  1. Fork it ( https://github.com/toshia/delayer-deferred/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

delayer-deferred's People

Contributors

toshia avatar cosmo0920 avatar

Stargazers

Yuki MIZUNO avatar

Watchers

 avatar James Cloos avatar shibafu avatar

delayer-deferred's Issues

1.1.0 で test/deferred_test.rb が失敗する

https://github.com/toshia/delayer-deferred/blob/v1.1.0/test/deferred_test.rb#L239-L240

failure.process が nil になっているようです。

# Running:

..E......................../var/local/deb/ruby-delayer-deferred/ruby-delayer-deferred/test/enumerable_test.rb:34:in `block (3 levels) in <top (required)>': Object#timeout is deprecated, use Timeout.timeout instead.
..

Finished in 0.219445s, 132.1516 runs/s, 346.3283 assertions/s.

  1) Error:
Delayer::Deferred::Deferredable#system#test_0002_command failed:
NoMethodError: undefined method `exited?' for nil:NilClass
Did you mean?  extend
    /var/local/deb/ruby-delayer-deferred/ruby-delayer-deferred/test/deferred_test.rb:239:in `block (3 levels) in <top (required)>'

Ruby 2.6.5でDeferred.when()を使うとエラーが発生する

Ruby 2.6.5, delayer-deferred 2.1.2の組み合わせで Deferred.when() を使用すると、常にTypeErrorが発生します。
Ruby 2.7.0では発生しません。

おそらく、下記の部分のkwrestの扱いだとは思うのですが……

def method_missing(*rest, **kwrest, &block)
Delayer::Deferred::Promise.__send__(*rest, **kwrest, &block)
end

Ruby version

ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]

Trace

Traceback (most recent call last):
        18: from mikutter.rb:117:in `<main>'
        17: from mikutter.rb:81:in `boot!'
        16: from /home/shibafu/git/mikutter/plugin/gtk/mainloop.rb:10:in `mainloop'
        15: from /home/shibafu/git/mikutter/plugin/gtk/mainloop.rb:10:in `catch'
        14: from /home/shibafu/git/mikutter/plugin/gtk/mainloop.rb:12:in `block in mainloop'
        13: from /home/shibafu/git/mikutter/plugin/gtk/mainloop.rb:12:in `loop'
        12: from /home/shibafu/git/mikutter/plugin/gtk/mainloop.rb:23:in `block (2 levels) in mainloop'
        11: from /home/shibafu/git/mikutter/vendor/bundle/ruby/2.6.0/gems/delayer-1.1.1/lib/delayer.rb:37:in `method_missing'
        10: from /home/shibafu/git/mikutter/vendor/bundle/ruby/2.6.0/gems/delayer-1.1.1/lib/delayer/extend.rb:102:in `run_once'
         9: from /home/shibafu/git/mikutter/vendor/bundle/ruby/2.6.0/gems/delayer-1.1.1/lib/delayer/procedure.rb:26:in `run'
         8: from /home/shibafu/git/mikutter/plugin/mastodon/mastodon.rb:67:in `block (2 levels) in <top (required)>'
         7: from /home/shibafu/git/mikutter/plugin/mastodon/mastodon.rb:51:in `block (2 levels) in <top (required)>'
         6: from /home/shibafu/git/mikutter/plugin/mastodon/mastodon.rb:51:in `each'
         5: from /home/shibafu/git/mikutter/plugin/mastodon/mastodon.rb:52:in `block (3 levels) in <top (required)>'
         4: from /home/shibafu/git/mikutter/vendor/bundle/ruby/2.6.0/gems/delayer-deferred-2.1.2/lib/delayer/deferred.rb:22:in `method_missing'
         3: from /home/shibafu/git/mikutter/vendor/bundle/ruby/2.6.0/gems/delayer-deferred-2.1.2/lib/delayer/deferred/tools.rb:37:in `when'
         2: from /home/shibafu/git/mikutter/vendor/bundle/ruby/2.6.0/gems/delayer-deferred-2.1.2/lib/delayer/deferred/tools.rb:37:in `each_with_index'
         1: from /home/shibafu/git/mikutter/vendor/bundle/ruby/2.6.0/gems/delayer-deferred-2.1.2/lib/delayer/deferred/tools.rb:37:in `each'
/home/shibafu/git/mikutter/vendor/bundle/ruby/2.6.0/gems/delayer-deferred-2.1.2/lib/delayer/deferred/tools.rb:39:in `block in when': Argument 3 of Deferred.when must be Delayer::Deferred::Deferredable::Chainable, but given Hash (TypeError)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.