用render_async加快Rails页面的渲染速度(附实例)

96 阅读3分钟

向Rails控制器添加新的代码会带来一些问题。有时控制器的动作会变得非常大,而且它们往往会做很多事情。另一个常见的问题是,随着时间的推移,数据会增加,这可能会导致页面加载时间过慢。在控制器动作中添加新的代码,如果失败,有时也会阻止一些动作的渲染,破坏用户体验和用户的幸福感。

Semaphore,我们遇到过几次这种类型的问题。我们通常通过将控制器动作分割成更小的动作,并使用普通的JavaScript异步渲染来解决这些问题。

一段时间后,我们发现这可以提取到render_async,这是一个为你加快Rails页面的宝石--它通过对你的Rails服务器进行AJAX调用,异步地将内容加载到你的HTML中。

问题一:迟钝随着时间的推移不断积累

随着新代码的加入,Rails控制器动作会变得很 "肥"。如果我们不小心,页面加载时间会随着代码和数据量的增加而慢慢增加。

问题二:处理阻碍你行动的代码

当我们在控制器中添加新的代码时,我们有时需要在控制器动作中加载额外的数据,以呈现完整的视图。

让我们来看看一个阻止动作渲染的代码的例子。

假设我们有一个movies_controller.rb,在展示动作中,我们想从数据库中获取一部电影,但我们也想从IMDB中获取电影评分:

class MoviesController < ApplicationController
  def show
    @movie = Movies.find_by_id(params[:id])

    @movie_rating = IMDB.movie_rating(@movie)
  end
end

通过find_by_id获取电影是一个正常的行,它试图在我们的数据库中找到一个我们可以控制的电影。

然而,我们获取电影评级的那一行向IMDB服务提出了一个外部请求,该服务被期望返回答案。当一个外部服务不可用或正在经历停机时,问题就开始了。现在我们的MoviesController#show已经宕机,无法加载给想要看电影的用户。

解决方案

这两个问题都可以通过使用rendered_async gem拆分你的代码来解决或缓解。rendered_async在你的Rails页面渲染后异步地加载内容。

为什么选择rendered_async而不是传统的JavaScript代码,因为后者会进行异步请求并将HTML添加到页面中?因为rendered_async为你完成了无聊的JavaScript获取和替换。

render_async如何工作

假设你有一个app/views/movies/show.html.erb文件,它显示了从外部服务获取的关于指定电影和评级的细节。

下面是使用render_async之前的代码:

# app/views/movies/show.html.erb

Information about <%= @movie.title %>
<%= @movie.description %>

<%= render_async movie_rating_path(@movie.id) %>

这是使用render_async之后的样子:

# app/views/movies/show.html.erb

Information about <%= @movie.title %>
<%= @movie.description %>

<%= render_async movie_rating_path(@movie.id) %>

使用render_async,电影评分部分在show.html.erb加载后被加载。页面使用jQuery对movie_rating_path进行了AJAX请求,并将AJAX响应的内容渲染到页面的HTML中。

由于rendered_async向指定的路径发出请求,我们需要将其添加到config/routes.rb中:

# config/routes.rb

get :movie_rating, :controller => :movies

我们还需要在我们在路由中设置的控制器中添加一个合适的动作,所以我们要在movies_controller.rb中添加movie_rating动作:

# app/controllers/movies_controller.rb

def movie_rating
  @movie_rating = IMDB.movie_ratings(@movie)
  render :partial => "movie_ratings"
end

因为我们的movie_rating要渲染一个局部,所以我们需要为它创建一个局部来渲染:

# app/views/movies/_movie_rating.html.erb
Movie rating on IMDB: <%%= @movie_rating %>

最重要的部分是在你的应用程序布局中加入content_for标签,因为rendered_async会把获取AJAX响应的代码放在那里。最好是把它放在你的布局中的页脚之前:

# app/views/layouts/application.html.erb
<%= content_for :render_async %>

如果IMDB服务出现故障或没有响应,我们的节目页面将在没有电影评级的情况下加载。现在,电影页面不能被外部服务破坏,剩下的页面可以完全使用。

总结

在这个例子中,我们通过rendered_async成功地加快了Rails页面的渲染速度:

  1. 简化MoviesController的显示动作,从而使其更容易测试和加载。
  2. 将我们的电影评级标记分割成一个部分,这在Rails世界中是一个很好的模式,并且
  3. 将展示动作从外部服务中解放出来。