mwunsch / weary

A framework and DSL for building RESTful web service clients
MIT License
480 stars 23 forks source link

How is Weary asynchronous? #11

Closed volkanunsal closed 12 years ago

volkanunsal commented 12 years ago

I'm curious about how asynchronicity is implemented compared to Typhoeus. Not having read the source code of Faraday and Httparty, I am not sure how it is inspired by those gems. Can anyone shed light on that?

volkanunsal commented 12 years ago

In particular, I want to bring up how I make requests using Weary versus using Typhoeus.

v1 = VotingClientWeary.new
v1.json {:vote=>"up"}
v1.create_new_vote(:user_id=>1, :entry_id=>1)
v1.perform                               

v2 = VotingClientWeary.new               
v2.json {:vote=>"down"}                  
v2.create_new_vote(:user_id=>2, :entry_id=>1)
v2.perform                               

Are these requests asynchronous? I am calling them one after the other, so that I can deal with the failures. There needs to be a way to chain these requests and perform all of them together at once, and from the documentation I don't think there is. Compared to that, here is how I do the same calls with Typhoeus:

hydra = Typhoeus::Hydra.new
req1 = Typhoeus::Request.new(
  "/api/v1/ratings/entries/1/users/1/vote",
  :method => :post,
  :body => {:vote => "up"}.to_json)

req2 = Typhoeus::Request.new(
   "/api/v1/ratings/entries/1/users/2/vote",
   :method => :post,
   :body => {:vote => "down"}.to_json)

hydra.queue(req1).queue(req2)
hydra.run
volkanunsal commented 12 years ago

Upon further reflection I seem to have mistaken asynchronous with parallel, but I think there should be a way of making requests parallel as well. Just adding an adapter wouldn't be enough for this purpose.

mwunsch commented 12 years ago

When you call Request#perform, that computation is performed independently of the main program flow. This action is always non-blocking. Consider this example:

require 'weary/request'

request = Weary::Request.new "https://api.github.com/repos/mwunsch/weary"

request.perform do |response|
  puts response.body
end

Remember that perform takes a block that accepts a response and executes once the response has completed. When you execute this file as is you will not see the response's body. Why? Because the main thread completes before the request has completed. Changing this to

require 'weary/request'

request = Weary::Request.new "https://api.github.com/repos/mwunsch/weary"

response = request.perform
puts response.body

will produce the desired results, and here's why: Request#perform returns a Future -- a lightweight proxy object wrapping the Response (Weary uses the promise gem under the hood). The main thread is only blocked when you call a method on the response. This is all done with plain ol' Ruby Threads (of which there is a lot of FUD in the Ruby community because of the GIL, but this is a perfect use case for them).

I haven't investigated Typhoeus's source code too deeply, but I believe it implements concurrency using an extension to libcurl-multi. Not sure what that means in practice compared to using a Thread.

Unlike Typhoeous, Weary does not provide a queue mechanism. But you could just do something like:

responses = [req1, req2].map(&:perform)

It might be helpful to look at how my Gilt gem performs multiple requests in parallel to get a sale's product.

Also unlike Typhoueus, Weary provides no internal mechanisms for memoization or caching, but you can use Rack middleware to achieve the desired result. Hope that answers your question.

Weary was inspired by HTTParty and Faraday in its API design. Versions of Weary pre-1.0 were more similar to HTTParty, and Faraday uses a Rack-esque approach that I think is pretty cool.