MENU

「async/await」についてあさーく学ぶ

今回は「async/await」について学んだ内容をまとめたいと思います。

「async/await」は非同期処理に関するもので、ちょっと複雑なことをやろうとすると必要になってきます。
今後、複雑なこともできるようになっていきたいのでなんとか身につけたいものです。

しかし自分は理解するのにかなり苦戦しました。というかまだ理解できてません、なのでいつでも確認できるように記録しておきたいと思います。

できるだけ分かりやすく書いていきたいと思いますので、よろしくお願いします。

目次

async/awaitについて学ぼう

まずは問題

問題
ここに3秒後に3を返す関数get3と、5秒後に5を返す関数get5があります。
この2つの足し算をしよう!

// 3秒後に3を返す関数
const get3 = async () => {
  await sleep(3000) // 3秒待機
  return 3
}

// 5秒後に5を返す関数
const get5 = async () => {
  await sleep(5000) // 5秒待機
  return 5
}

// 時間経過用
const sleep = (msec: number) => {
  return new Promise<void>((resolve) => {
    setTimeout(resolve, msec)
  })
}

// 問題:get3とget5の返り値の足し算をしよう

やってみる

単純にそのまま足してみるとどうなるでしょうか?

vscode

・・・エラー出てる・・・

エラー文をみると

Operator '+' cannot be applied to types 'Promise<number>' and 'Promise<number>'.

と出ています。(”+”はPromise<number>とPromise<number>には使えないよ!)

Promise<number>???numberじゃないの???
そもそもコードにあるasyncやawaitって何よ?って思うかもしれませんが、もうちょっと我慢してください。

書き直すと

const main = async () => { // asyncを追加
  const r3 = await get3() // awaitを追加
  const r5 = await get5() // awaitを追加

  console.log(r3 + r5) // 8
}

こんな風に async と await を使うとうまくいきます。

なんでasync/awaitがないとエラーになって、async/awaitを使うとうまくいくんでしょうか?

なんで async や await が必要なの?

async/await が必要な理由は「3秒後に3を返す関数get3」「5秒後に5を返す関数get5」の「3秒後」「5秒後」の部分、つまりget3()、get5()には待ち時間があるからです。

いつもなら

noWaitAction() // 待ち時間なし
anotherAction() // noWaitActionが終了したら、anotherActionが実行される

こんなふうにコードの上から順番に処理されていきます。

しかし待ち時間があると

waitAction() // 待ち時間あり
anotherAction() // waitAction()の終了を待たずに、anotherActionが実行される

待ち時間があっても終了を待たずに、次の処理が実行されてしまいます。

じゃあどうしたらいいの?

というと、ここで async/await が出てきます。

const main = async () => { // 全体をasyncをつけた関数で囲む
  await waitAction() // 待ち時間がある処理の前に await をつける
  anotherAction() // waitAction()の前に await があるので、
           // waitAction()の終了後にanotherAction()が実行される
}

awaitを前につけることで、その処理が終わるまで「待つ」ことができます。また、awaitはasyncをつけた関数の中でのみ使用できるのでmainにはasyncをつけています。

つまり、まとめると

まとめ

  • コードは上から順に処理される
  • 待ち時間があっても、待たずに次の処理が実行されてしまう
  • 待ち時間がある処理の前に await をつけると、その処理が終わるまで次の処理は実行されない

ということです。

待ち時間をちゃんと待つために async/await が必要なんですね。

レストランの予約で例えると

const レストランの予約をする = () => {
  const 返事 = 予定が空いているか連絡する()
  Webサイトでレストランを予約する()
 予約ができたことを相手に連絡する()
}

async/await がないと

>相手に連絡する

>相手から返事が来る前にWebサイトで予約、予約できたことを連絡

と、おかしなことになってます。相手からしたら「O日にXXX行かない?」って連絡が来て、返事もしないうちに「レストラン予約できたよー」と連絡が来るようなものです。怖いわ。

正しくは

const レストランの予約をする = async () => { // 待ち時間があるので async
  const 返事 = await 予定が空いているか連絡する() // 相手からの連絡待ちなので await
  Webサイトでレストランを予約する()
 予約ができたことを相手に連絡する()
}

と、なります。async/await があることで

>相手に連絡し、返事が来るのを待つ

>相手からの返事が来る

>Webサイトで予約し、予約できたことを相手に連絡

と、ちゃんと処理できますね。

実際のコードだと「サーバーからのレスポンス待ち」だったり「ユーザーからの入力待ち」などで「待つ」必要があると思います。こういう時に async/await が必要になりますね。

ただ、これだけだと説明不足の点がかなーりあるので次はPromiseについて書きたいと思います。

Promiseについて(少し)学ぼう

Promiseってなに?

async/await はすこーしイメージできた気がします。だけどさっきから出てきてるPromiseについては何にも説明してません。

なので、これまたすこーしPromiseについて書きたいと思います。

ぶっちゃけPromiseはよくわからなかったので
超シンプルに書きます。

Promiseは上で出てきたget3の返り値の型に出てきます。

const main = async () => {
  const r3 =  get3() // r3の型がPromise<number>
 const r5 = await get5() // r5の型はget5()の前にawaitをつけたのでnumber
}

上で説明した通り、get3は待ち時間があるので await をつけていました。

ここで疑問なんですが、「待ち時間がある」とはどうやって判断すればいいんでしょうか?

コードを見ればいいんでしょうか?毎回?全部?「それは無理!」
なら全部に await をつける?「効率悪」
しかし、ここでPromiseが出てきます。

そう、「待ち時間がある」=「返り値の型がPromise」です。

つまり、返り値の型を見て、「Promise」の場合、その関数は「待ち時間がある」、だから await が必要になる、というわけです。

では、返り値の型をPromiseにするにはどうしたらいいんでしょうか?

どうやって返り値の型をPromiseにするの?

これは簡単です。関数に async をつければいいんです。

// すぐに7を返す
const get7 = async () => {
  return 7
}

const main = async () => {
  const r7 = get7() // r7の型は Promise<number>
  const R7 = await get7() // R7の型はget7()の前に await がついているので number
  
}

get7 はただ7を返すだけ、待ち時間なんてありませんが、async をつけたので返り値の型はPromise<number>になってます。

async をつけると返り値がPromise<xxx>になり、待ち時間があるので await が必要、ということになりますね。

ここまでをまとめると

まとめ その2

  • コードは上から順に処理される
  • 待ち時間があっても、待たずに次の処理が実行されてしまう
  • 待ち時間がある処理の前に await をつけると、その処理が終わるまで次の処理は実行されない
  • 待ち時間のある・なしは、返り値の型が Promise かどうかで分かる
  • 関数に async をつけると返り値の型が Promiseになる

こんな感じです。

本当は Promise のresolveとかrejectとか学んだんですけど、あんまり理解できず・・・なのでその点は飛ばします。

これだけでもなんとなーく async/await のイメージが出てきたと思うので最初のコードの問題点を解決しながらもっと学んでいきたいと思います。

async/await についてもっと学ぼう!

最初の問題の答えはこんなのでした。

// 時間経過用
const sleep = (msec: number) => {
  return new Promise<void>((resolve) => {
    setTimeout(resolve, msec)
  })
}

// 3秒後に3を返す関数
const get3 = async () => {
  await sleep(3000) // 3秒待機
  return 3
}
// 5秒後に5を返す関数
const get5 = async () => {
  await sleep(5000) // 5秒待機
  return 5
}

const main = async () => {
  const r3 = await get3()
  const r5 = await get5()

  console.log(r3 + r5) // 8
}

main()

get3()、get5()の前に await をつけて、上から順に処理されるようにする、というものです。

しかし、そもそもなんですが「上から処理するだけなら await ってなんの意味があるの?」って疑問が出ます。
「最初っからそういう仕様で良くない?」と。

実は await を上手に使うことで待つタイミングをコントロールできます。

await の場所を変えてみよう!

次のようにコードを変更したらどのようにコンソールに出ると思いますか?また、何秒かかると思いますか?

// get3, get5の開始と終わりにログを出すよう変更
const get3 = async () => {
  console.log('get3 Start')
  await sleep(3000) // 3秒待機
  console.log('get3 Finish')
  return 3
}
const get5 = async () => {
  console.log('get5 Start')
  await sleep(5000) // 5秒待機
  console.log('get5 Finish')
  return 5
}

const main = async () => {
  const r3 = await get3() // ①
  const r5 = await get5() // ②

  console.log(r3 + r5) // ③
}

main()

正解は

get3 Start
get3 Finish
get5 Start
get5 Finish
8

つまり

① get3が実行される

② ①の3秒後にget5が実行される

③ ②の5秒後、つまりスタートから8秒後に「8」と出力される

トータルで8秒かかってます。上から順に処理しているから当然ですね。

さて、main関数の部分だけ次のように変更したらどうなるでしょうか。何秒かかると思いますか?

const main = async () => {
  const p3 = get3() // ①
  const p5 = get5() // ②
  const r3 = await p3 // ③
  const r5 = await p5 // ④

  console.log(r3 + r5)
}

実行してみると、次のようにコンソールに出力されます。

get3 Start
get5 Start // get3が終わるよりも前に「get5 Start」と出力されている
get3 Finish
get5 Finish
8

これは

① get3が実行される

①にawait がないのすぐにget5が実行される

③ await があるので、get3の処理が終了するまで3秒待つ

④ awaitがあるので、get5の処理が終了するまで待つ。ただし、get5が実行されたのは②の後なので②から5秒まつ

⑤ スタートから5秒後に「8」と出力される

さっきとは異なりスタートから5秒で「8」と出力されました。

これは await の場所を変えることで、get3とget5を同時に待つことができた、というわけです。

このように async/await を上手に使えば、同時に複数の処理をこなすことができます。便利ですね。

しかし確かに便利ですが、async/await を使うのは非常に難しい、というか難しくなりやすいそうです。

同時にたくさん処理をすればするほど、それぞれの調整が大変になり訳がわからなくなっていきます。
なので使うときは気をつけたいですね。

まとめ

  • コードは上から順に処理される
  • 待ち時間があっても、待たずに次の処理が実行されてしまう
  • 待ち時間がある処理の前に await をつけると、その処理が終わるまで次の処理は実行されない
  • 待ち時間のある・なしは、返り値の型が Promise かどうかで分かる
  • 関数に async をつけると返り値の型が Promiseになる
  • async/await を使うことで同時に複数の処理が行える

以上が、自分が学んだことです。まだ async/await の浅い部分しか理解できていません。今後少しずつ理解していきたいと思います。

ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

コメントは日本語で入力してください。(スパム対策)

CAPTCHA

目次