0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初心者】同期通信と非同期通信の違いを、GitHub APIでリポジトリ一覧を表示しながら理解する

0
Posted at

はじめに

JavaScriptの非同期通信を学んでいて、「同期通信・非同期通信ってそもそも何が違うの?」と疑問に思いました。この記事では、疑問を一つずつ整理した過程を、最終的に動くコードまでつなげてまとめます。

1. 同期通信と非同期通信とは

  • 同期通信: 一つの処理が完了するまで次の処理を開始しない方式。
  • 非同期通信: 処理の完了を待たず、次の処理を進められる方式。

サーバーとの通信を同期的に行うと、処理中はブラウザが応答しなくなり、ユーザーにとってストレスになります。

最初は「ブラウザの応答に時間がかかっても、他の作業ができるのが非同期、ずっと待つのが同期」と理解していました。大筋は合っていますが、実際のWebアプリでは次の違いとして現れます。

同期通信 非同期通信
処理の進み方 前の処理が終わるまで次に進めない 完了を待たずに次に進める
画面の変化 ページ全体が作り直される 必要な部分だけ更新される

覚え方は「同期 = 全体の更新、非同期 = 一部分だけの更新」です。パラパラ漫画で言えば、同期は1ページ目から全部めくり直す、非同期は1コマだけ貼り替えるイメージです。

2. 非同期のほうが優れている?

非同期のほうが使いやすそうに思えますが、同期通信にも向いている場面があります。

同期通信のメリット

  • 処理の順序が保証され、データの整合性を保ちやすい
  • 実装がシンプルで、エラー箇所も特定しやすい
  • ブラウザの標準機能だけで完結する

同期通信が向く場面

  • ログイン・ログアウト(セッションを確実に切り替えてから遷移したい)
  • 投稿・編集フォームの送信(保存完了を確認してから遷移したい)
  • 決済処理(順序と完了確認が重要)

つまり「どちらが優れているか」ではなく、目的に応じた使い分けが大切です。

3. Railsではどのコードがどちら?

Railsの基礎で書いてきたコードは、見た目の挙動としては同期通信です。

<%= link_to "投稿一覧", posts_path %>
<%= form_with model: @post do |f| %>
  <%= f.text_field :title %>
  <%= f.submit %>
<% end %>
def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to @post, notice: '投稿しました'
  else
    render :new
  end
end

リンクを押すとURLが変わり、ページ全体が切り替わります。これが「同期っぽい」動きの目印です。

一方、fetch や turbo_frame_tag / turbo_stream を使うと、クリックした部分だけが更新される非同期の動きになります。

Rails 7系では、link_to や form_with も Turbo Drive によって内部的には fetch で通信され、HTMLを差し替える形で動いています。厳密には「見た目は同期的なページ遷移」ですが、初学者はまず「ページ全体が切り替わる = 同期的な動き」と捉えれば十分みたいです。

4. fetchとAPIの関係

fetch を学ぶ中で、「API」という言葉が2つの意味で使われて混乱しました。

fetch("https://api.github.com/users/ユーザー名/repos")
言葉 指しているもの
fetch ブラウザが用意している、通信するための道具(Web API)
https://api.github.com/... GitHubが用意している、データを返してくれる窓口(エンドポイント)

役所にたとえると、役所全体がGitHubのAPIサービス、住民票の窓口が各URL(エンドポイント)、そこへ行く交通手段が fetch です。

  • HTTPで通信するWeb APIなら、基本的にどれも fetch で呼び出せます。
  • 同じ目的の道具として XMLHttpRequest や axios などもあり、開発者やプロジェクトの方針で選びます。

APIから受け取るのはGitHubの「機能」ではなく、すでに存在するデータ(JSON)です。URLごとに取れるデータが違います。

URL 取得できるデータ
/users/ユーザー名/repos リポジトリ一覧
/users/ユーザー名 プロフィール情報
/repos/ユーザー名/リポジトリ名/issues Issue一覧

5. 実装:GitHubのリポジトリ一覧を表示する

ここまでの知識を使って、「GitHub nameを入力してボタンを押すと、リポジトリ名の一覧が表示される」機能を作ります。

HTML

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JavaScript基礎 非同期通信について</title>
  <script src="./index.js"></script>
</head>
<body>
  <input type="text" id="github_name" placeholder="github nameを入力してください">
  <button id="get_repositories_button">取得</button>
  <ul id="repository_list"></ul>
</body>
</html>

<ul> は Unordered List(順序なしリスト、箇条書き)のタグで、<li> とセットで使います。

STEP1:要素を取得する

index.js
window.addEventListener('load', () => {
  const button = document.querySelector("button")
  const repositorySpace = document.querySelector("#repository_list")
  console.log(button)
  console.log(repositorySpace)
})

document.querySelector("#repository_list") は、id="repository_list" の要素をJavaScriptから取得する処理です。この段階では、操作したい要素を取得できているかを確認するだけです。

STEP2:fetchでデータを取得する

index.js
button.addEventListener('click', () => {
  fetch('https://api.github.com/users/runteq/repos')
    .then((response) => response.json())
    .then((data) => console.log(data));
})

動かすと、入力欄が空でも runteq のリポジトリ情報(AdminTEQ など)がコンソールに表示されました。URLにユーザー名を直接書いているため、入力欄の値を一切使っていないのが原因です。

STEP3:入力値をURLに組み込む

index.js
button.addEventListener('click', () => {
  const githubName = document.querySelector("#github_name").value
  fetch(`https://api.github.com/users/${githubName}/repos`)
    .then((response) => response.json())
    .then((data) => console.log(data));
})

ポイントは2つです。

  • .value で入力欄の文字を取得する
  • 変数をURLに埋め込むには、バッククォート(`) と ${} のテンプレートリテラルを使う。シングルクォートだと展開されません。

入力欄を空にして押すと Not Found が返ります。入力値が正しく使われている証拠です。

STEP4:for文でリポジトリ名を取り出す

/users/○○/repos のレスポンスは、リポジトリ情報のオブジェクトが入った配列です。for...of で1件ずつ取り出し、name を表示します。

index.js
.then((data) => {
  for (const repositoryInfo of data) {
    console.log(repositoryInfo["name"])
  }
});

STEP5:画面に表示する(完成)

index.js
window.addEventListener('load', () => {
  const button = document.querySelector("button")
  const repositorySpace = document.querySelector("#repository_list")

  button.addEventListener('click', () => {
    const githubName = document.querySelector("#github_name").value
    fetch(`https://api.github.com/users/${githubName}/repos`)
      .then((response) => response.json())
      .then((data) => {
        for (const repositoryInfo of data) {
          // li要素を作成
          const li = document.createElement("li")
          // li要素にリポジトリ名を入れる
          li.innerHTML = repositoryInfo["name"]
          // ulにli要素を追加する
          repositorySpace.appendChild(li)
        }
      });
  })
})

for の中でやっていることは3つです。

ステップ 処理 たとえ
① createElement("li") で <li> を作る 空の箱を作る
② innerHTML にリポジトリ名を入れる 箱に名前の紙を入れる
③ appendChild(li) で <ul> に追加する 棚に箱を置く(ここで画面に表示)

これをリポジトリの数だけ繰り返すと、<ul> の中に <li> が積み重なって一覧になります。

6. 画面遷移せずに表示が変わる = 非同期通信

ボタンを押してもページはリロードされず、<ul> の中身だけが書き換わります。

  1. fetch で裏側でAPIと通信し、データだけを受け取る
  2. JavaScriptが <li> を作って、HTMLの一部分だけを書き換える

これが、冒頭で整理した「非同期通信は一部分だけ更新」の実例です。

7. このコードの改善ポイント

動くコードはできましたが、実際に使うと次の点が気になります。

  • 存在しないユーザー名を入れるとエラーになる: Not Found のときの data は配列ではなくオブジェクトなので、for...of で TypeError になります。response.ok や Array.isArray(data) で判定するとよさそうです。
  • ボタンを押すたびに一覧が追加され続ける: 表示前に repositorySpace.innerHTML = "" で中身を空にする必要があります。
  • for (repositoryInfo of data) の const 忘れ: 宣言なしだとグローバル変数になるため、const を付けるのが安全です。上のコードでは付けています。

まとめ

  • 同期通信は「全体の更新」で、順序や確実性が必要な場面(ログイン・決済・フォーム送信)に向く
  • 非同期通信は「一部分だけの更新」で、ユーザー体験をスムーズにしたい場面に向く
  • fetch は通信の道具で、GitHub APIなどのURL(エンドポイント)からデータ(JSON)を受け取る
  • 受け取った配列を for...of で回し、createElement と appendChild で画面に反映する

疑問を一つずつ言葉にして整理すると、「なんとなく分かる」から「手で書ける」に近づけた気がします。同じところでつまずいている方の参考になれば嬉しいです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?