🦁

javaScript非同期処理まとめ

2022/09/08に公開約5,600字

javaScript 非同期処理まとめ

javaScript の非同期処理は、callback, promise, await-async を使用した
三種類の方法がある。それぞれ確認していこう。

callback

callback 関数を使用することで非同期処理を実現できます
しかし、モダン javaScript ではあまり使わないのでさらっと流します

では試しに setTimeout で 1 秒後に処理を行う wait 関数を定義しましょう
num を表示した後に callback 関数を実行する関数になっています

index.js
const wait = (callback, num) => {
  setTimeout(() => {
    console.log(num);
    callback(num);
  }, 1000);
};
  //実行
wait((num) => {
  console.log("test");
}, 0);

実行結果は以下になります。0 が表示された後に test と表示されていますね。

ここから、wait の実行が終わったら違う処理をしたい場合を考えましょう
今回は wait を入れ子にすることで実現します

index.js
wait((num) => {
  num++;
  wait((num) => {
    num++;
    wait((num) => {
      num++;
      console.log("test");
    }, num);
  }, num);
}, 0);

実行結果
1 秒ごとに0,1,2,と表示の後 test と表示する

これで非同期処理が実現できてる!やったー!

…いや、入れ子構造は見づらいわ!!!

というわけでこのめんどくささの解決をしましょう

Promise

先ほどの wait を Promise を使って書き換えます

index.js
const wait = (num) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(num);
      resolve(num);
    }, 1000);
  });
};

最初見た時「なんだこれ」と思いましたが、めちゃくちゃよく出来てるメソッドですよね。
ここで return で Promise クラスを返しているのはわかると思います。
問題は引数の resolve と reject です。
とりあえず resolve を見ていきましょう。

resolve とは?

Promise はメソッドチェーンでつなげることが出来るのですが、
その戻り値が resolve で設定した値になります
そしてメソッドチェーンでつなげる方法は、then メソッドでつないでいきます

index.js
wait(0).then((num) => {
    num++;
    // returnでPromiseを返すとチェーンが継続し非同期処理ができる
    return wait(num);
  })

上記のようにすることで、wait(0)の実行後に then メソッド以降の処理を行えます
さらに、return で Promise を返すことで、さらにメソッドチェーンをつなげることができます
(この場合、wait の戻り値は Promise になっている)

では return で Promise を戻さなかった場合は?
もちろん非同期処理が崩れる

index.js
wait(0).then((num) => {
    num++;
    return wait(num);
  })
  .then((num) => {
    num++;
    // 戻り値をPromiseにしない
    wait(num);
    // チェーンが切れて非同期処理が出来なくなる
    return num;
  })
  .then((num) => {
    num++;
    //2と3は同時に出力される
    wait(num);
    return num;
  });

2つめの then メソッドの中で、return で Promise を戻さなかったため、
2,3 は同時に出力されるようになる

reject は?

reject は何に使うかというと、エラーが起きた時の処理に使用します

index.js
const wait = (num) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(num);
      // numが2になったらエラーとする
      if (num === 2) {
        reject(num);
      } else {
        resolve(num);
      }
    }, 1000);
  });
};

エラーになったら処理はどうなるのか?

他の言語の場合、例えば Java ならエラーは try して catch しますよね?
この場合も同じで、reject に値が渡った時点で then メソッドではなく
catch メソッドに処理が渡ります

index.js
wait(0)
  .then((num) => {
    num++;
    return wait(num);
  })
  .then((num) => {
    num++;
    return wait(num);
  })
 .catch(num => {
    // rejectに値が渡ったらcatchに渡り、
    // 引数がrejectに設定した値になる
    // 今回の場合はnum
    num++;
    console.error(num, 'error');
  })

実行結果

並列処理

Promise を使って、複数のメソッドを並列に処理し、全てが終わったら次の工程に進むということができます

まずは wait を引数に与えられた秒数待つように変更

index.js
const wait = (num) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(num);
      resolve(num);
    }, num);
  });
};

そして並列処理は以下のように Promise.all メソッドを使います

index.js
Promise.all([wait(1000), wait(1500), wait(2000)]).then((nums) => {
  console.log(nums);
});

実行結果

全ての wait が終わった後に console.log で戻り値を表示していることがわかります

そして、戻り値は配列で渡されていることがわかります

もう一つ並列処理があります。
一つでも処理が終われば次の処理に進む race というメソッドです

index.js
Promise.race([wait(1000), wait(1500), wait(2000)]).then((nums) => {
  console.log(nums + 1);
});

実行結果

凄く簡単に非同期処理が実現できますね!

「でもさー、もうちょっと直感的に書けない?メソッドチェーンムズくない?」

という方は、await と async を考えてみましょう

await と async

  • await ... Promise を返すメソッドにつけることで、非同期処理を行える
  • async ... await を持つメソッドの頭につけることで、非同期処理であることを教える

これらを使うと、先ほどの wait メソッドを、then を使わずに記述することができます

index.js
const wait = (num) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(num);
      resolve(num);
    }, 1000);
  });
};

const init = async () => {
  let num = await wait(0);
  num++;
  num = await wait(num);
  num++;
  num = await wait(num);
  num++;

  return num;
};

init();

実行結果

なるほど、記述した順番通りに処理が実行されていますね。

「そしたら、reject を使ってエラー処理を行う場合はどうなるのさ?」

その場合は、try-catch 文を使って実現することができます。よく見る形ですね。

index.js
const wait = (num) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(num);
      if (num === 2) {
        reject(num);
      } else {
        resolve(num);
      }
    }, 1000);
  });
};

const init = async () => {
  let num;
  try {
    num = await wait(0);
    num++;
    num = await wait(num);
    num++;
    num = await wait(num);
    num++;
  } catch (error) {
    console.error(error, "error");
  }

  return num;
};

init();

実行結果

ちなみに戻り値は Promise でラップされたものになるので注意しましょう

そのため、init メソッドも以下のように書くことができますね。

index.js
init().then((num) => {
  console.log(num, "End");
});

参考

https://www.udemy.com/course/draft/2611196/learn/lecture/16597766
GitHubで編集を提案

Discussion

ログインするとコメントできます