JavaScriptで非同期処理を行うには、コールバック関数やPromiseを使う方法があります。
それらに加え、ES7からはAsync/Awaitと呼ばれる非同期処理を同期処理的に書く手法が導入されました。

Promiseの知識が必要ですが、Async/Awaitを知っておくとより快適に記述することができます。それでは、Async/Awaitについて解説していきます。

Async/Await

Async/Awaitとは、Promiseによる非同期処理をより効率良く記述することができる構文です。

Async

asyncを関数の前に置くことで、asyncファンクションを生成することができます。
asyncファンクションは、常にresolveされたPromiseオブジェクトを返します。

通常のPromise構文と同様に、thenを利用して繋げていくことが可能です。

async function sample() {
  return 123;
}

sample().then(function(value) {
  console.log(value)
});
// 123

明示的にpromiseを返すと以下のように記述できます。内容は上記と全く同じもなので、Promise.resolveの記述は必須ではありません

async function sample() {
  return Promise.resolve(123);
}

sample().then(function(value) {
  console.log(value);
});
// 123

また、アロー関数で表現することも可能です。

const sample = async () => {
  return 123;
};

sample().then(value => console.log(value));
// 123

このようにasync関数を用いることで、new Promiseの記述がなくても、かんたんにPromiseオブジェクトを作成することができます。

Await

awaitは、asyncファンクション内のみで使用できる予約語です。
Promiseが結果を返すまで処理を待機させることが特徴的で、thenメソッドのような役割があります。

async function sample() {
  return 123;
}

async function sample_2() {
  const result = await sample(); // Promiseの結果が分かるまで待機
  console.log(result);
}

sample_2();
// 123

また、asyncではなく、通常のPromise構文で返されたPromiseオブジェクトを、async/awaitで繋げることもできます。

async function sample() {
  const promise = new Promise((resolve, reject) => {
    resolve(123);
  });

  const result = await promise; // promiseの結果が分かるまで待機
  console.log(result);
}

sample();
// 123

awaitはasyncファンクションの中のみでしか使えないことに注意が必要です。
通常の関数の中でawaitを使うとエラーになります。

function sample() {
  const promise = new Promise((resolve, reject) => {
    resolve(123);
  });

  const result = await promise();
  console.log(result);
}

sample();
// "Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules"

asyncはawaitがなくてもthenメソッドで繋いでいくことができますが、awaitはasyncファンクションの中でないと機能しないため、基本的にasync/awaitはセットであることを意識しておくと良いでしょう。

連続した非同期処理

Promiseチェーンになると、よりasync/awaitのメリットを感じることができます。
従来のPromise構文で処理を繋げて書く場合と、async/awaitによる処理を繋げていく場合で比較してみましょう。

以下は、それぞれ1秒後に処理を実行していく例です。

// Promiseの場合
const promiseFunc = value => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(value), 1000)
  });
};

promiseFunc(1)
  .then((num) => {
    console.log(num);
    return promiseFunc(2);
  })
  .then((num) => {
    console.log(num);
    return promiseFunc(3);
  })
  .then((num) => {
    console.log(num);
  })
// 1
// 2
// 3
// async/awaitの場合
const promiseFunc = value => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(value), 1000);
  });
};

const asyncFunc = async () => {
  console.log(await promiseFunc(1));
  console.log(await promiseFunc(2));
  console.log(await promiseFunc(3));
};

asyncFunc();
// 1
// 2
// 3

どちらも得られる結果は同じです。

単純な非同期処理であれば、従来のPromise構文でも問題ありませんが、コードが長くなると少し分かりにくい印象です。
一方、async/awaitの記述方では、thenメソッドよりもすっきりしたコードで表現できます。
それだけでなく、非同期処理を行っているのにも関わらず、同期処理のように上から順番に処理しているかのように記述できるため、内容を把握しやすい点もメリットです。

まとめ

今回は、async/awaitについて解説をしました。

// ポイント
* asyncは、常にresolveされたPromiseオブジェクトを返す
* awaitは、promiseが結果を返すまで処理を待機させる役割がある
* awaitは、asyncファンクションの中のみで有効

async/awaitの構文だけ見ると、Promiseよりも簡潔に書くことができますが、奥が深いのも事実です。
使いこなすためには、非同期処理の概念からPromiseの理解を深めていくことも大切です。

合わせて読みたい非同期処理シリーズ

第1回:非同期処理とコールバック関数
第2回:Promise -then・catch
第3回:Promise -finally・Promise.all
第4回:Async/Await(当記事)