FJCT_ニフクラ mobile backend(mBaaS)お役立ちブログ

スマホアプリ開発にニフクラ mobile backend(mBaaS)。アプリ開発に役立つ情報をおとどけ!

JavaScriptにおけるasync/awaitの書き方

先日、スクリプト機能がバージョンアップしてNode.jsが8.11.3を選択できるようになりました。この8系以降の特徴として、async/awaitに対応したという点があります。async/awaitはJavaScriptの非同期処理を同期処理のように書ける記法になります。コールバックやPromiseで苦しんだ経験のある方はぜひasync/awaitを使ってみてください。

今回はこのasync/awaitの書き方を解説します。

関数名の頭にasyncを付ける

まずasync/awaitに対応させるために、関数の頭にasyncと書かなければなりません。これは関数、クラスのメソッドでも同じです。

async function Hello() {
  // 非同期処理
}

const Hello = async () => {
  // 非同期処理
}

class Hello {
  async message() {
    // 非同期処理
  }
}

そして、そのasyncと書かれた処理内ではawaitで非同期処理を扱えるようになります。具体的にはPromise処理が対象になります。

const Hello = () => {
  return new Promise((res, rej) => {
    return res();
  })
}

const main = async () => {
  await Hello();
}
main();

なお、Promise処理における失敗時のrejectを呼ぶと、async側ではエラー処理になります。そのため、全体をtry/catchで囲む必要があります。

const main = async () => {
  try {
    await Hello();
  } catch (err) {
    // エラー処理
  }
}

mainのような変数を作るのは面倒なので、無名関数で囲むこともできます。

(async () => {
  try {
    await Hello();
  } catch (err) {
    // エラー処理
  }
})();

コールバックでは使えません

従来のコールバック関数の場合、await/asyncでは使えませんので、Promiseで囲む必要があります。

const fs = require('fs');
const readFile = (path) => {
  return new Promise((res, rej) => {
    fs.readFile(path, 'utf-8', (err, data) => {
      return err ? rej(err) : res(data);
    });
  })
}

(async () => {
  try {
    const content = await readFile('/tmp/file.txt');
  } catch (err) {
    // エラー処理
  }
})();

さらにreadFileのような関数を定義するのが面倒な場合は promisify が使えます。これは標準で用意されているユーティリティです。

const fs = require('fs');
const { promisify } = require('util');

(async () => {
  try {
    const content = await promisify(fs.readFile)('/tmp/file.txt', 'utf-8');
  } catch (err) {
    // エラー処理
  }
})();

ループ処理に注意しましょう

async/awaitでのループ処理は推奨されていません。この場合は一旦配列にまとめるのが良いようです。以下は推奨されない方法です。

(async () => {
  try {
    const ary = [];
    for (let i = 0; i < 100; i += 1) {
      ary.push(await promisify(fs.readFile)(`/tmp/file.${i}.txt`, 'utf-8'));
    }
  } catch (err) {
    // エラー処理
  }
})();

これを以下のようにします。ただし、個別の処理結果を受けて次の処理に繋ぐ場合など、ループ処理内でawaitを使わざるをえないケースもあるでしょう。

(async () => {
  try {
    const promises = [];
    for (let i = 0; i < 100; i += 1) {
      promises.push(promisify(fs.readFile)(`/tmp/file.${i}.txt`, 'utf-8'));
    }
    // ここでまとめて処理
    const ary = await Promise.all(promises);
  } catch (err) {
    // エラー処理
  }
})();

まとめ

async/awaitを使いこなすことで、Promiseでは面倒だった書き方がすっきりします。asyncを忘れるとエラーになったり、awaitを忘れてPromiseで返ってきてしまうこともありますが、それは書いている内に慣れることでしょう。ぜひ積極的に使ってみてください。

導入相談会

中津川 篤司

中津川 篤司

NCMBエヴァンジェリスト。プログラマ、エンジニアとしていくつかの企業で働き、28歳のときに独立。 2004年、まだ情報が少なかったオープンソースソフトの技術ブログ「MOONGIFT」を開設し、毎日情報を発信している。2013年に法人化、ビジネスとエンジニアを結ぶDXエージェンシー「DevRel」活動をスタート。