
ブログをNext.js14(App Router)でリファクタリングしました
JavaScript(TypeScript)のthenについて解説し、async/awaitとの違いを説明した後、使い分けや同時に使うケースなどを紹介します。この記事はTypeScriptで解説しますが、型情報を無視すればTypeScriptをご存じない方もJavaScriptコードとして読めます。
目次
まず初めにこれらの構文がJavaScriptに搭載された順番を知っておくといいでしょう。
PromiseとthenはES2015で採用され、async/awaitはそれよりも後のES2017で採用されました。
つまりthenのほうが昔からある構文になります。
これらの2つの違いは書き方の違いくらいです。thenで書かれた処理はasync /awaitに置き換え可能です。
書き換え可能なら新しい記述方法のasync / awaitの方が良いのでは?と思うかもしれませんが、どちらが良いかは状況によります。また、同時に利用することでコードが簡潔になることもあります。
使い分けや併用についてこの記事で解説します。
まずは以下のような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句をチェーンすることで、逐次タイマーが作動していきます。
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句のどちらがいいでしょうか。
正直、プロジェクトのルールがあるならそれに従って、でないのであれば好みで使って構わないと思います。
この例ですと個人的にawait句のほうがコードが少なくてすっきりとしてて読みやすいです。
コードをシンプルに書ける方を採用する、という観点も使い分ける際に重要だと思います。
逆にthen句を使った方がシンプルになる例も載せます。
// then句の方がシンプルな例
fetch("/api/hoge").then(setData)
// ↑と同じ処理をawait句で
(async () => {
const data = await fetch("/api/hoge")
setData(data)
})()
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句に完全に取って代わられたわけでなく、それぞれ使い分けたり併用することで、コードをよりシンプルに分かりやすく書くことが可能となります。
シェア!
関連記事
最新の記事
カテゴリー一覧
アーカイブ