LoginSignup
1
0

More than 1 year has passed since last update.

非同期処理の基本的な制御

Last updated at Posted at 2021-06-23
1 / 18

非同期処理の動作を確認

setTimeout関数で簡単な非同期な関数を確認することができます。


// setTimeout:第一引数の関数を、第二引数のミリ秒後に実行

// 2秒後に'Hello'を出力
const outputHello = () => {
  setTimeout(() => {
    console.log('Hello');
  }, 2000);
}

// 1秒後に'World'を出力
const outputWorld = () => {
  setTimeout(() => {
    console.log('World!');
  }, 1000);
}

outputHello();
outputWorld();


実行結果

前ページのコードをブラウザのコンソールに貼り付けて実行します。
出力結果は期待通りだったでしょうか?

先に書いたsetTimeoutのコールバック関数の実行より前に、後ろで書いたsetTimeoutのコールバック関数が実行されています。


非同期処理制御の必要

非同期処理が絡むと、単純に順番に書いただけでは、想定する順番で動かないことがあります。
例えば、サーバAPIでJSONデータをフェッチした後に、ローカル変数に代入する場合は、レスポンスが返ってくる前に、代入の処理に進んでしまいます。


コールバック(非推奨)

非同期処理の古典的な実装にコールバックを段々と積み上げることで順序保障する方法があります。
例外処理などを書いていくと、コード見通しが非常に悪くなるので、現在ほぼ使う機会はないかと思います。

// 2秒後に'Hello'を出力
const outputHello = (callback) => {
  setTimeout(() => {
    console.log('Hello');
    callback(); // コールバック関数を実行
  }, 2000);
}

// 1秒後に'World'を出力
const outputWorld = () => {
  setTimeout(() => {
    console.log('World!');
  }, 1000);
}

outputHello(outputWorld); // outputWorld関数をoutputHello内で実行する

Promise

JavaScript標準でpromiseという非同期処理を制御するためのものが用意されています。
後述するasync/awaitなどもフロントエンドの非同期処理の基盤となっている機能です。


Promiseに書き換え

さきほどのsetTimeoutの関数をPromiseで扱えるようにします。
API通信時はaxiosなどライブラリを使うことが多いので、実業務でPromiseを返す関数を作成することは少ないかと思います。

// 2秒後に'Hello'を出力
const outputHello = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {

      // 乱数(0〜10)が3未満の場合はreject
      Math.random() *10 > 3
        ? resolve(console.log('Hello'))
        : reject(Error('outputHelloでエラー発生'));

    }, 2000);
  });
}

// 1秒後に'World'を出力
const outputWorld = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {

      // 乱数(0〜10)が3未満の場合はreject
      Math.random() *10 > 3
        ? resolve(console.log('World!'))
        : reject(Error('outputWorldでエラー発生'));

    }, 1000);
  });
}

outputHello()
  .then(outputWorld)
  .catch((e) => {
    console.log(e.message);
  })
  .finally(()=>{
    console.log('処理終了');
  });

Promiseの状態

Promiseを返却する関数は3つ状態を持ちます。

  • pending

resolveもrejectも実行していない状態。例えば。APIサーバに投げたりクエストが返ってきていない状態など。

  • fulfill

resolveが実行されて、正常に処理が完了した状態。thenでチェインしている場合、次の関数に進む。

  • reject

rejectが実行された状態。基本的に例外が発生した場合にrejectとなる。thenでチェインしている場合でも、次の関数に進まず、catchメソッドに飛ぶ。

スクリーンショット 2021-06-23 10.09.04.png


便利メソッド(Promise.all)

複数のPromiseを返す関数を一斉に走らせて、全ての関数のPromiseの状態がfullfillになるとthenを実行する。
それぞれの非同期処理の順序はどうでも良いが、全て完了したあとに処理を行いたい際に使う。

※1つでもrejectがあると、catchを実行する。

Promise.all([outputHello(), outputWorld()])
    .then(()=> {
      // all fulfill
    })
    .catch((e)=> {
      // exist reject
    });

たいていの処理がPromise.thenとPromise.allで対応できますが、必要に応じて他のメソッドも検討します。


async/await

Promiseは非同期処理をコールバックを使わずに記述できますが、async/awaitを使うことで、さらに簡略化できます。


awaitの役割

awaitを付けるとPromiseの結果が返却されるまで処理を待機します。
thenでチェインする必要なく記述した順序で実行され、Promiseの結果を待ちます。

後述するasyncを付けた関数内でしか使用できません。try-catchで例外を捕捉することができます。

try {

    await outputHello();
    await outputWorld();

  } catch (e) {
    console.log(e.message)
  } finally {
    console.log('処理終了')
  }

asyncの役割

  • asyncを付けた関数(async function)がPromiseを返すようになる。
  • async function内でreturnすると、resolveを実行する
  • async function内でthrowすると、rejectを実行する

要するに非同期処理をあまり意識せずに、returnしたら正常終了、throwしたら例外発生とすることができる。


asyncのサンプルコード

// 2秒後に'Hello'を出力
const outputHello = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {

      // 乱数(0〜10)が3未満の場合はreject
      Math.random() *10 > 3
        ? resolve(console.log('Hello'))
        : reject(Error('outputHelloでエラー発生'));

    }, 2000);
  });
}

// 1秒後に'World'を出力
const outputWorld = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {

      // 乱数(0〜10)が3未満の場合はreject
      Math.random() *10 > 3
        ? resolve(console.log('World!'))
        : reject(Error('outputWorldでエラー発生'));

    }, 1000);
  });
}

const sequence = async () => {

  try {

    await outputHello();
    await outputWorld();

  } catch (e) {
    throw e;
  } finally {
    console.log('sequence処理終了')
  }
}

// asyncをつけるとPromiseを返す関数となるので、Promiseのメソッドが使える!
sequence()
  .then(sequence)
  .catch((e)=> console.log(e.message))
  .finally( ()=> console.log('処理終了'));

おまけ


課題1

コンソール出力内容は?

setTimeout(function() {
  console.log('A');
}, 5000);

setTimeout(function() {
  console.log('B');
}, 4000);

setTimeout(function() {
  console.log('C');
}, 3000);

setTimeout(function() {
  console.log('D');
}, 2000);

setTimeout(function() {
  console.log('E');
}, 1000);

課題2

コンソール出力内容は?

setTimeout(function() {
  console.log('A');
  setTimeout(function() {
    console.log('B');
    setTimeout(function() {
      console.log('C');
      setTimeout(function() {
        console.log('D');
        setTimeout(function() {
          console.log('E');
        }, 1000);
      }, 2000);
    }, 3000);
  }, 4000);
}, 5000);

課題3

コンソールにHelloと出力されましたが、続いてWorldが出力されません。

const outputHello = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      console.log('Hello');
    }, 2000);
  });
}

const outputWorld = () => {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      console.log('World!');
    }, 1000);
  });
}

outputHello()
  .then(outputWorld)
  .catch((e) => {
    console.log(e.message);
  })
  .finally(()=>{
    console.log('処理終了');
  });

課題4

asyncの機能の認識を間違って実装している部分があります。指摘してください。


// httpリクエストを発行する関数
public async executeRequest ():Promise<any> {

    if (!this.isValid) {

      return new Promise(() => {
        throw new InvalidError('無効なリクエストです。');
      });
    }

    // axiosを使ってJSONデータをリクエスト
    return axios.request(this.config)
      .then((res) => {
        return res.data;
      })
      .catch((e) => {
        throw new Error(e.message);
      });
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0