Open5

[JS] 非同期処理 Promise オブジェクト, .then()/catch(), async/await, try/catch() の使い方

おにぎおにぎ

非同期処理と Promise の解釈 🎗

非同期処理とは

非同期処理とは、ある程度時間を要するタスクを複数同時に処理することである。
例えば JavaScript で書いた3つの関数を上から順に実行開始したとして、処理に要する時間がそれぞれ違う場合、3つのタスク完了の着地順は順不同になる。そうなると、

  • 完了した処理の戻り値を元に、続きの処理を実行したい
  • 3つのタスクを順番に(同期的に)実行したい

という場面が出てくるはず。
そんな時は非同期処理を Promise でラップして、 Promise オブジェクトを返す非同期関数とすることで、呼び出し側はその返された Promise の結果(成功または失敗)からハンドラーを結びつけて続きの処理を実行したり、await を使って非同期関数の完了を待つことで同期的な処理ができるようになる。

Promise オブジェクトとは

ここでの Promise (プロミス) とは、未来のある時点で値を提供しますという約束のこと。
未来のある時点とは、その非同期関数の処理が完了した時点のこと。
値とは、主に、処理が履行したときに返す値と、例外(エラー)が発生した時に返す値の2つのこと。
Promise オブジェクトは、処理が完了するとそのどちらかの値を示す。
非同期関数を呼び出した側は、その関数が Promise を返すことを知ると、直ちに待機状態と判断して一旦非同期関数は終了させられ、次に控えているタスクを順番に実行していく。その間に時間を要して処理が完了した先ほどの非同期関数が、本来返したかった完了した Promise を返すと、それを感知して再びその非同期関数の地点へ戻って成功または失敗のコールバック関数が実行される。

Promise の状態

処理を実行した現時点の初期状態は Pending(待機)と呼ばれ、
処理が完了した未来の時点の状態は Settled(決定)と呼ばれる。

Settledfulfilledrejected の2種類あり、

  • fulfilled(履行) : 処理が成功して完了
  • rejected(拒否): 処理が失敗

を表す。

おにぎおにぎ

履行 Promise を返す非同期関数の作り方 🍳

履行された Promise オブジェクトを返す非同期関数を作ってみる。

0. Promise インスタンスを生成して resolve() する方法

Promise<void> を返す

const sleep = (n:number):Promise<void> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(), n)
  })
}

この sleep という関数は、n秒後の未来に、Promise<void> を返す非同期関数である。
関数内で resolve() すると、その非同期関数は履行されたとして Promise の状態が Settled となる(関数が終了する)。
ちなみに一行で書くとこうなる↓

const sleep = (n:number):Promise<void> => new Promise((resolve, reject) => setTimeout(() => resolve(), n))

呼び出し元(Promiseオブジェクトを返される側)の記述

then()を使う

sleep(3000).then(() => alert('wake up!'))

3秒後に wake up! というアラートが出る。

await を使う

(async () => {
  await sleep(3000)
  alert('wake up!')
})()

ちなみに、今回 Promise<void> のため、つまり返り値が undefined のため、result は undefined になる。

(async () => {
  const result = await sleep(3000)
  console.log(result) // undefined
})()

Promise<string> を返す

n秒後の未来に、Promise<string> を返す非同期関数。

const sleep = (n:number):Promise<string> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve('起きて!'), n)
  })
}

呼び出し元

3秒後に、resolve('起きて!') で渡された文字列を受け取って、
「起きて!」というアラートを出す。

then を使う

sleep(3000).then((message) => alert(message))

await を使う

(async () => {
  const message = await sleep(3000)
  alert(message)
})()