是否有类似JS' Promise.all()'在Ruby?

时间:2018-04-25 03:57:31

标签: ruby-on-rails ruby

以下是应优化的代码:

def statistics
  blogs = Blog.where(id: params[:ids])
  results = blogs.map do |blog|
    {
      id: blog.id,
      comment_count: blog.blog_comments.select("DISTINCT user_id").count
    }
  end
  render json: results.to_json
end

每个SQL查询的成本大约为200毫秒。如果我有10篇博文,这个函数需要2s,因为它同步运行。我可以使用GROUP BY来优化查询,但我先把它放在一边,因为任务可能是第三方请求,我对Ruby如何处理异步感兴趣。

在Javascript中,当我想调度多个异步工作并等待所有这些工作解析时,我可以使用Promise.all()。我想知道Ruby语言的替代方案是什么来解决这个问题。

这个案例我需要一个帖子吗?在Ruby中这样做是否安全?

3 个答案:

答案 0 :(得分:3)

在ruby中有多种方法可以解决这个问题,包括承诺(由宝石启用)。

JavaScript使用事件循环和事件驱动的I / O完成异步执行。有一些事件库可以在ruby中完成同样的事情。其中最受欢迎的是eventmachine

正如您所提到的,线程也可以解决此问题。线程安全是一个很大的主题,并且由于不同风格的红宝石(MRI,JRuby等)中的不同线程模型而进一步复杂化。总而言之,我只是说当然可以安全地使用线程......有时很难。但是,当与阻塞I / O(如API或数据库请求)一起使用时,线程可能非常有用并且相当直接。带线程的解决方案可能如下所示:

# run blocking IO requests simultaneously
thread_pool = [
  Thread.new { execute_sql_1 },
  Thread.new { execute_sql_2 },
  Thread.new { execute_sql_3 },
  # ...
]

# wait for the slowest one to finish
thread_pool.each(&:join)

您还可以访问其他货币模型,例如演员模型,异步类,承诺以及由concurrent-ruby等宝石启用的其他货币模型。

最后,ruby并发可以采用通过内置机制(drb,套接字等)或通过分布式消息代理(redis,rabbitmq等)进行通信的多个进程的形式。

答案 1 :(得分:1)

当然只需在一次数据库调用中进行计数:

blogs = Blog
  .select('blogs.id, COUNT(DISTINCT blog_comments.user_id) AS comment_count')
  .joins('LEFT JOIN blog_comments ON blog_comments.blog_id = blogs.id')
  .where(comments: { id: params[:ids] })
  .group('blogs.id')

results = blogs.map do |blog|
  { id: blog.id, comment_count: blog.comment_count }
end

render json: results.to_json

您可能需要更改语句,具体取决于您在数据库中命名的表的方式,因为我只是猜到了您的关联名称。

答案 2 :(得分:1)

好的,概括一点:

您有一个数据列表List,并希望异步操作该数据。假设列表中的所有条目的操作相同,则可以执行以下操作:

data

将它拆开:

data = [1, 2, 3, 4] # Example data
operation = -> (data_entry) { data * 2 } # Our operation: multiply by two
results = data.map{ |e| Thread.new(e, &operation) }.map{ |t| t.value }

这可以是从数据库ID到URI的任何内容。这里使用数字来简化。

data = [1, 2, 3, 4]

定义一个lambda,它接受一个参数并对其进行一些计算。这可能是API调用,SQL查询或需要一些时间才能完成的任何其他操作。同样,为简单起见,我只是将数字乘以2。

operation = -> (data_entry) { data * 2 }

此数组将包含所有异步操作的结果。

results =

对于数据集中的每个条目,生成一个运行data.map{ |e| Thread.new(e, &operation) }... 的线程并将该条目作为参数传递。这是lambda中的operation参数。

data_entry

从每个线程中提取值。这将等待线程首先完成,因此在此行结束时,所有数据都将存在。

lambda表达式

如果传入错误数量的参数,Lambdas实际上只是引发错误的美化块。语法...map{ |t| t.value } 只是-> (arguments) {code}的语法糖。

当方法接受Lambda.new { |arguments| code }之类的块时,您也可以传递前缀为Thread.new { do_async_stuff_here }的Lambda或Proc对象,并且它将以相同的方式处理。