JavaScriptの非同期処理入門

JavaScript では Promise を用いて,非同期に処理することができます.

いつもサーバ上で動くプログラムだけ書いていたのでクライアント上で非同期処理することはありませんでしたが,すべてをメインスレッド上で動かすと大変なことになるので,Promise とか async/await を使っていきたいと思います.

個人的見解を含みます.誤りがあったらすみません.

Promise の出番

非同期通信するとき

通信には時間がかかるので,メインスレッド上で実行すると結果が返ってくるまで他の処理を保留することになります.これは大変使い勝手が悪いので,Web API を利用するときなどは非同期にします.

複数の処理を実行するとき

複数の関数を組み合わせて実行するとき,正しい順番で処理しなければ変数が undefined になったりします.それを防ぐためにコールバック関数を使うことができますが,処理が複雑になるにつれてネストが増えて判読性が皆無になります.

Promise を使って書くと,コールバック関数を使ったときよりもわかりやすく記述できるようです.async/await を使うと更に簡潔になります.

AJAX とは

JavaScript 上で XMLHttpRequest を使って非同期通信を実現するという概念や技術を指しています.

XMLHttpRequest という紛らわしい名前ですが,JSON なども普通に扱うので,Web API を利用するときに使われます.

Promise の使い方

new Promise(function(resolve, reject) { ... }); という構文です.Promise を呼ぶと関数を実行し,成功すれば resolve を,失敗すれば reject での処理内容を返します.また,return 文は無視されます.

使用例

MDN web docs より.

function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}

上のようにして Promise を使った関数を定義できます.

処理の順番は .then で制御します.

myAsyncFunction.then( ... );

また,複数の処理を扱う場合は Promise.all で制御します.

const promiseA = () => {
    return new Promise((resolve, reject) => {
        ...
        resolve( ... );
    });
};

const promiseB = () => {
    return new Promise((resolve, reject) => {
        ...
        resolve( ... );
    });
};

Promise.all([promiseA, promiseB])
.then((values) => {
    console.log(values[0]); // promiseA が返した値
    console.log(values[1]); // promiseB が返した値
});

すべて成功すればそれぞれの値が返ってきます.どこかの Promise で処理が失敗した場合は,その時点で失敗した理由が返ってきます.また,ここでは .all を使ったので返ってきた値は配列ですが,それが嫌であれば .spread を使います.

async/await の出番

Promise で書くとやばいとき.Promise で複雑な処理を書くと深くなるので,判読性皆無です.

async function 内で await すると,非同期処理が終わってから実行することができます.

const myAsync = async () => {
  const value = await promiseB;
  console.log(value);
};

myAsync();

例外処理

Promise では try/catch ではなく,Promise.prototype.catch を使うことができます.async/await でも内部は Promise なので同様です.

Promise.all([promiseA, promiseB])
  .then((values) => {
    console.log(values[0]); // promiseA が返した値
    console.log(values[1]); // promiseB が返した値
  })
  .catch((error) => {
    console.error(error);
  });
const myAsync = async () => {
  const value = await promiseB.catch(() => "");
  console.log(value);
};

myAsync();

ブラウザの対応状況

MDN web docs のブラウザ実装状況で確認したところ,Promise は ECMAScript 2015 (6th Edition, ECMA-262) で定義され,IE はさようならって感じです.

async function は ECMAScript 2017 (ECMA-262) で定義され,当然 IE は非対応です.また,比較的バージョンが新しいブラウザでないと動きません.iOS Safari であれば 10.1 以上なので,iOS 10 以上のようです.

後者は Node.js の対応は不明となっていますが,私の環境では恐らく動いてるので,8.10.0 では動きます.

最後に

Promise で書くより async/await のほうが簡潔なので好きですが,古いデバイスへの対応を考えると,うーん....