その他の非同期処理テクニック:Generator、Observable、Web Worker

2024-05-18

JavaScriptはシングルスレッドなの? 非同期処理とイベントループで探る真相

この仕組みを理解することで、JavaScriptにおける「シングルスレッド」の本当の意味と、非同期処理を駆使した高速で滑らかなWebアプリケーション開発が可能になります。

シングルスレッドの制約:1つのタスクずつ順番に処理

従来のプログラミング言語では、マルチスレッドと呼ばれる仕組みで複数のタスクを並行して処理することが一般的でした。一方、JavaScriptはシングルスレッドであり、一度に処理できるタスクは1つだけという制約があります。

例えば、以下のようなコードを実行した場合を考えてみましょう。

function task1() {
  for (let i = 0; i < 1000000; i++) {
    console.log("task1");
  }
}

function task2() {
  for (let i = 0; i < 1000000; i++) {
    console.log("task2");
  }
}

task1();
task2();

このコードでは、task1task2という2つの関数が呼び出されています。しかし、シングルスレッドであるため、実際には順番に処理されます。つまり、

  1. task1が100万回ループ処理を実行

という順番になります。

この制約により、CPU密集型の処理を実行すると、他のタスクがブロックされてしまうという問題が発生します。例えば、画像処理や計算処理など、時間がかかる処理を実行すると、ユーザーインターフェースが反応しなくなる可能性があります。

非同期処理とイベントループで疑似的な並行処理を実現

しかし、JavaScriptには非同期処理と呼ばれる仕組みがあります。非同期処理とは、タスクを完了してから結果を通知するという方法です。具体的には、以下のような方法があります。

  • コールバック関数: 関数を実行し、その関数が完了した後に別の処理を実行する
  • Promise: 非同期処理の結果を非同期的に取得する
  • async/await: 非同期処理を同期的に記述する

これらの仕組みを活用することで、あたかも並行処理しているかのように動作させることができます。

さらに、イベントループと呼ばれる仕組みも重要です。イベントループは、ブラウザが処理すべきイベントを管理する仕組みです。例えば、ユーザーがクリックしたとき、タイマーが終了したときなど、様々なイベントが発生します。イベントループは、これらのイベントをキューに貯め、順番に処理していきます。

JavaScriptはシングルスレッドですが、非同期処理とイベントループを組み合わせることで、ユーザーインターフェースを滑らかに動作させながら、CPU密集型の処理を実行することができます。

シングルスレッドのメリットとデメリット

メリット

  • シンプルで理解しやすい: マルチスレッドに比べてコードがシンプルで理解しやすい
  • デバッグしやすい: スレッド間の競合などの問題が発生しにくい
  • 軽量: マルチスレッドに比べてメモリ使用量が少ない

デメリット

  • CPU密集型の処理が遅くなる: CPU密集型の処理を実行すると、他のタスクがブロックされてしまう
  • 複雑な処理には向かない: 複雑な並行処理を記述するには、非同期処理とイベントループの仕組みを深く理解する必要がある

まとめ

JavaScriptはシングルスレッドですが、非同期処理とイベントループを活用することで、あたかも並行処理しているかのように動作させることができます。この仕組みを理解することで、高速で滑らかなWebアプリケーション開発が可能になります。

ただし、シングルスレッドにはCPU密集型の処理が遅くなるというデメリットもあります。複雑なアプリケーション開発を行う場合は、この点を考慮する必要があります。

参考情報

  • [JavaScriptにおける非同期処理



JavaScriptにおける非同期処理サンプルコード

コールバック関数を使用した非同期処理

function task1(callback) {
  setTimeout(() => {
    console.log("task1");
    callback();
  }, 1000);
}

function task2() {
  console.log("task2");
}

task1(task2); // task1が完了したらtask2を実行

Promiseを使用した非同期処理

function task1() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("task1");
      resolve();
    }, 1000);
  });
}

function task2() {
  console.log("task2");
}

task1().then(task2); // task1が完了したらtask2を実行

このコードでは、task1関数の中で非同期処理を行い、完了したらPromiseオブジェクトを返しています。thenメソッドを使用して、Promiseオブジェクトが完了したときに実行する関数を指定することができます。task2関数をthenメソッドの引数として渡すことで、task1が完了した後にtask2が実行されます。

async/awaitを使用した非同期処理

async function main() {
  await task1();
  task2();
}

async function task1() {
  console.log("task1");
  await new Promise((resolve) => {
    setTimeout(resolve, 1000);
  });
}

function task2() {
  console.log("task2");
}

main();

このコードでは、async/await構文を使用して非同期処理を記述しています。asyncキーワードを関数名の前に記述することで、その関数が非同期処理を実行できることを示します。awaitキーワードを非同期処理の前に記述することで、その処理が完了するまで待機することができます。

これらのサンプルコードは、JavaScriptにおける非同期処理の基本的な仕組みを理解するのに役立ちます。

  • 複数の非同期処理を順番に実行する
  • エラー処理を行う

これらのサンプルコードは、以下のURLで確認することができます。

    これらのサンプルコードを参考に、さまざまな非同期処理を駆使したWebアプリケーション開発に挑戦してみてください。




    JavaScriptで非同期処理を行うその他の方法

    Generator

    • ジェネレータ関数は、イテレータオブジェクトを生成する関数です。
    • イテレータオブジェクトは、next()メソッドを呼び出すことで、順番に値を返すことができます。
    • 非同期処理をイテレータとして表現することで、コードをより分かりやすく記述することができます。
    function* main() {
      const result1 = yield task1();
      console.log(result1);
      const result2 = yield task2();
      console.log(result2);
    }
    
    async function task1() {
      const data = await fetch('https://api.example.com/data1');
      return data.json();
    }
    
    async function task2() {
      const data = await fetch('https://api.example.com/data2');
      return data.json();
    }
    
    const generator = main();
    let result = generator.next();
    while (!result.done) {
      result = generator.next(result.value);
    }
    

    Observable

    • Observableは、時間経過とともに値を発行するストリームを表すオブジェクトです。
    • 購読者は、subscribe()メソッドを使用してObservableに購読し、発行される値を受け取ることができます。
    const observable = Rx.Observable.create(observer => {
      task1().then(data => observer.onNext(data));
      task2().then(data => observer.onNext(data));
      observer.onCompleted();
    });
    
    observable.subscribe(
      data => console.log(data),
      error => console.error(error),
      () => console.log('完了しました')
    );
    

    Web Worker

    • Web Workerは、メインスレッドとは別のスレッドで実行されるスクリプトです。
    • CPU密集型の処理をWeb Workerで実行することで、メインスレッドをブロックせずに処理することができます。
    • ブラウザの描画やユーザーインターフェース操作などのメインスレッドの処理を妨げずに、重い処理を実行したい場合に有効です。
    const worker = new Worker('worker.js');
    worker.postMessage({ task: 'task1' });
    worker.onmessage = (event) => {
      console.log(event.data);
      worker.postMessage({ task: 'task2' });
    };
    

    EventTarget

    • EventTargetは、イベントを発行および処理するためのオブジェクトです。
    const target = new EventTarget();
    
    target.addEventListener('task1', (event) => {
      console.log(event.data);
    });
    
    target.addEventListener('task2', (event) => {
      console.log(event.data);
    });
    
    setTimeout(() => {
      target.dispatchEvent(new Event('task1', { data: 'task1完了' }));
    }, 1000);
    
    setTimeout(() => {
      target.dispatchEvent(new Event('task2', { data: 'task2完了' }));
    }, 2000);
    

    これらの方法は、それぞれ異なる特徴 and 利点があります。状況に合わせて適切な方法を選択することで、より効率的で分かりやすいコードを書くことができます。


      javascript concurrency


      安全な比較のために:JavaScriptの === 演算子を使いこなそう

      == 演算子は、値の型を変換して比較を行います。つまり、異なる型の値であっても、値が同じであれば true と判定されます。例:これらの例では、左辺と右辺の値は異なる型ですが、== 演算子によって true と判定されています。=== 演算子は、値の型と値を厳密に比較します。そのため、異なる型の値は常に false と判定されます。...


      【徹底解説】HTML、JavaScript、jQueryで実現!ラジオボタンのonChangeイベントハンドラ

      しかし、ラジオボタンの onChange イベントハンドラが期待通りに動作しない場合があるという問題があります。具体的には、ラジオボタンの値が変更されたときにイベントハンドラが一度しか実行されないことがあります。これは、複数のラジオボタンが同じ名前を持つグループに属している場合によく発生します。...


      JavaScriptのmapで要素をスキップする4つの方法:continue、filter、reduce、その他

      ここでは、map() で要素をスキップする3つの方法を紹介します。continue は、ループ処理の現在のイテレーションをスキップし、次のイテレーションに移行します。この例では、偶数の要素は continue でスキップし、奇数の要素のみを2倍して新しい配列に格納しています。...


      React Native vs ReactJS:モバイルアプリ開発の選択肢 (2023年最新版)

      ReactJS:Webアプリケーション開発向けのJavaScriptライブラリReact Native:モバイルアプリ開発向けのJavaScriptフレームワークメリット学習曲線が比較的緩やか軽量で高速な動作豊富なライブラリとコミュニティSEO対策に有利...


      React Router Link でのページ更新:パフォーマンス、データ保持、SEO のバランスを考慮した最適な方法

      React Router Linkを使用してページを更新するには、いくつかの方法があります。最も一般的な方法は、useRefフックとuseEffectフックを使用して、コンポーネントのマウント時にページを更新することです。この方法は、コンポーネントが最初にレンダリングされたときにのみページを更新する必要がある場合に適しています。...


      SQL SQL SQL SQL Amazon で見る



      JavaScript 非同期処理の待機:Promise、async/awaitを超えた多様なアプローチ

      同期処理 とは、コードを上から順番に処理していく方式です。つまり、前の処理が終わるまで次の処理は実行されない ということを意味します。一方、非同期処理 は、前の処理が終わるのを待たずに次の処理を開始し、処理が完了したタイミングで結果を処理するという方式です。