TypeScript(JavaScript)・Reactの非同期処理について

  • #開発
  • #typescript
  • #React

こんにちは、CURUCURUエンジニアの水谷です。 フロントエンドの学習をしていてよく躓くジャンルとして「非同期処理」があると思います。 (asyncとかawaitとかPromiseとか出てくるやつです。)

今回はそこの理解が足りず躓いていたところがあったので、 ブログにてまとめておこうと思います。


概要

TypeScriptやJavaScript、Reactの非同期処理について

非同期処理とは

非同期処理という言葉の通り、「非」同期処理なので、同期処理の否定形になります。

では、同期処理とは何か

同期処理について

同期処理とは、プログラムを書いた順番に流れていく処理のことです。 この同期処理だけでは都合が悪くなるときがあります。

例えば、データベースから値を取ってきたり、他のアプリケーションからのデータを受け取ったりする処理を入れた場合、データの要求から応答が返ってくるまでに時間がかかります。

書かれた処理の順番でプログラムが走るとすると、データの応答を待っている間に他の処理ができなくなります。 これが都合が悪くなるパターンです。

非同期処理について

ここで登場するのが非同期処理です。 データの応答を待っている間に他の処理を実行します。他に処理を任せるイメージです。 言ってしまえばそれだけの処理ですが、ここで使うコードの概念がよくわからずに躓くことが多いのだと思います。 具体的なコードについてまとめておきます。

非同期処理で使うコード

非同期処理で使うコードで主に下記があります。

  • ・Promise
  • ・async/await

Promise について

Promiseについてまとめておきます。 Promiseは主に3つの状態を持っています。

  • ・pending:非同期処理の実行中の状態を表す
  • ・fulfilled:非同期処理が正常終了した状態を表す
  • ・rejected:非同期処理が異常終了した状態を表す

この3つの状態を持っているというのが最初はイメージがしづらかったのですが、Promiseが返す値が時間が経つにつれて変わってくるというイメージをしました。

非同期処理は応答が返ってくるまでPromiseに任せておいて、返ってきたらPromiseが教えてくれるといったイメージです。

例えば下記のコードです。

return new Promise((resolve, reject) => {
    const form = new hogehoge();
    hogehoge.task(req, (err, check) => {
        if (err) {
            reject(err);
        } else {
            resolve({ check });
        }
    });
});

上記のコードでは、Promiseの持っている3つの状態のいづれかを返します。 なので、このPromiseの返り値を待ってから次の処理を書くことが出来ます。

const hugahuga = () => new Promise((resolve, reject) => {
    const form = new hogehoge();
    hogehoge.task(req, (err, check) => {
        if (err) {
            reject(err);
        } else {
            resolve({ check });
        }
    });
});

hugahuga.then(higahiga());

ここでthenというメソッドが出てきましたが、これはPromiseで処理が完了してから実行される処理を書きます。

awaitとasyncについて

awaitとasyncも非同期で使います。 上記を書き換えると、、

const hugahuga = () => new Promise((resolve, reject) => {
    const form = new hogehoge();
    hogehoge.task(req, (err, check) => {
        if (err) {
            reject(err);
        } else {
            resolve({ check });
        }
    });
});

const test = async() => {
  await hugahuga();
  higahiga();
}

hugahugaという処理はPromiseを返しますが、awaitをつけて実行することで、その後のhigahiga()はhugahugaの実行を待ってから実行されます。

ですが、testは非同期関数なので、

test();
best();

上記の実行をすると、higahiga()が実行される前にbestが先に実行されます。 このあたりの順番がややこしかったです。

Reactの非同期処理

ここで少しReactの話になるのですが、ReactにはuseEffectというhooksがありこれを利用することで非同期処理のようなことが出来ます。

useEffect(() => {
  /* fetchが完了したあとの処理 */
},[test]);

const test = fetch();

上記のように書くとfetchで返ってきてから処理をしたいときにhooksで処理を書くことが出来ます。

useEffectでハマったこと

またちょっとズレますが、useEffectでハマったことをここでまとめておきます。 上記のように書くと非同期的な書き方ができますが、useEffect内で使った変数をトリガにセットすると無限ループの可能性があります。

特にfetchが完了したあとの処理でセットしたデータをコンポーネントの中で使用しており、そのデータをuseEffectのトリガにしているとまずいです。

useEffect(() => {
  getTemp();
},[huga,temp]);

const huga = hoge();

const getTemp () => {
  const test = fetch();
  setTemp(test);
};

上記の場合、hogeが実行されるとhugaの値が変化します。 useEffectはそれを検知して、getTempを実行します。

getTempの関数が実行されたらsetTempでtempというStateにフェッチした値が格納されます。

それをuseEffectが検知して再度getTempが実行されて、、 といった感じで無限にループします。

似た現象が実体験としてあったので、ブログにまとめておきました。

注意点としては、useEffect内でsetやgetをしている処理と同じものがコンポーネント内にあるのであれば、そこが無限ループの種になるので、そこをトリガーにしないように注意するのが良いと思います。

まとめ

非同期処理から始まってReactの話まで書きましたが、最近悩んでいた部分だったので一度ブログにして知識の整理が出来ました。 優しい先輩からペアプロでみっちり教えてもらえました。

メンバー募集

CURUCURUでは開発メンバーを絶賛募集中です。 CURUCURUの開発に興味があったり、モダンな開発環境で挑戦してみたいという方がいましたら、ぜひこちらも覗いてみてください!