thumbnail

【Promise】thenとasync/awaitの違い, 使い分けと同時利用法

JavaScript(TypeScript)のthenについて解説し、async/awaitとの違いを説明した後、使い分けや同時に使うケースなどを紹介します。この記事はTypeScriptで解説しますが、型情報を無視すればTypeScriptをご存じない方もJavaScriptコードとして読めます。

thenとasync/awaitはいつからある?

まず初めにこれらの構文がJavaScriptに搭載された順番を知っておくといいでしょう。
PromiseとthenはES2015で採用され、async/awaitはそれよりも後のES2017で採用されました。
つまりthenのほうが昔からある構文になります。

thenとasync/awaitの違い

これらの2つの違いは書き方の違いくらいです。thenで書かれた処理はasync /awaitに置き換え可能です。

書き換え可能なら新しい記述方法のasync / awaitの方が良いのでは?と思うかもしれませんが、どちらが良いかは状況によります。また、同時に利用することでコードが簡潔になることもあります。

使い分けや併用についてこの記事で解説します。

thenについておさらい

簡単な例

まずは以下のようなPromiseを例に考えます。

const promise = new Promise(resolve => resolve(10))

単純に10を返すだけのPromiseです。これは下記のように簡単な式で書き換えられます。

const promise = Promise.resolve(10)

さて本題のthenですが、これはPromiseがresolveされた場合に実行されます。Promise型のオブジェクトからメソッドとして生やして利用します。
then句の中にはコールバック関数を記述し、その関数の引数にresolveの引数が代入されることになります。

const promise = Promise.resolve(10)
promise.then(data => console.log(data)) // 10と表示。dataには10が入る

Promiseがrejectされた場合は、then句ではなくcatch句でエラーを捕捉します。
また、resolveされようがrejectされようが共通の処理を書きたい場合はfinally句を続けます。

const promise = Promise.reject("ERROR")
promise
  .then(() => console.log("正常な処理"))
  .catch(err => console.error(err)) // ERRORと表示
  .finally(() => console.log("いずれにしろ表示")) // ここも表示

非同期処理の例

実際に非同期処理をthen句で書いてみます。非同期処理として、引数に渡した秒数待機する関数を例に挙げます。

const timer = (sec: number): Promise<string> =>
  new Promise((resolve) => setTimeout(() => resolve(`${sec}秒待機`), sec * 1000))
timer(2)
  .then((data) => {
    console.log(data)
    return timer(5)
  })
  .then((data) => {
    console.log(data)
    return timer(4)
  })
  .then((data) => {
    console.log(data)
    return timer(3)
  })
  .then(console.log)
// 出力
// 2秒待機
// 5秒待機
// 4秒待機
// 3秒待機

このようにthen句の中で更にPromiseをreturnしてthen句をチェーンすることで、逐次タイマーが作動していきます。

  • 最後のthen(console.log)のように、thenの中に関数名(この場合console.log)を直接書くと、thenの引数に渡されたパラメータを自動で関数に流します。

thenをasync/ awaitで書き換える

async/ awaitとは何かという解説はこの記事では行っていないので、ご存じなかったり不安な方は次の記事を参照ください。

さて、先ほどのタイマーの例をasync/ awaitで書き直してみます。

const timer = (sec: number): Promise<string> =>
  new Promise((resolve) => setTimeout(() => resolve(`${sec}秒待機`), sec * 1000))
;(async () => {
  let data = await timer(2)
  console.log(data)
  data = await timer(5)
  console.log(data)
  data = await timer(4)
  console.log(data)
  data = await timer(3)
  console.log(data)
})()

then句の時よりもシンプルで、より従来のコードに近くて分かりやすくなります。
ただし、awaitはasync関数で囲む必要があるため、上の例ではasyncの即時関数として実行しています。

ES2022からはトップレベルでawaitを使う場合はasync関数で囲む必要がなくなりました。関数の中で使うには従来通りasyncが必要です。

※参考

then句とawait句の使い分けについて

さて、今回の例を見てthen句とawait句のどちらがいいでしょうか。
正直、プロジェクトのルールがあるならそれに従って、でないのであれば好みで使って構わないと思います。
この例ですと個人的にawait句のほうがコードが少なくてすっきりとしてて読みやすいです。

コードをシンプルに書ける方を採用する、という観点も使い分ける際に重要だと思います。

逆にthen句を使った方がシンプルになる例も載せます。

// then句の方がシンプルな例
fetch("/api/hoge").then(setData)
// ↑と同じ処理をawait句で
(async () => {
  const data = await fetch("/api/hoge")
  setData(data)
})()

then句とawait句を同時に使ってよりシンプルに

then句とawait句は同時に使えます。
そして、同時に使った方がシンプルに書けることもあります。

先ほどのタイマーのawait句の例で考えます。
まずは先ほどのコードを再掲します。

const timer = (sec: number): Promise<string> =>
  new Promise((resolve) => setTimeout(() => resolve(`${sec}秒待機`), sec * 1000))
;(async () => {
  let data = await timer(2)
  console.log(data)
  data = await timer(5)
  console.log(data)
  data = await timer(4)
  console.log(data)
  data = await timer(3)
  console.log(data)
})()

このコードはawait句とthen句を利用して、より少ない行数で書き直すことが可能です。

const timer = (sec: number): Promise<string> =>
  new Promise((resolve) => setTimeout(() => resolve(`${sec}秒待機`), sec * 1000))
;(async () => {
  await timer(2).then(console.log)
  await timer(5).then(console.log)
  await timer(4).then(console.log)
  await timer(3).then(console.log)
})()

行数が半分になったうえ、dataという変数すらいらなくなりました

timerはPromise型なのでthen句が使えますし、Promise.then()もPromise型なのでawait句も使えるのです。

まとめ

then句とasync/await句の違いや使い分け、併用方法について解説しました。Promiseの処理はthenの後に登場したasync / await句に完全に取って代わられたわけでなく、それぞれ使い分けたり併用することで、コードをよりシンプルに分かりやすく書くことが可能となります。