TypeScriptの非同期処理を解説

TypeScript用アイコン TypeScript
TypeScript eye catch
この記事は約13分で読めます。

この記事は、以下の方を対象に書いています。

  • 非同期処理の基礎から知りたい
  • 非同期処理を使ったサンプルコードを知りたい

非同期処理とは?

非同期処理とは、一連の処理が終了するまで待たずに、他の処理を実行するプログラミング手法のことです。

例えば、画面の描画をしている最中に、サーバー側からDBのデータを持ってくる等が非同期処理にあたります。

このように、必ずしも処理が終わるまで待たなくても、同時並行で別の処理をさせることで、

パフォーマンスが向上します。これが非同期処理のメリットだと言えます。

ただし、処理が複雑になり、データの流れが追いづらくなるというデメリットもあります。

TypeScriptを使用する意味

非同期処理の機能は、JavaScriptも持っています。

非同期処理のデメリットを緩和できるものが、TypeScriptです。

TypeScriptは、JavaScriptの上位互換である言語のため、当然、非同期処理の機能を持ちます。

プラスで、型注釈などの便利な機能の恩恵で、非同期処理のデメリットを緩和できるという訳です。

これがTypeScriptを使用する意味です。

TypeScriptの恩恵

TypeScriptを使うことで得られる恩恵は、たくさんあります。

恩恵説明
型安全型注釈を利用することで、型の不一致がある場合にコンパイルエラーになります。
そのため、想定外のバグを事前に検知できる可能性が高くなります。
補完機能型情報が明確な場合、IDE側で型の推測が可能になり、予測変換の幅が広がります。
理解度の向上型注釈を利用することで、その関数が何者か判別しやすくなります。
よって、コードの理解度が速くなります。
TypeScriptの恩恵一覧

Promise

非同期処理の基本的な概念の一つに、Promise(プロミス)があります。

Promiseは、非同期操作の結果を表すオブジェクトです。

そして、処理の終わりに結果を返すことを約束しています。

この約束は、処理が成功か失敗に関わらず、結果を返します。

Promiseは、以下の3つの状態を持つことができます。

状態説明
Pending(未決)処理が進行中で、結果がまだ返ってきていない状態
Fulfilled(履行)処理が成功し、結果を利用できる状態
Rejected(拒否)処理が失敗しエラーが発生している状態
Promiseが持つ状態一覧

Promiseのサンプルコード

Promiseは、then()メソッドを持っており、

非同期処理の結果を受け取るためのコールバック関数を登録するために使用します。

実際に使用してみます。

// 非同期処理を行う関数
function fetchData() {
  return new Promise((resolve, reject) => {
    // ここで非同期処理を行う
    // 処理が成功したら resolve を呼び出し、失敗したら reject を呼び出す
  })
}

// fetchDataを呼び出す
fetchData()
  .then(data => {
    // 非同期処理が成功した場合に実行される
    console.log(data)
  })
  .catch(error => {
    // 非同期操作が失敗した場合に実行される
    console.error("エラー検知:", error)
  })

このように、then()メソッドを用いて、

受け取ったデータの処理や、エラーが発生した際の処理を分岐させることが可能です。

次に、4行目と5行目の処理を具体的に書いたサンプルコードを紹介します。

下記4行目のfetch関数は、Promiseを返す組み込み関数です。

fetch関数を使用することで、指定したURLからのデータを受け取ることできます。

そのデータにthen()メソッドを使ってデータにアクセスします。

function fetchData(url) {
    return new Promise((resolve, reject) => {
        fetch(url)
            .then(response => {
                if (!response.ok) {
                    // サーバからエラーレスポンスが返された場合
                    // (404などの200番台以外のエラーコード)
                    throw new Error('Network response was not ok')
                }
                // レスポンスをJSONとしてパース(解析)する
                return response.json()
            })
            .then(data => {
                // データの取得に成功したので、resolveを呼び出す
                resolve(data)
            })
            .catch(error => {
                // エラーが発生したので、rejectを呼び出す
                reject(error)
            })
    });
}

// 仮のURLを作成
const url = 'https://example.com/sample-data'
fetchData(url)
    .then(data => console.log(data))  // 成功時の処理
    .catch(error => console.error(error));  // 失敗時の処理

response.okって何?

まず、responseは、非同期処理が成功したときに受け取れる値の名前です。

つまり、なんでもいいです!(めんどくさいので、resとかにしたりします)

そして、okは、responseオブジェクトのプロパティです。(これは名前変えられない)

戻り値は、boolean型になります。

つまり、サーバーからのステータスコードが200番台であればtrueを返します。

逆にそれ以外(404や500など)なら、falseを返します。

resolveとrejectって何?

resolveとrejectは、プログラマーが勝手につけた名前であり特に意味はありません。

サンプルコードの2行目で定義したものが、resolveとrejectだったというだけです。

ですので、onSuccessとonFailureでも構いません。

本当に大切なのは、名前ではなく、何をしているかです。

サンプルコードの2行目で定義したresolveは、Promiseが成功したことを意味します。

つまり、通信が上手くできたから、APIから返却された値を返しているという意味になります。

サンプルコードでは、JSONとしてパースされた値を成功した保証書として返却しています。

次に、rejectですが、これはPromiseが失敗したことを意味します。

resolveの逆で、失敗した保証書としてエラー情報を返却しています。

このエラー情報には、様々なデータが含まれており、

エラーの把握や解決に役立ちます。

リクエストとしてパラメータを渡す場合は?

まず、サーバーへのリクエストは、サンプルコードのfetch(url)で送信しています。

fetch(url)

fetchはPromiseを返し、そのPromiseは非同期処理が完了すると解決します。

この場合はGET送信になります。

そこで、リクエストにパラメータ(情報)を付与した状態で渡したい場合は、

以下のように修正します。

GET送信の場合

GET送信の場合は、searchParamsプロパティを使用して値を付与します。

注意点としては変数urlを定義する際にnewをしないと、searchParamsは使用できません。

url = new URL('https://example.com/sample-data') // 新たにURLオブジェクトを生成
url.searchParams.append('param1', 'value1')
url.searchParams.append('param2', 'value2')

console.log(url.toString()) // https://example.com/sample-data?param1=value1&param2=value2

// fetchでレスポンスの受け取り
fetch(url)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error))

POST送信の場合

POST送信の場合は、オブジェクトを作成してリクエストボディにパラメータを渡します。

const url = 'https://example.com/data'
const data = {
  param1: 'value1',
  param2: 'value2'
};

fetch(url, {
  method: 'POST', // HTTPメソッドをPOSTに指定
  headers: {
    'Content-Type': 'application/json', // コンテンツタイプをJSONに指定
  },
  body: JSON.stringify(data), // ここでデータをJSON形式の文字列に変換
})
.then(response => response.json()) // レスポンスを解析し、次のthenで受け取れる形に変換
.then(data => console.log(data)) // 成功した場合に、dataにレスポンスが入る
.catch(error => console.error(error)) // 失敗した場合に、エラーハンドリングをする

asyncとawait

次は、asyncとawaitについて解説します。

この2つは、ES2017で導入され、

現在のJavaScriptやTypeScriptでは一般的に使用されています。

asyncとは?

asyncキーワードを関数の前につけると、その関数は非同期関数として認識されます。

また、return文がある場合、その値は自動的にPromise.resolveにラップされます。

async function asyncFunc() {
  return "Hello, World!"
}

asyncFunc().then(message => console.log(message))
// Hello, World!

awaitとは?

awaitは、非同期関数の中でのみ使用できます。

下記のサンプルコードでは、fetchによって返されるPromiseを待ってから、

非同期処理を行うという意味になります。

下記のサンプルコードでは、通信に成功してJSONにパースされた値が、

返却され、asyncFunc()で非同期関数を呼び出した後、then()メソッドのdataという

変数で値を取得することができます。

async function asyncFunc() {
  const response = await fetch("https://example.com/sample-data")
  const data = await response.json()
  return data
}

asyncFunc().then(data => console.log(data))

また、3行目でawaitを使っているので、7行目の最後にcatchを付けて、

そこでエラーハンドリングをすることも出来ます。

エラーハンドリング

asyncとawaitを使ったエラーハンドリングのサンプルコードを紹介します。

awaitを使用することで、Promiseが拒否されると拒否結果を返します。

そのため、try-catch文を使用してエラーハンドリングをすることが可能です。

async function asyncFunc() {
  try {
    const response = await fetch("https://example.com/data");
    const data = await response.json()
    // 成功した時にdataを返却
    return data
  } catch (error) {
    console.error(error)
    // 拒否された時にnullを返却
    return null
  }
}

asyncFunc().then(data => {
  if (data !== null) {
    console.log(data)
  }
})

今回は、サンプルコードなので、結果をコンソールに表示しているだけですが、

自由に拡張することができます。

例えば、利用者がエラーに気づけるように、アラートを表示したりすることも可能です。

TypeScriptで非同期処理

最後に、非同期処理のサンプルコードをTypeScriptバージョンで書いてみます。

TypeScriptはJavaScriptの上位互換ですので、

Promiseやasyncやawaitの概念はそのまま使えます。

変化点としては、型注釈を用いることで安全性が向上する点です。

まずは、基本的な宣言からしてみます。

戻り値は、Promise<string>です。

async function fetchData(): Promise<string> {
  let response = await fetch('https://example.com/sample-data')
  let data = await response.text()
  return data
}

上記の非同期関数を呼び出して、結果をコンソールに表示するためには、下記のコードを追加します。

fetchData().then(console.log)

エラーハンドリングをする場合は、非同期関数を下記のように書き換えます。

async function fetchData(): Promise<string> {
  try {
    let response = await fetch('https://example.com/sample-data')
    let data = await response.text()
    return data
  } catch (error: any) {
    console.error(error)
    throw error  // エラーを再スロー
  }
}

エラーがあった場合に、catchされるので、

catchのスコープ内に、どのような対応をするかプログラマーが設定することができます。

まとめ

非同期処理は、一連の処理の終了を待たずに、他の処理をするプログラミング手法の1つです。

非同期処理をするためには、Promiseやasync/awaitを使って非同期関数を作ります。

TypeScriptを使用することで、非同期処理の安全性が向上したり、可読性が向上します。

かなり複雑な概念ですので、慣れるまでに時間がかかりますが、

慣れてしまえば便利すぎて手放せなくなります!

ここまで、読んでいただき、ありがとうございます。

コメント

タイトルとURLをコピーしました