async と awaitに関して

2022/02/28に公開

どうもフロントエンドエンジニアのoreoです。前回の「Promiseに関して」の続きとして、asyncawaitと例外処理に関して記載します!

1 async と awaitとは?

asyncawaitを利用すると、Promiseをさらに直感的に記述することができます。

  • async
    • asyncを使うと、Promiseオブジェクトを返却する関数を宣言できます。
  • await
    • Promiseオブジェクトをreturnする関数の前にawaitをつけると、Promiseresolveまたはrejectされるまで待機することができます。待機中は、await以降の処理は中断され、待機が終わると以降の処理が再開されます。
    • awaitは、async内で使用します。

2 基本的な使い方

2-1 Promise構文の復習

Promiseに関して」で、記載したPromiseチェーンは以下ex1~ex2のようなものでした。

関数appleは、Promiseを用いて、非同期処理が実装されています。

【ex1】

function apple(num) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log(`りんごは${num}`);
      num += 1;
      resolve(num);
    }, 1000);
  });
}

Promiseを使った場合には、.thenメソッドで、非同期処理の後に実行したい処理を繋げることができました。

【ex2】

/**
 * 以下が1秒毎に出力
 * りんごは1個
 * りんごは2個
 * りんごは3個
 * 終了!  ※これは「りんごは3個」と同時に出力
 */
apple(1)
  .then(function (num) {
    return apple(num);
  })
  .then(function (num) {
    return apple(num);
  })
  .then(function (num) {
    apple(num);
    console.log("終了!")
  })

2-2 asyncawaitを使う

このex2の処理をasyncawaitを使って、簡略化するとex3のようになります。

まず、asyncを使って関数appleAsyncを定義し、その中でawaitを使って関数appleに引数1を渡します。await apple(1)では、関数appleで、resolveされた値が返されます。その値を変数valに代入し、続いてawait apple(val)valに再代入していくことで、ex2と同じ挙動をとることができます。

ここで、awaitをつけた処理は、resolveが呼ばれるまで(非同期処理が終わるまで)、待機状態となり、それ以降の処理は中断されます。全ての非同期処理が終了した後に、console.log("終了!")は実行されます。

【ex3】

/**
 * 以下が1秒毎に出力
 * りんごは1個
 * りんごは2個
 * りんごは3個
 * 終了!  ※これは「りんごは3個」と同時に出力
 */

async function appleAsync(){
  let val = await apple(1)  //関数appleでresolveされた値(ここでは2)がvalに代入される
  val = await apple(val)   //3がvalに代入される
  val = await apple(val)   //4がvalに代入される
 console.log("終了!")
}
appleAsync()

ex3での関数appleAsync()は、Promiseを返すため、関数appleAsync()を実行した後に.thenメソッドを繋げることができます。

【ex4】

/**
 * 以下が出力。
 * りんごは1個
 * appleAsyncから2が取得できます
 */

async function appleAsync(){
  let val = await apple(1)  //関数appleでresolveされた値(ここでは2)がvalに代入される
 return val
}

appleAsync()
  .then(function (val) {
    console.log(`appleAsyncから${val}が取得できます`);
  })

また、関数appleAsync()内でthrow new Error()を呼ぶと、.catchに繋げることも可能です。

【ex5】


async function appleAsync() {
  let val = await apple(1);
  throw new Error();
  return val;
}

appleAsync()
  .then(function (val) {
    console.log(`appleAsyncから${val}が取得できます`);
  })
  .catch(function (e) {
    console.error(e);   //Error !!!
  });

3 使いどころ

fetchなどでのデータ取得やエラーハンドリングでよく使いますね。

3-1 fetchでデータを取得する

fetchメソッドを使えば、HTTPリクエストを発行して、サーバーなどからデータを取得できます。fetchメソッドは、Promiseオブジェクトを返却します。

ここで下記のようなfruits.jsonからfetchメソッドを用いてデータを取得したいと思います。

【fruits.jsonファイル】

[
  {
    "name": "Apple",
    "price": 150
  },
  {
    "name": "Orange",
    "price": 100
  }
]

fetchメソッドはPromiseオブジェクトを返すので、.thenを使ってfetchメソッドでresolveされた値を取得できます(ex6)。

【ex6】

fetch("fruits.json").then((data) => {
  console.log(data)
})

resolveされた値を見てみると、HTTPステータスコードなどが格納されたオブジェクトが確認できます。

プロトタイプを見てみると、さまざまなメソッドが準備されています。

それらの中で、.jsonメソッドを使うとJSONデータを取得できます(ex7)。

【ex7】

/**
 * 以下が出力
 * [{"name": "Apple","price": 150},{"name": "Orange","price": 100}]
 */
fetch("fruits.json").then((data) => {
  return data.json()     //jsonを取得して、returnする
}).then((json)=>{
  console.log(json)
});

これらをasyncawaitを使って書き換えると、【ex8】のようになります。

【ex8】

/**
 * 以下が出力
 * [{"name": "Apple","price": 150},{"name": "Orange","price": 100}]
 */
async function fetchFruits() {
  const data = await fetch("fruits.json");
  const fruits = await data.json();    //.jsonは、Promiseをreturnするのでawaitする
  console.log(fruits);
}
fetchFruits();

3-2 例外処理(エラーハンドリング)

例外処理とはエラーが発生した場合に行う処理のことで、【ex9】のようにtrycatchfinallyを用いて実装します。

tryブロックの中でエラーが発生した場合に、それ以降の処理は行わずcatchブロックに移行して、エラーハンドリングを行ないます。

【ex9】

try {
  //tryの処理でエラーを投げるとそれ以降の処理は行わずcatchに移行する
  throw new Error();
} catch (e) {
  // エラー時の処理(エラーハンドリング)を行う
  console.error(e)
} finally {
  //try、catchに関わらず行う終了処理を記載
}

3-1で作成したfruits.jsonをfetchするような処理で例外処理を実装するとex10のように記載できます。ここでは、データを取得する関数としてfetchFruitsを定義し、fetchが失敗した場合は、throw new Error("データ取得失敗!");、jsonが空だと、throw new Error("データがないよ!");を投げます。

また、取得したデータを出力する関数として、outputFruitsを定義します。tryブロックの中でfetchFruits関数を実行し、throw new Error()が実行されると、そのエラーをcatchブロックのconsole.error(e);でエラー内容を確認できます。

【ex10】

//fruitsを取得する関数
async function fetchFruits() {
  const data = await fetch("fruits.json");
  //data.okには、fetchが成功したかどうかのbooleanが格納されている
  if (data.ok) {
    const fruits = await data.json();
    //jsonが空だとエラー投げる
    if (!fruits.length) {
      throw new Error("データがないよ!");
    }
    return fruits;
  }else{
    throw new Error("データ取得失敗!");
  }
}

//fruitsを出力する関数
async function outputFruits() {
  try {
    const fruits = await fetchFruits();
    console.log(fruits);
  } catch (e) {
    console.error(e);
  } finally {
    console.log("終了です");
  }
}
outputFruits();

最後に

前回のPromiseに続いて、asyncawaitや例外処理を整理しました。ここら辺は押さえておきたい内容ですね。

参考

Async/await

async/await 入門(JavaScript) - Qiita

Discussion