JavaScriptのasync/awaitとPromiseの関係は?

皆さんこんにちは。

前回、JavaScriptのPromiseを用いた非同期処理の行い方を詳細に解説しました。
その際「Promiseは現在では若干古い書き方である」ということも解説しました。

本記事ではサンプルコードを用いて、「Promiseに代わる新しいasync/awaitの使い方」を初心者でも分かりやすいよう詳細に解説します。加えて、「Promiseとasync/awaitの利点欠点」まで言及しますので是非最後までご覧ください。

▼本記事を読んで分かること

  • JavaScriptのPromiseとは
  • JavaScriptのasync/awaitとは
  • JavaScriptのasync/awaitとPromiseの対応について
  • JavaScriptのasync/awaitやPromiseのメリット/デメリット

前回のおさらい:JavaScriptのPromiseとは?

JavaScriptにおける「Promise」(プロミス、約束事)とは、Promiseオブジェクトを用いて一定の手続きを踏むことで「非同期処理を実現するための仕組み」です(参考1)。

非同期処理とは何かや、はたまたPromiseの使い方といった内容は、前回の記事にて詳細に解説しているためここでは割愛します。

JavaScriptのasync/awaitとは?

JavaScriptの「async」(参考2)や「await」(参考3)は、Promiseを用いた非同期処理をベースに、より簡潔に記述できるよう『ES2016』より利用可能になった記述です。

asyncキーワードは非同期に値を取得するための関数定義に付与し、awaitキーワードは非同期処理による値取得を同期するために用います。なお、awaitを用いるためには、async指定された関数内から呼び出す必要があります。

サンプル1

function getX(){
  return 1;// 実際はなんらかの同期処理
}
async function getY(){
  // 実際はなんらかの非同期処理
  return new Promise(resolve=>resolve(3));
}
async function getZ(){
  // 実際はなんらかの非同期処理中にエラー発生
  throw "error";
}

async function main(){
  try {
    let x = getX();       console.log("x:" + x);
    let y = await getY(); console.log("y:" + y);
    let z = await getZ(); console.log("z:" + z);
  } catch(err) {
    console.log("err:" + err);
  }
}

実行結果1

x:1
y:2
err: error

このサンプルでは、「x, y, z」という値をそれぞれ同期処理や非同期処理で取得します。
しかし、「z」の値は取得中にエラーが発生します。

このようにasyncやawaitを用いた例外処理は、通常のtry~catchを用いて捕捉できます。

JavaScriptのasyncやawaitとPromiseの関係は?

先ほどのサンプル1で示したasync/awaitによるコードは、Promiseオブジェクトを使用したサンプル2と同義になります。この2つの対応を紐解くことで、より深く理解できることでしょう。

サンプル2

function getX() {
  return 1; // 実際はなんらかの同期処理
}

function getY() {
  // 実際はなんらかの非同期処理
  return new Promise(resolve => resolve(2));
}

function getZ() {
  // 実際はなんらかの非同期処理中にエラー発生
  return new Promise(function(resolve, reject) {
    reject("error");
  });
}

function main() {
  let x = getX();
  console.log("x:" + x);
  getY().then(
    y => {
      console.log("y:" + y);
      getZ().then(
        z => console.log("z:" + z)
      ).catch(
        err => console.log("err:" + err)
      )
    }
  );
}

このサンプルの対応から、

async指定された関数は、戻り値としてPromiseオブジェクトを返却しない場合、【「return」される値が「resolve」されたPromiseオブジェクトを返却するものとして解釈される】ことが分かります。

そして、async指定された関数からthrowにより例外が投げられた場合、その内容がPromiseで「reject」されたものとして解釈されることも分かります。またasync/awaitなら例外はtry~catchで捕捉できることも分かるでしょう。

JavaScriptのasnyc/awaitやPromiseのメリットとデメリット

それではasync/awaitや、Promiseのメリット・デメリットは何があるでしょうか。

async/awaitのメリットとPromiseのデメリット

まずasync/awaitのメリットとしては、非同期処理の同期を圧倒的に簡単に記述できることです。

先程のサンプルから改行を除いた字数は以下の通りです。

  • サンプル1:413字
  • サンプル2:474字

async/awaitを用いたサンプル1の方が同じ処理を「61」文字も短く記述できています。
これは言い換えれば、Promiseは冗長な記述が多いことを意味しています。

特にPromiseは同期すべき値が増えていくとメソッドチェーンのネストが問題になりかねません。この話は前回の記事で追及しているため省略しますが、ネストの問題を回避しようとするとサンプル3のようにmain関数を記述することになります。

サンプル3

function main() {
  let x = getX();
  console.log("x:" + x);
  let y, z;
  getY().then(r => {
    y = r;
    console.log("y:" + y);
    return getZ();
  }).then(r => {
    z = r;
    console.log("z:" + z)
  }).catch(err => console.log("err:" + err));
}

基本的にはネストの問題は非常に深刻ですので、Promiseを用いる場合サンプル3のように記述することが多くなります。

こうしてみると、async/awaitを用いた方がすっきりと記述できることが直感的に理解できることでしょう。

async/awaitのデメリットとPromiseのメリット

万能に見えるasync/awaitですが、もちろんデメリットもあります。

それはasyncを指定できるのが「functionキーワードの直前のみ」という点があげられます。これは言い換えれば、asyncキーワードはラムダ記法によって定義した関数に付与できないことを意味します。

サンプル4:ラムダ記法により定義した関数からPromiseを使用する例

$(() => {
  getPlayerHands().then(p => {
    getEnemyHands().then(e => {
      :
    }).catch(err => {
      :
    })
  })
});

サンプル5:ラムダ記法により定義した関数からasync/awaitを使用する例(実行時エラーが発生する)

$(() => {
  try {
    const p = await getPlayerHands();
    const e = await getEnemyHands();
      :
  } catch(err) {
      :
  }
});

サンプル4のように、Promiseなら「$(() =>{~});」によるラムダ記法でも利用できます。

対してサンプル5は上手くいきません。なぜなら「await」を利用するためには、asyncを指定した関数でなければならないからです。asyncはラムダ記法により定義した関数には指定できないため、awaitが許可されず、実行時エラーが発生してしまうのです。

最後に

前回と今回の2回に渡ってJavaScriptによる非同期処理を実現するためのPromiseとasync/awaitについて解説しました。この記事は初心者向けとして必要十分な記述を行っていますが、中級者や上級者になるにはさらに踏み込んだ理解が必要です。

Promiseのthenにおいてネストの深さで問題があったように、async/awaitには非同期処理の効率が問題になる局面が出てきます。また同期処理によっては「yield」を用いた処理の一時停止なども必要でしょう。

ここら辺は要望があれば掲載する機会があるかもしれませんが、ひとまず今回はこの辺で失礼します。今後みなさんが非同期処理を記述する際に、本記事や前回の記事が参考になれば幸いです。




エンジニア募集中

株式会社キャパでは中途エンジニアの募集をしています。
私たちと一緒に働きませんか?
募集概要について詳しくは


◆参考文献

参考1:Promise
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

参考2:非同期関数
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function

参考3:await
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await

関連記事一覧