kei0425tan’s blog

技術的なことを主に

mochaでasync/awaitが使えるようになってました!

node v7.0からasync/awaitが実装されていましたが、起動オプションに--harmony-async-awaitの指定が必要でした。

しかし、v7.6からJavaScriptエンジンのV8がバージョン5.5に上がったため、起動オプションの指定が不要になりました。

その前に、nodeでの非同期処理の扱いについて簡単に解説

コールバック時代

元々ノンブロッキングでイベントドリブンで記述するのがnodeの特徴のため、読み込みイベントにコールバックを登録して処理を記述します。
どこに処理を記述するのかわかりにくいですね。
ただし、chunk形式のため、非常に大きな入力でも対応できるのがメリットです。

コールバック版標準入力読み込み

Promise時代

コールバックを利用すると、ネストがどんどん深くなってしまう欠点がありました。
(複数ファイルを読み込むだけで、単純にそのファイル数だけネストしてしまったりなど)
まあ、無名関数にせずに全部通常の関数化して記述すればネストについては何とかなるのですが、手軽に記述できるとは言い難いです。

そこで、Promiseという仕組みを偉い人が考えました。
(元はデザインパターンのひとつのFutureだそうです。)
future - Wikipedia

ようは、コールバックの中にコールバックを記述するのではなく、チェインして連続して記述できる仕組みです。
(めっちゃ大雑把だけどゆるしてね)

Promise版標準入力読み込み

Promise化の方法

関数の中身を以下で記述します。

function hoge() {
  return new Promise((resolve, reject) => {
    hogehoge(param, (err, data) {
      if (err) {
        // 失敗した場合
        reject(err);
      }
      else {
        // 成功した場合
        resolve(data);
      }
    }
  });
}

resolveは必須です。rejectはなくてもかまいません。

async/await時代

Promiseを経て、コールバック地獄からは免れるようになったのですが、まだ見た目が慣れないことが多いです。
さらに、無名関数が多数書かれることによる変数のスコープの問題や、例外の問題などもあります。
そこで、async/awaitを使うことにより、通常のプログラムと同じく上から下へ記述できるようになり、コールバック関数も不要になり、ブロッキングもされないようになりました。

async/await版標準入力読み込み
paiza.ioのnodeのバージョンがv6のため、async/awaitに未対応なので、gistです。

async/await 標準入力読み込み

async/await化の方法

まず、Promise化を行います。
その後、呼び出し側の関数定義に、asyncを付けて、呼び出す関数は結果を待つ場合には、awaitをつけます。
非同期で呼び出したい場合には、awaitを付けなければ従来通り非同期になります。

ユニットテストではどうする?

そんなわけで、async/awaitを利用すると非常に記述しやすい&読みやすくなります。
でも、ユニットテストを書くときはどうするのでしょう??

答えは簡単。普通に無名関数を記述する箇所をasync function() にすれば大丈夫でした。
rejectされた場合もちゃんと例外が発生します。
ただし、assert.throwsはasyncつけてもうまく動作しませんでした。
仕方がないので、try/catchで検査しましょう。


mocha async/await テスト

結果は以下。
case2は例外が発生したケース
case3はassert.throwsを使って補足できなかったケース
なので、失敗します。

  test
    ✓ case1
    1) case2
    2) case3
(node:11613) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: ENOENT: no such file or directory, open './test/nofile'
(node:11613) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
    ✓ case4


  2 passing (24ms)
  2 failing

  1) test case2:
     Error: ENOENT: no such file or directory, open './test/nofile'
  

  2) test case3:
     AssertionError: Missing expected exception (Error)..
      at _throws (assert.js:345:5)
      at Function.throws (assert.js:369:3)
      at Context.<anonymous> (test/await-test.js:23:16)