RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

【TypeScript】axiosの内側をPromiseから理解する

はじめに

こんにちは、開発課に所属している新卒 1 年目のke-suke0215です。

今回、 axois について調べてみました。
axios は非同期で API 呼び出しを簡単に行うことができるライブラリです。
しかし、簡単がゆえに内側でどのように動いているか曖昧な人も多いのではないでしょうか。

axios の中身を理解するには、よく一緒に使われる async と await の理解が重要になってきます。
また、 async と await の理解には Promise についての理解が必要になり、Promise は非同期処理の記述に使うものです。
なので、非同期処理、Promise、async と await、最後にメインの axios の順で説明していきます。

サンプルプログラムは TypeScript で記述しています。

目次

非同期処理とは

非同期処理は字からも分かる通り、同期処理ではない処理のことです。
具体的には下のように表されます。

  • 同期処理:実行した処理の終了を待って次の処理に進む動き
  • 非同期処理:実行した処理の終了を待たずに次の処理に進む動き

言葉だけだと分かりづらいため、実際のプログラムを見ながら説明していきます。

ここでは 2 つの引数(文字列)を受け取り、setTimeoutを用いて 1 秒後に 1 つ目の引数を表示し、その直後に 2 つ目の引数を表示するプログラムを考えたいと思います。
実行時には引数に"引数1""引数2"を入れているので、実行した1秒後に

引数1
引数2

と表示されるのが期待する振る舞いです。

const sampleFunc = (str1: string, str2: string): void => {
  // 1秒後に実行
  setTimeout(() => {
    console.log(str1);
  }, 1000);

  console.log(str2);
};

sampleFunc("引数1", "引数2");

しかしこのプログラムを実行すると、下のように直ちに引数2が表示され、その 1 秒後に引数1が表示されます。

引数2
引数1

これが非同期処理です。setTimeoutで 1 秒待っている間、TypeScript は処理の終了を待たずに次に進んでしまいます。なので引数2が先に表示されてしまうのです。
期待する振る舞いを実現するためには、setTimeoutの処理が終了してから次に進んでほしいので、ここでやりたいことは非同期処理を同期処理のように扱うことです。

Promise について

Promise は一言で表すと『非同期処理の状態を監視するためのオブジェクト』です。
非同期処理を同期処理のように扱える書き方の 1 つになります。
具体的に説明していきます。

pending, resolve, reject の 3 つの状態がある

これが Promise を考える上で重要な概念になってきます。3 つの状態の意味は下記のようになっています。

  • pending:待機(処理の完了を待っている)
  • resolve:解決(処理が成功)
  • reject:拒否(処理が失敗)

Promise は常にこの 3 つのうちどれかの状態になっています。
はじめは待機を表す pending になっており、処理の内容によって状態を変化させます。
期待通りであれば成功を意味する resolve 、期待にそぐわない場合は失敗を意味する reject に変更するのが基本的な使い方です。

Promise を動かしてみる

先程のプログラムに少し変更を加え、setTimeoutの中で条件分岐を追加して Promise を使ってみます。
第 1 引数に文字列を入れて関数を実行すれば成功とし、1 秒後に第 1 引数を表示して直後に第 2 引数を表示します。
第 1 引数が空文字なら失敗とし、第 1 引数が空文字である旨を伝えるメッセージを出力します。
まだ説明していない記述もありますが、一旦スルーしてください。

const sampleFunc = (str1: string, str2: string): void => {
  new Promise<void>((resolve, reject) => {
    setTimeout(() => {
      if (str1 !== "") {
        console.log(str1);
        resolve(); // 状態をresolve(成功)に変更
      } else {
        reject(); // 状態をreject(失敗)に変更
      }
    }, 1000);
  })
    .then(() => {
      console.log(str2);
    })
    .catch(() => {
      console.log("第1引数が空文字です");
    });
};

sampleFunc("引数1", "引数2");

今回は先程と同様"引数1""引数2"を引数にいれているので、実行した 1 秒後に

引数1
引数2

と出力されます。

Promise を使用していない記述では引数2が先に表示されていましたが、今回は期待通りの動きです。
プログラムの詳細を見ていきましょう。

状態を変更する

コメントでも記載していますが、6 行目のresolve()と 8 行目のreject()が状態を成功と失敗に変更する記述です。

これは Promise をインスタンス化する際の引数に入れる、コールバック関数の 2 つの引数が変更する関数名に該当します。str1が空文字ではない場合は状態を resolve にしたいので、値を出力してからresolve()を実行し、空文字の場合はreject()を実行することで状態を reject にしています。

then と catch

Promise が非同期処理を制御できるのは、状態によってその後の処理を変える仕組みを持っているからです。
Promise の処理の後には、.then.catch を続けることができます。
具体的には、Promise の中の処理が終了したとき状態が resolve であれば then の引数の中が、reject であれば catch の引数の中が実行されるようになっています。

今回はresolve()が実行されて処理の終了時には Promise の状態が resolve になっているので、then の引数の中身が実行され、catch は無視されます。仮に引数であるstr1を空文字で実行した場合、then は無視されて catch の引数の中身が実行される動きとなります。

したがって、特定の処理(今回の場合はsetTimeout)の終了を待ってから次の処理を行いたい場合、

  • Promise の中で最初の処理を実行
  • 状態を resolve に変更
  • then の引数に次の処理を記述

このようにすることで実現できます。
以上が Promise における非同期処理の基本的な説明です。

async と await

ここまでで見てきた Promise は、次に実行したい処理を then や catch に入れていました。
ですが async と await を使うと then などを使用しなくても非同期処理を同期処理のように動かすことができるので、よりシンプルに書くことができます。
Promise の節に記述したプログラムを async と await に書き換えたものを見てみます。

const sampleFunc = async (str1: string, str2: string): Promise<void> => {
  await new Promise<void>((resolve, reject) => {
    setTimeout(() => {
      if (str1 !== "") {
        console.log(str1);
        resolve(); // 状態をresolve(成功)に変更
      } else {
        reject(); // 状態をreject(失敗)に変更
      }
    }, 1000);
  });

  console.log(str2);
};

sampleFunc("引数1", "引数2");

出力結果は Promise の時と同様に下記のようになります。

引数1
引数2

順を追って説明します。

async は同期処理として扱いたい一連の処理を記述した関数の前につけます。

次に、await です。await には以下のようなルールがあります。

  • await の次で Promise をインスタンス化する
  • async をつけた関数の中に書く

Promise オブジェクトの前に await をつけることで、その Promise オブジェクトが値を返すのを待つようになります。
つまり Promise 内の処理が終了するまで次の処理に進まなくなるのです。
これにより非同期処理を同期処理のように扱うことができます。

await をつけた処理が終了したときに状態が resolve であればそのまま次の処理に進み、reject であればエラーとなります。
今回は省略していますが、trycatchで囲むことでエラー後の処理を書くことができます。

この例では制御する非同期処理は 1 つですが、複数ある場合には async と await で then などを省略できるメリットがより顕著に感じられるはずです。

axios の中身で行われていること

ようやく本題の axios についてです。
冒頭でも述べましたが、axios は非同期で API 呼び出しを行うことができます。
また、先程説明した async,await と一緒に使われることが多いです。

例として郵便番号をパラメータとして渡すと住所を返す API を使用します。
引数に郵便番号を入れるとその住所を取得する関数を作成し、実行してみます。
それではサンプルプログラムを見ていきましょう。

import axios from "axios";

const sampleFunc = async (zipcode: string): Promise<void> => {
  // APIを呼び出す
  const response = await axios.get("https://zipcloud.ibsnet.co.jp/api/search", {
    params: { zipcode: zipcode },
  });

  console.log(response.data);
};

sampleFunc("7830060");

こちらのプログラムを実行すると、次のような出力となります。

{
  message: null,
  results: [
    {
      address1: '高知県',
      address2: '南国市',
      address3: '蛍が丘',
      kana1: 'コウチケン',
      kana2: 'ナンコクシ',
      kana3: 'ホタルガオカ',
      prefcode: '39',
      zipcode: '7830060'
    }
  ],
  status: 200
}

きちんと住所の情報が帰ってくることが確認できます。
それではプログラムの解説に移ります。

axios はライブラリなので、1 行目でインポートしています。
今回は get を使用していますが、post や delete を使うこともできます。
引数に URL を入れ、必要であればパラーメタを指定することで API を呼び出すことができます。

先程まではsetTimeoutを利用し、明示的に 1 秒処理を待っていたのでわかりやすかったです。
API の呼び出しについても具体的な待機時間は場合によりますが、通信するのに一定の時間がかかるため await を使用しないとデータを取得する前にconsole.log(response.data);が動いてしまいます。

axios は「Promise ベース」と言われますが、この面で 2 つのことを自動的にやってくれています。

await のルールを説明した部分で、

await の次で Promise をインスタンス化する

と書きました。
今回のプログラムでは await の後に axios が来ており、一見 Promise をインスタンス化していないように思えます。
しかしこのとき axios は、裏側で自動的に Promise をインスタンス化してくれているのです。
また、通信が成功したかどうかによって Promise の状態を、resolve もしくは reject に変えてくれています。

これらによって Promise をあまり意識せずに「async と await つけて axios で API 呼び出せばいい感じに動く」ということが実現されています。

まとめ

axios が内側でどのような処理を行っているのかを、遡って見てきました。
Promise から順を追って見ていくと、わかりやすかったと思います。

内側の処理を理解することで、実行する位置やエラーの拾い方などで迷うことが減るのではないでしょうか。
参考にしていただければ幸いです。


エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
20210916153018
https://career-recruit.rakus.co.jp/career_engineer/

カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com

ラクスDevelopers登録フォーム
20220701175429
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/

イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!

◆TECH PLAY
techplay.jp

◆connpass
rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.