📄

同期と非同期

に公開

はじめに

この記事は、プログラミング初心者を対象に、「同期処理」と「非同期処理」の基本をわかりやすく解説する。
両者の概念や違いを理解すると、プログラムの挙動をイメージしやすくなり、開発の効率やコードの質を高める助けになる。

同期処理(Synchronous)とは

定義

ある処理が終わるまで次の処理が始まらない方式のこと。
コードを上から順番に実行していき、前の処理が完了するまで次の処理に進まない。

メリット

  • 処理の流れがわかりやすい
    コードが直列的に実行されるため、初心者でも読みやすく理解しやすい構造になりやすい。

  • デバッグしやすい
    順番に処理が進むので、バグが起きた場合に原因箇所を特定しやすい。

デメリット

  • 待ち時間が長くなる場合がある
    時間のかかる処理(ネットワーク通信、ファイル操作など)があると、その処理が終わるまでプログラム全体が停止する。

  • ユーザー体験の低下
    UI が固まっているように見える(フリーズしたように感じられる)など、操作性に悪影響を及ぼす可能性がある。

同期的なコード例(TypeScript)

以下は while ループで 3 秒間プログラムを停止し、同期的な動きをイメージするサンプル。

const syncExample = (): void => {
  console.log("処理1開始");

  // 3秒間ブロックする(同期的に待機)
  const start = Date.now();
  while (Date.now() - start < 3000) {
    // ループ中は他の処理を行わない
  }

  console.log("処理1終了");
  console.log("処理2開始");
  console.log("処理2終了");
};

// 実行
syncExample();

このコードを実行すると、3 秒間何も出力がなく止まった後に以下の順番で出力される。

  1. 処理 1 終了
  2. 処理 2 開始
  3. 処理 2 終了

非同期処理(Asynchronous)とは

定義

ある処理の完了を待たずに、他の処理を進める方式のこと。
処理中に別の作業を並行して実行できるため、待ち時間を効率的に使うことができる。

メリット

  • 待ち時間の有効活用
    重い計算やネットワーク通信をしている最中でも、別の処理を先に進められる。

  • アプリケーションの応答性が高い
    UI が固まりづらく、スムーズに操作できるので、ユーザー体験が向上する。

デメリット

  • プログラムが複雑になりがち
    処理の終了タイミングやデータの受け渡しを管理する必要があり、コードが複雑化しやすい。

  • デバッグが難しい
    タイミング依存の不具合が起きやすく、原因を突き止めるのに時間がかかることがある。

非同期的なコード例(TypeScript)

setTimeout を使った例

const asyncExample = (): void => {
  console.log("処理1開始");

  // 3秒後にコールバックを実行する(非同期)
  setTimeout(() => {
    console.log("処理1終了(非同期)");
  }, 3000);

  console.log("処理2開始");
  console.log("処理2終了");
};

// 実行
asyncExample();

setTimeout で 3 秒後に実行される処理を登録し、その間に「処理 2」の部分が先に進む。
結果として以下の順番で表示される。

  1. 処理 1 開始
  2. 処理 2 開始
  3. 処理 2 終了
  4. 処理 1 終了(非同期)

了解です!
非同期処理の 3 つの代表的な書き方「コールバック(Callback)」「Promise」「async/await」について、初心者向けにわかりやすくそれぞれの特徴と使い方をまとめたセクションを追記できます。

非同期処理の書き方

TypeScriptでは、非同期処理を扱う方法がいくつか存在する。
ここでは代表的な 3 つの方法を紹介する。

コールバック(Callback)

概要

関数に別の関数を引数として渡すことで、処理が終わった後にその関数(コールバック)を呼び出す方法(前述のコード例)。

サンプルコード

const doSomething(callback: () => void): void => {
  console.log("処理開始");

  setTimeout(() => {
    console.log("処理終了(非同期)");
    callback(); // 処理が終わった後にコールバック関数を呼ぶ
  }, 2000);
}

doSomething(() => {
  console.log("次の処理");
});

特徴

単純な非同期処理には向いている
コールバック地獄(Callback Hell) になりやすい

doSomething(() => {
  doSomethingElse(() => {
    doAnotherThing(() => {
      // ネストが深くなり読みづらい
    });
  });
});

Promise(プロミス)

概要

非同期処理の完了や失敗を表現するオブジェクト
コールバックに比べて構造が明確で、連続処理(チェーン) もしやすい。

サンプルコード

const doSomethingPromise(): Promise<void> => {
  console.log("処理開始");

  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("処理終了(非同期)");
      resolve(); // 成功時に呼ばれる
    }, 2000);
  });
}

doSomethingPromise().then(() => {
  console.log("次の処理");
});

特徴

then() で処理をつなげることができる
catch() でエラー処理ができる
ネストが少なく、コールバックより読みやすい

async / await

概要

Promise同期処理のように書ける構文
読みやすく、エラーハンドリングも try/catch で書ける。

サンプルコード

const doSomethingAsync = async (): Promise<void> => {
  console.log("処理開始");

  await new Promise<void>((resolve) => {
    setTimeout(() => {
      console.log("処理終了(非同期)");
      resolve();
    }, 2000);
  });

  console.log("次の処理");
};

doSomethingAsync();

特徴

await を使うことで、コードの見た目は同期的になる
try / catch で直感的にエラー処理が書ける

まとめ

特徴 同期処理 非同期処理
実行順 上から順に処理 順番がずれることもある
分かりやすさ △(慣れると ◎)
UI への影響 固まりやすい スムーズに動作する
主な用途 簡単なロジック 通信、ファイル、待ち処理など

用途や処理内容に応じて、同期と非同期を使い分けることが大切。
非同期は最初は少し難しく感じるかもしれないが、慣れてくるととても便利なのでこれを機にマスターしてみてはいかがだろうか。

GitHubで編集を提案

Discussion