1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptの非同期処理メモ

Last updated at Posted at 2023-10-12

はじめに

JavaScriptの非同期処理メモ

参考

まとめ

  • 前提としてJavaScriptはシングルスレッドのため特定の処理(例えば非同期処理やI/O操作)が完了するのを待たずに次の処理を進める
  • だが、非同期処理を複数考える場合、終了の順番によって、結果が常にランダムになる(終わる順番が保証されていない) -> 非同期処理を順番に実行したい
  • 非同期処理を順番に実行
    • コールバックで実現 -> Promiseのthenで実現 -> async/awaitで実現
  • TypeScriptで非同期処理を扱う場合はasync/awaitを利用する
  • async/awaitPromiseのシンタックスシュガーなため、Promiseは理解する

非同期処理とは

  • 多くの場合時間がかかる処理で、裏で行われるもの
  • 通信処理とかディスクアクセス処理とか

プログラムが他の作業を待たずに次の処理を進めること

JavaScriptの非同期処理

JavaScriptの実行環境が、非同期処理についてはバックグラウンドで処理させて、次の処理に移れるような仕組みを持っている。
例えば、setTimeout関数は非同期処理なので、setTimeoutの処理終了をまたずに次の処理が実行される、など

JavaScript自体はシングルスレッドで動作する言語ですが、それを実行する環境(例: ブラウザやNode.js)は非同期処理をバックグラウンドでサポートする仕組みを提供しているはず

非同期処理に順序を付けたい場合

非同期処理関数のコールバック関数

  • 非同期処理の完了をコールバック関数で検知する仕組み
  • 非同期処理を順番に実行する場合、非同期処理にコールバック関数を渡していた。そして、コールバック関数の中に次の非同期処理とそのコールバックをネストする形で記述して順番通りに非同期処理を実行していた。コールバック関数を使わないと、非同期処理が終わった順番で実行されてしまう。
  • このコールバック関数の利用方法(コールバック関数の引数の数、型など)が各APIで違っていて、毎回調べる必要があった

しかし、非同期処理を重ねていくとネストがどんどん深くなってコードが見づらくなり、流れも追いにくくなります。これがコールバック地獄です。

Promise

Promise は「非同期で処理を走らせて、結果があとで手に入る」ってだけの仕組み。

  • Promiseを使った非同期処理の関数は、コールバック関数を引数に取るのではなくPromiseオブジェクトを返します。Promise は返された瞬間にもう実行されている

  • コールバック関数を使う場合は、API毎にコールバック関数をどのように渡すのか毎回調べる必要があったが、Promiseの使い方さえ覚えれば非同期処理を受け取れるようになった
    非同期処理を扱う使い方がそもそも便利になった↑

  • もちろん、thenをつなげることで簡潔に非同期処理を順番に連鎖させることができるようになった

Promise は、その非同期処理が 完了するまで待機するための仕組み です。具体的には、非同期処理の結果を 後で使うことができる ようにするためのオブジェクトです。

Promise は以下の3つの状態を持っています

  • Pending(保留中): 非同期処理がまだ終わっていない状態

  • Fulfilled(完了): 非同期処理が成功した状態。結果の値を持っている

  • Rejected(拒否): 非同期処理が失敗した状態。エラーメッセージなどの理由を持っている

Promiseをイメージする

Promiseは非同期処理そのものを表している

const p = new Promise<number>((resolve) => {
   // setTimeoutは非同期処理関数
   // ライブラリもここに非同期処理が記述されていて、その中でresolve()を利用しているはず
   setTimeout(() => {
      resolve(100);
  }, 3000);
});

p.then((num) => {
  console.log(num);
});
  • resolveは内部的に用意される関数で、非同期処理が終わったらresolveを呼び出されることを求められている。なのでここでは3秒後にresolve()している
  • resolve()が呼び出されると、Promiseが成功に解決される
  • 引数の関数は(resolve) =>{...} 即座に実行される
  • イメージ
    • 例えばfetchはPromiseを返すが、非同期処理が引数のURLの通信で、それは即座に実行されていて、通信が終わればresolve()を実行していて、Promiseのthenが呼べる状態になっているはず、、
function fetchDataFromAPI(url) {
  return new Promise((resolve, reject) => {
    fetch(url)
      .then((response) => {
        if (!response.ok) {
          reject('APIリクエストが失敗しました');  // ステータスコードが 200 以外の場合はエラー
        } else {
          return response.json();  // JSON データをパース
        }
      })
      .then((data) => {
        resolve(data);  // APIからのデータを成功として返す
      })
      .catch((error) => {
        reject(`エラー: ${error.message}`);  // エラーが発生した場合
      });
  });
}

fetchDataFromAPI('https://jsonplaceholder.typicode.com/posts/1')
  .then((result) => {
    console.log('データ取得成功:', result);  // 成功した場合の処理
  })
  .catch((error) => {
    console.error('データ取得失敗:', error);  // 失敗した場合の処理
  });

このpromiseを返すのがfetchとか

function fetch(url, options) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()

    xhr.open(options.method || 'GET', url)
    for (const key in options.headers) {
      xhr.setRequestHeader(key, options.headers[key])
    }

    xhr.onload = () => {
      const response = {
        ok: xhr.status >= 200 && xhr.status < 300,
        status: xhr.status,
        json: () => Promise.resolve(JSON.parse(xhr.responseText)),
        text: () => Promise.resolve(xhr.responseText),
      }
      resolve(response)
    }

    xhr.onerror = () => reject(new TypeError('Network request failed'))

    xhr.send(options.body)
  })
}
Promise<T>

将来 T が返ってくる非同期な値

さらに便利になったasync/await

  • 非同期処理をPromiseを意識しないで記述できるようになった。なので非同期処理を書く場合は大抵の場合async/awaitを記述すると思われる

  • async/awaitを使うと、より同期的な処理の書き方で非同期処理を順番に記述できるようになった

  • エラー処理もtyr-catch処理で記述することができる

  • asyncもawaitもPromiseを返すので、Promiseを理解していることが大事

  • awaitを使うと、asyncの関数が中断される、asyncの関数の次から実行される

function exampleFunction = async() => {
     // asyncの中でawaitを書くことで同期的に処理をかけるようになる
    console.log("Start of async function");

    const result = await new Promise((resolve) => {
        setTimeout(() => {
            resolve("Resolved after 2 seconds");
        }, 2000);
    });

    console.log(result);
    console.log("End of async function");
}

console.log("Before calling async function");
exampleFunction();
console.log("After calling async function");

表示

Before calling async function
Start of async function
After calling async function
Resolved after 2 seconds
End of async function
1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?