最終更新日

JavaScript 非同期処理メモ

前提

JavaScript の関数は「同期」処理される。処理が終わらないと次の処理に行けない。

ex. alert 関数でダイアログ出すとその場所でイベントから処理から全部止まる。

立脚すると、いつくるか分からない操作系のイベントとかタイマーとか、いつ終わるか分からない通信系の関数を、この原則で普通にブラウザーに定義すると、イベントくるまで全部待ちになってブラウザーまったく動かんってなってしまうので、この手の関数は「コールバック関数」を仕掛ける仕組みで実装された。

コールバック関数の設定だけなのでプログラムはさらっと「非同期」に下に抜けて、ブラウザーはイベントの発生を監視だけしとく。

// ex. addEventListener
target.addEventListener('onClick', function(e) {
  // コールバック関数
});

かくして、ブラウザーはイベントが発生すると、処理中の関数と関数の呼び出しの「間」にコールバック関数を挟んで呼び出してくれる。のでいい感じに処理が進む。なんとなく見た目は同時に動いているように見えますが、全部関数と関数の隙間にコールバック関数が挟まって呼ばれています(並行処理)

コールバックの書き方

さて、このようなコールバック関数の定義は該当関数の引数や戻りのオブジェクトのメソッドで行うですが、ブラウザー根っこの標準関数はともかく、それを使うライブラリー群は、ライブラリーによりけり、コールバック引数指定やらメソッド名やら書き方がまちまちで悲しい事態になってしまった。

// jQuery ajax (done と fail らしい)
$.ajax('/example', { })
  .done(function(data) {
    // 次の処理
  })
  .fail(function(data) {
    console.log(data);
  });

Promise の登場

いろんな書き方が乱立してきたので、みんなが使えるキレイなインターフェースを誰かが決めればいいんじゃないってことで、JavaScript に Promise オブジェクトが仕様として装備された。

これを知った多くのライブラリー群たちは Promise に対応して(要は関数の返値が全部 Promise になった)、コールバック系の処理がどれでも同じ Promise が持つ .then() .catch() という決まったインターフェースでプログラムがかけるようになった。嬉しい。

// axios の例
axios.get('/example')
  .then(function (response) {
    // 次の処理
  })
  .catch(function (error) {
    console.log(error);
  })

// fetch の例
fetch('/example')
  .then(function(response) {
    // 次の処理
  })
  .catch(function(error) {
    console.log(error);
  });

// jQuery 3 の例
$.ajax('/example')
  .then(function(response) {
    // 次の処理
  })
  .catch(function(error) {
    console.log(error);
  })

でも、これでもまだなんとなくダサい?。コールバックとして書くのが面倒かも?。

async/await の登場

ということで、JavaScript に async/await 構文が追加され Promise のインターフェースが見た目として消すことができるようになりました。

await

Promise 対応ライブラリーの呼び出し側としては try {} catch{}await でプログラムが下に下にまっすぐかけるようになりました。

// axios の例
try {
  const response1 = await axios.get('/example1');
  // 次の処理
  const response2 = await axios.get('/example2');
  // 次の処理
} catch(error) {
  console.log(error);
}

// fetch の例
try {
  const response1 = await fetch('/example1');
  // 次の処理
  const response2 = await fetch('/example2');
  // 次の処理
} catch(error) {
  console.log(error);
}

// jQuery 3 の例
try {
  const response1 = await $.ajax('/example1');
  // 次の処理
  const response2 = await $.ajax('/example2');
  // 次の処理
} catch(error) {
  console.log(error);
}

見た目 await のところでじっと待ってるように見えますが、内部的には Promise のコールバックに書き換えられているのでこの関数内での待ちで他の処理が止まったりしません。すっきり、まっすぐ、かっこいい。

try {} catch {} も、うざければ次のようにもかけます。

async関数においてtry/catchではなくawait/catchパターンを活用する

async

asyncawait される関数側が return new Promise(...) resolve() reject() と書くのが面倒なので代わりに、

// async なし版
function example() {
  return new Promise((resolve, reject) => {
    try {
      // ここで非同期関数を呼ぶ
      // resolve は .then() を呼ぶ
      resolve('success');
    } catch(e) {
      // reject は .catch() を呼ぶ
      reject(err);
    }
  });
}

// async 版
async function example() {
  try {
    // ここで非同期関数を呼ぶ
    // await に値が return される。
    return 'success';
  } catch(e) {
    // await に例外が throw される。
    throw e
  }
}

async キーワードがいい感じに new Promise() 形式に書き換えてくれる感じになります。この関数からの戻値は見た目の return と異なり Promise 型です。

async/awaitPromise のシンタックスシュガーみたいなもんですね。new Promise.then() catch() を使わずに async/await でキレイに Promise を記述できます。