📚

【初学者向け】非同期で考えるとループ処理がわかる?

3 min read

はじめに

この記事では、for,forEach,mapの違いを非同期処理を通して、解説します。

📚 非同期処理の準備

  • axios
    axiosはPromiseベースのHTTPClientライブラリでGETやPOSTのHTTPリクエストを使ってサーバからデータの取得、データへのデータ送信を行い、この記事では、データ(JSON)を取得するために利用

  • The Rick and Morty API
    こちらのデータ(API)を使用

api.js
const axios = require('axios');
const endpoint = 'https://rickandmortyapi.com/api/character';

const characterIds = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
async function getCharacter(id) {
  return axios.get(`${endpoint}/${id}`).then((result) => result.data);
}

module.exports = { getCharacter, characterIds };

axiosを読み込み、endpoint[1]を定義します。

The Rick and Morty API のデータを取得するために characterIdsgetCharacter関数 を追加し、exportしています。

これから、本題に進みます

💁 ‍非同期で for について考える

for.js
const { getCharacter, characterIds } = require('./api')

let results = [];
(async function() {
  console.time('get character') // タイマーの開始する
  
  for(let i = 0; i < characterIds.length; i++){
    const character = await getCharacter(characterIds[i]);
    results.push(`${character.id}:${character.name}`);
  }
  console.timeEnd('get character') // タイマーの停止する
  
  console.log(results);
})();
// TIME: 1.177s 
// RESULTS: [
// '1:Rick Sanchez',
//   // ...
// ]

即時関数[2]の本体に経過時間を図るためconsole.time[3]console.timeEnd[4]を利用します。
実行すると、経過時間は 1.177s かかり、体感で遅いと分かります。

では、forEach の場合はどうなるのでしょうか?

💁 非同期で forEach について考える

forEach.js
let result = [];
(async function() {
  console.time('get character'k
  characterIds.forEach(async(id) => {
    const character = await getCharacter(id);
    results.push(`${character.id}:${character.name}`);
  });
  console.timeEnd('get character')
  console.log(results);
})();

// TIME: 12.563ms 
// RESULTS: []

実行時間は 12,563msとかなり早かったのですが、empty が戻ってきました。
実はasync/awaitをするとき forEach は使えないのです。

なぜ、非同期では forEach は使えないでしょうか?

forEach[5]のコールバックの返り値はundefinedになります。なので、引数にasync関数を渡してもawaitされることもなく、undefinedになります。

では、非同期をするにはどうすればよいのでしょうか?

💁 ‍非同期で map と Promise.all について考える

map.js
const { getCharacter, characterIds } = require("./api");

(async function () {
    console.time('get character')

    const mapResult = characterIds.map((id) => {
        return getCharacter(id);
    });

    const results = await Promise.all(mapResult);
    console.timeEnd('get character')
    console.log(results);
})();

// TIME : 170.563ms 
// RESULTS: [
//   {
//     id: 1,
//     name: 'Rick Sanchez'
//     // ...
//   },
//   // ...
// ]

実行時間は 170.563ms とはやく、欲しかったデータもありますね。
書き方も単純です。

配列から異なる配列を生成するmap()[6]を利用し、Promise.all[7]ですべてのPromise(async)が完了したときに欲しかったデータがもらえます。

さいごに

非同期処理を通して、forの処理速度やforEachは非同期処理はできないということと、mapPromise.allで非同期処理を行いました。

またご不明な点やご指摘があれば、ご質問よろしくお願いします。

脚注
  1. endpoint(エンドポイント)とは、指定されたリソースに対して与えられた固有の一意な URI のこと。この記事では api.js にアクセスされる https://rickandmortyapi.com/api/character(URL) のことです。 ↩︎

  2. 関数を定義すると同時に実行するための構文。書き方は(function () { //処理 }());
    関数の定義と実行するためコードを分別せずに即時関数にしております。 ↩︎

  3. 測りたい処理の開始時にconsole.time()を呼び出す。このとき引数にラベル名を渡す必要がある。 ↩︎

  4. 計測終了したいタイミングでconsole.timeEnd()を呼び出す。このとき引数には同じラベル名を渡す。 ↩︎

  5. 参考: forEach ↩︎

  6. 配列の各要素に指定された関数を適用した新しい配列を生成する ↩︎

  7. Promiseを持つイテラブルを引数に受け取り、すべてのPromiseが成功した時に成功し、いずれかのPromiseが失敗したときに失敗するPromiseを返す ↩︎

GitHubで編集を提案

Discussion

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