📖

[Rails]非同期処理でいいね機能実装方法- 非同期処理,ajaxについて

に公開

同期通信と非同期通信の違いについて

[同期通信]

クライアントとサーバーが交互に処理を行い、同調して通信を行うこと
webブラウザからサーバーにリクエストを通信し、レスポンスが戻ってくる。
⚠️サーバーからレスポンスが返ってくるまでは他の作業はできない。

  • merit:全体を把握しやすい
  • demerit:処理完了までに時間がかかり、ユーザーにとってはストレス

[非同期通信]

一部の情報をサーバーに送信して、それを受け取り反映させる仕組み。
webプラウザから一部の情報のみをリクエストするので、それ以外の部分は変わらない。
なので画面が白くなることもない。
🌸サーバーからレスポンスが返ってこなくても他の作業ができる。

~比較~ 同期処理 非同期処理
merit 全体を把握しやすい 全体の処理速度を速められる
demerit 処理完了までに時間がかかり、ユーザーにとってはストレス プログラムの全体像が複雑になりやすい
使用場面 処理結果を待つ必要があるもの 処理結果を待たなくても良いもの、処理結果がなくても進められるもの

なぜ非同期処理が必要なのか

=> ”快適な操作性を確保するため”
同期処理だと、上からプログラムを実行していくため、
ある一つの処理が終了するまで次の処理を行えない。これは効率が悪い。

プログラムでの非同期処理の実装方法

jsでの非同期処理の方法はコールバック、Promise , async/await
登場順で、それぞれを見ていくと…

コールバック関数

  • コールバック=引数にとる関数のこと
  • Node.jsの標準ライブラリーで、非同期処理にコールバックを使っている
    →node.js = Webページ作成などの際に使われるJavaScriptを、
     サーバー側で動作させるプラットフォーム。

【問題点】

  • 複数の非同期処理を順に実行する場合、ネストがどんどん深くなる.(=読みづらい)
     (→ネスト…ルーチンやデータブロックの中に、またルーチンやデータブロックが入ること)
  • 複数の非同期処理を並列に実行して完了を待ち合わせるのは難しい

ネストが深くなりすぎるが故に、読みづらく**”コールバック地獄”** とも呼ばれるほどに…
そこでこのデメリットを改善した、新しいものが誕生!!

promise

前回のコールバックでの三つの問題点を解決するために生まれた!!!!

  • futureパターン(promiseパターン)と言われるデザインの一種
  • promiseオブジェクト(インスタンス)には三つのstatus(状態)がある.
      ①pending(保留): 初期状態。まだ非同期処理は終わってない(成功も失敗もしてない)
      ②fulfilled(成功): 非同期処理が正常に終了した
      ③rejected(拒否): 非同期処理が失敗した
    (簡単に言うと、後で値返すから待っててね、と言う約束!)
    【promiseの利点】
  • 連鎖した非同期をフラットにかける。
  • statusを持つ

<promiseの基本>

//promiseの基本!!
//最初は待っててねの状態
new promise(()=>{});

//resolveすると完了の状態
new promise( resolve => resolve() );

//完了時の値をthenで繋げて次の処理に渡せる
new promise(resolve => resolve("こんにちは")).then(res => console.log(res));

処理が成功した時 : PromiseStatusがresolvedになって、thenに書かれた処理が実行される。
処理が失敗した時:PromiseStatusがrejectedになって、catchに書かれた処理が実行される。

【promiseの問題点】
thenで繋げていくのは、みづらいのだ…

async / await

promiseをより快適に利用する特別な構文!!****

func = async() => {  //関数に対してasyncと宣言=> "この関数は非同期関数だよ"
	await log(3);
	await log(2);
  await log(1); //関数の呼び出しの前にawaitつけることでpromiseの結果が返ってくるまで待つ
};

log = (num) => {
	return new promise(resolve => {
		setTimeout() => {
			console.log(num);
			resolve();
		}, 1000);
	})
}

func();

async関数

async を関数の前につけるだけで 関数は常にpromiseを返す
自動的にpromiseでラップしてくれる。この中で await を使えるようにする

await関数

awaitはasync関数内のみで動作し、promiseが確定するまでJavaScriptを待機させる。
⚠️通常の関数でawaitを使うことはできない⚠️
非async関数で await を使おうとした場合、構文エラーになる


ajax通信とは

"Asynchronous JavaScript + XML"の略。
(=> JavaScriptがAjaxを構成する中核になるよ。)
Asynchronous = 非同時性の、非同期のの意味。
「JavaScriptとXMLを使って非同期にサーバとの間の通信を行うこと。」

ajaxは一つの機能ではなく、複数が組み合わさっている!

一つの機能でできているのではなく、以下のような複数の機能が組み合わさって実装している。

  • XML
  • JavaScript
  • DOM
  • XML

Ajaxの処理の流れ

  1. JavaScriptの組み込みオブジェクトである,XMLHttpRequestを使用し,
    更新に必要な一部のデータだけをサーバーにリクエストする。
    XMLHttpRequestについて:ドキュメントはここ

XMLHttpRequest:サーバーと対話するために使用され、これがあることで、
ページの再読み込みなしで直接サーバーにリクエストを送信する事が出来る。

  1. XMLファイルを使用し、レスポンスを返す。

XML:クライアントとサーバー間で情報をやりとりする際に、使用されるマークアップ言語

?:なんでHTMLではなくXML??
=> リクエストで、XMLHttpRequestオブジェクトを使用しているため。
⚠️JSONも可能!

  1. DOMを使用し、書き換える場所を指定する

DOM:Document Object Modelの略.
APIの一つで、HTMLやXMLをプログラムで操作する事が出来る。


いいね機能実装していこう!

1.jqueryの読み込みを行う。

<Gem file>

gem 'jquery-rails'

記入したら必ず、bundle install する!

jQueryとは

  • JavaScriptでできることを、より簡単な記法で実現できるように設計された
    JavaScriptライブラリの一種
  • JavaScriptライブラリのデファクトスタンダード

2.いいね機能にajaxを適応させる

Railsでは、フォーム系ヘルパーなどにremote: trueを設定するのみで、実装することができる!
<Books/_index.html.erb>

:
	省略
:
	
<% if book.favorited_by?(current_user) %>
<%= link_to book_favorites_path(book), method: :delete,remote: true,data: {"turbolinks" => false} do %>
  <i class="fa fa-heart" aria-hidden="true" style="color: red;"></i>
  <%= book.favorites.count %>
<% end %>
<% else %>
<%= link_to book_favorites_path(book.id) , method: :POST,remote: true,data: {"turbolinks" => false} do %>
<i class="fa fa-heart" aria-hidden="true" style="color: blue;"></i>
  <%= book.favorites.count %>
  <% end %>
<% end %>
  • data: {"turbolinks" => false}
    turbolinksを限定的に動かないようにするために記入。
         この記入がないと、リロードしないと反映されない事件になってしまう。

3.部分テンプレートを作成します。

views/favorites/_favorite.html.erbという新規ファイルを作成。
上記の部分をここに移し、partialの記述を行う。
<favorites/_favorite.html.erb>

<% if book.favorited_by?(current_user) %>
<%= link_to book_favorites_path(book), method: :delete,remote: true,data: {"turbolinks" => false} do %>
  <i class="fa fa-heart" aria-hidden="true" style="color: red;"></i>
  <%= book.favorites.count %>
<% end %>
<% else %>
<%= link_to book_favorites_path(book.id) , method: :POST,remote: true,data: {"turbolinks" => false} do %>
<i class="fa fa-heart" aria-hidden="true" style="color: blue;"></i>
  <%= book.favorites.count %>
  <% end %>
<% end %>

<Books/_index.html.erb>

:
	省略
:	
<tbody>
    <% books.each do |book| %>
      <tr>
        <td><%= link_to user_path(book.user) do %>
          <%= image_tag book.user.get_profile_image, size:'50x50' %>
          <% end %>
        </td>
        <td><%= link_to book.title, book_path(book.id) %></td>
        <td><%= book.body %></td>

        <!--いいね機能ajax-partial-->
        <td id="favorite_buttons_<%= book.id %>">
            <%= render "favorites/favorites", book: book %>
        </td>
        <td>  
            <%= link_to "#{book.book_comments.count} コメント", book_path(book.id) %>
        </td><% end%>
  • この箇所の更新を指定するために、変更したい箇所にidで名前をつけている。

4.FavoritesControllerのredilectさきを消す

class FavoritesController < ApplicationController
  def create
    @book = Book.find(params[:book_id])
    favorite = @book.favorites.new(user_id: current_user.id)
    favorite.save
    #redirect_back(fallback_location: root_path)
  end

  def destroy
    @book = Book.find(params[:book_id])
    favorite = current_user.favorites.find_by(book_id: @book.id)
    favorite.destroy
    #redirect_back(fallback_location: root_path)
  end
end
  • リダイレクト先がなくなるため、JavaScriptリクエストという状況になる。

5.利用するjs.erbファイルの編集

<view/favorites/create.js.erb>

$('#favorite_buttons_<%= @book.id %>').html("<%= j(render "favorites/favorites", book: @book) %>");

<view/fovorites/destroy.js.erb>

$('#favorite_buttons_<%= @book.id %>').html("<%= j(render "favorites/favorites", book: @book) %>");

以上!

Discussion