その他

【JavaScript】非同期処理とPromise / async・await について

その他
この記事は約6分で読めます。

同期処理とは

上から順番に行われる処理です。処理のタイミング、つまり時間軸が一定であり、これを「同期」と呼びます。

非同期処理とは

上から順番に行われない処理です。処理順を気にしません。時間軸が同期していないので「非同期」と呼びます。

同期処理の例

以下は同期処理の例です。

let a = 1;

console.log(b);
console.log(a);

let b = 2;

出力は以下のようになります。

1
Error: b is not defined.

変数 b を定義する前にログに出力しようとしているため、エラーになります。

非同期処理の例

以下は非同期処理の例です。先ほどと同じく、

  • 変数 a, b を出力します。
  • b の定義よりも上の行で、ログ出力を試みます。
  • ただし、非同期処理で b のログ出力を行います。
let a = 1;

let p = new Promise((res) => res()); // 非同期処理
p.then(() => console.log(b)); // 非同期処理の完了後、ログ出力

console.log(a);
let b = 2

出力は以下のようになります。

1
2

`console.log(b)` は b の定義よりも前に書かれていますが、今回はログが出力されました。Promiseについては、後述します。

別の時間軸で処理が行われる

  • 非同期処理は、同期処理とは別の時間軸で並行して処理されます。
  • 非同期処理のコールバックは、同期処理が全て終わった後で実行されます。

Promiseについて

同期処理とは別の時間軸で処理を行う仕組みとして、JavaScriptには Promise が用意されています。これを使用することで、時間のかかる処理をバックグラウンドで並行処理したりできるようになります。

非同期処理を行う

非同期で処理を行うためには、Promise を作成します。引数に非同期で行いたい処理を記述します。

以下の例では、`() => {}` という関数を引数に渡しています。

new Promise(() => {});

改行して、処理を追加します。

new Promise(() => {
  // バックグラウンドで行いたい処理...
});

これだけで、メインの同期処理と並行して、別の処理を行うことができます。

よくあるシチュエーション

例えば、WebAPIと通信する際に、fetchという関数がよく用いられます。この関数は Promise を作り、つまり非同期で通信処理が行われます。

const p = fetch("https://example.com/user/1");

console.log(p); // Promise{}

非同期処理の完了

非同期処理が完了した後で、何か別の処理を行いたい場合はよくあります。例えば、画像の圧縮をおこなった後でDBに保存する…等です。

非同期処理の完了を伝える手段として、コールバックは resolve, reject という2つの引数を受け取ることができます。

new Promise((resolve, reject) => {
  // バックグラウンドで行いたい処理...

  // エラーの場合
  if (error) {
    reject(new Error("エラーが発生しました"));
  }
  // 正常終了する場合
  resolve(image);
});
  • 正常に完了したら、resolve
  • エラーなど失敗なら、reject

をそれぞれ呼び出します。

非同期処理完了後の処理

非同期処理が完了した後の処理は、以下の2つの引数にそれぞれ渡します。

  • then: resolveされたら、thenに渡された関数が実行されます。
  • catch: rejectされたら、catchに渡された関数が実行されます。
const p1 = new Promise((res, rej) => {
  res("成功");
});

p1.then((a) => console.log(a)); // "成功"
const p2 = new Promise((res, rej) => {
  rej("失敗");
});

p1.catch((a) => console.log(a)); // "失敗"
const p3 = fetch("https://example.com/user/1");

p3
  .then((a) => console.log(a)) // 成功の場合こっちが実行される
  .catch((a) => console.log(a)); // 失敗の場合こっちが実行される

実際には、成功したり失敗したり結果はわからないので、2つ繋げて記述します。

async / await

async / await を使用すると、非同期処理を 同期処理として行うことができます。

const result = await fetch("https://example.com/user/1");

この場合、変数 result には本来 then のコールバックで受け取っていた値が代入されます。

エラーの処理がなくなりましたが、あった方が良いです。

const result = await fetch("https://example.com/user/1").catch((e) => {});

await しない例

() => {
  console.log(1);
  fetch("https://example.com/user/1").then((user) => console.log(user));
  console.log(2);
}

以下のように出力されます。

1
2
{user: "太郎"}

await する例

async () => {
  console.log(1);
  console.log(await fetch("https://example.com/user/1"));
  console.log(2);
}

以下のように順番に出力されます。

1
{user: "太郎"}
2

await は処理を一時停止する

await は 関数内の処理を停止します。同期処理をストップするということです。Promiseの完了まで同期処理が停止します。

つまり、時間のかかる処理をバックグラウンドで実行しておく…といったようなメリットは消えてしまいます。

async は Promiseを返す

async 関数 から戻り値を返すと、Promiseでラップされます。

const createP = async () => "aaa";

const p4 = new Promise((res) => res("aaa"));
const p5 = createP();

console.log(p4); // Promise{}
console.log(p5); // Promise{}