😳

非同期処理・Promiseについて

2022/10/10に公開

なんとなくで使っていた、非同期処理・Promise について勉強し直したいと思います。

目次

  1. 非同期処理の実行順番
  2. Promise について
  3. Async Function について

非同期処理の実行順番

非同期処理の実行順番を確認しました。
まず、下記のようなコードでどのような動作になるかを見てみます。
asynchronousFunction が非同期関数となっています。

const syncFunction = () => {
  console.log("同期関数が呼ばれた");
};

const asynchronousFunction = () => {
  setTimeout(() => {
    const nowTime = new Date();
    console.log(
      nowTime.getSeconds() - afterTime.getSeconds(),
      "秒後コールバック関数が実行された"
    );
  }, 3000);
};

const afterTime = new Date();
console.log("スタート");
asynchronousFunction();
syncFunction();
スタート
同期関数が呼ばれた
1 秒後に非同期関数が実行された

結果の通り、先に同期関数の値が出力されており、非同期関数は1秒後に実行されています。
では、同期関数の実行にとても時間がかかってしまった場合はどうでしょうか。下記のコードでは、同期関数が 3 秒かかるようにしています。

const asynchronousFunction = () => {
  setTimeout(() => {
    const nowTime = new Date();
    console.log(
      nowTime.getSeconds() - afterTime.getSeconds(),
      "秒後コールバック関数が実行された"
    );
  }, 1000);
};

const slowFunction = (timeout: number) => {
  const startTime = Date.now();
  while (true) {
    const diffTime = Date.now() - startTime;
    if (diffTime >= timeout) {
      console.log(timeout / 1000, "秒後");
      return;
    }
  }
};

const afterTime = new Date();
console.log("スタート");
asynchronousFunction();
slowFunction(3000);
スタート
3 秒後
3 秒後コールバック関数が実行された

結果の通り、非同期関数は一秒後に実行されるはずが、3 秒後に実行されてしまっています。これは、javascript が単一のスレッドでしか動作できないからです。setTimeout 関数は、非同期で実行されますがコールバックされた関数は、イベントキューに追加され、全ての同期処理が完了した後に、イベントキューにある関数が実行されます。
次に、非同期関数は 3 秒後に実行され、同期関数に 1 秒かかった場合を見てみます。

const asynchronousFunction = () => {
  setTimeout(() => {
    const nowTime = new Date();
    console.log(
      nowTime.getSeconds() - afterTime.getSeconds(),
      "秒後に非同期関数が実行された"
    );
  }, 3000);
};

const slowFunction = (timeout: number) => {
  const startTime = Date.now();
  while (true) {
    const diffTime = Date.now() - startTime;
    if (diffTime >= timeout) {
      console.log(timeout / 1000, "秒後に同期関数が呼ばれた");
      return;
    }
  }
};

console.log("スタート");
asynchronousFunction();
slowFunction(1000);
スタート
1 秒後に同期関数が呼ばれた
3 秒後に非同期関数が実行された

このように同期関数が 1 秒かかっていたとしても、非同期関数の実行時間はちゃんと 3 秒後になっています。

Promise について

プロミス (Promise) は、作成された時点では分からなくてもよい値へのプロキシーです。非同期のアクションの成功値または失敗理由にハンドラーを結びつけることができます。これにより、非同期メソッドは結果の値を返す代わりに、未来のある時点で値を提供するプロミスを返すことで、同期メソッドと同じように値を返すことができるようになります。
引用:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

あとで値を返すことを証明するオブジェクト(Promise オブジェクト)を返すことができます。Promise では、メソッドチェーンを使用することによってコードを見通しを良くすることができます。例えば、以下のような Promise を使っていないコードの場合、

const asynchronousFunction2 = () => {
  setTimeout(() => {
    try {
      throw new Error("エラー");
    } catch (error) {
      console.log("エラーをキャッチ");
    }
  }, 3000);
};
const asynchronousFunction = () => {
  setTimeout(() => {
    try {
      asynchronousFunction2();
    } catch (error) {
      console.log(error);
    }
  }, 3000);
};
asynchronousFunction();

このようにネストが深くなってしまい見にくくなります。いわゆるコールバック地獄というものです。
反対に Promise を使用すると、

const asynchronousFunction2 = (value) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(value);
      reject("2個目は失敗");
    }, 1000);
  });
};

const asynchronousFunction = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("1個目成功");
    }, 1000);
  });
};
asynchronousFunction()
  .then((value) => asynchronousFunction2(value))
  .then(() => cosnole.log("成功"))
  .catch((error) => console.log(error));
1個目成功
2個目は失敗

このように見通しの良いコードを書くことができます。

Async Function について

関数の前に async をつけると必ず Promise を返す関数にすることができます。

const asyncFunction = async () => {
  return "test";
};

console.log(asyncFunction());
Promise { 'test' }

また、async をつけた関数の中では await を使用することができ、await を使用すると右辺の Promise が成功か失敗するまで結果を待つことができます。
つけなかった場合

const asyncFunction = async () => {
  const value = asynchronousFunction();
  console.log(value);
};

asyncFunction();
Promise { <pending> }

await をつけた場合

const asynchronousFunction = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("完了");
    }, 1000);
  });
};

const asyncFunction = async () => {
  const value = await asynchronousFunction();
  console.log(value);
};

asyncFunction();
完了

結果の通り、await を使用した場合は非同期関数の値を受け取ってから出力することができています。

他にも
・Promise メソッド
・async を使用したループ
など書きたいことはありますが、この記事ではここまでとします。

参考文献

https://jsprimer.net/basic/async/

Discussion