🧐

Async / Awaitを使う時、Asyncファンクションは必ずPromiseが返る

2021/08/26に公開

Promise

軽くPromiseの話からします。
ある程度分かっている人であれば読み飛ばして大丈夫です。

Promiseは、JavaScriptで非同期処理を行う際に使われるパターンで
外部通信や、データの取得など、同期的に動いてる処理の中に割り込ませる形で使います。

データの取得や通信で取得に完了していない状態で、次の処理に移った場合
次の処理の中に、通信によって得られたデータを使った処理が含まれていると、正常に動作しない事があります。

例えば以下の様な状況

function getUserAge() {
  const user = getUser(1) // 外部情報を取得
  return user.age
}

idが1のユーザーを取得し、そのageを取得する関数があったとして
この時、ユーザーは外部APIなどを通して取得する必要がありましたが、取得に時間がかかってしまった時
user.ageは、userが取得される前にreturnされる事になります。

これは割と極端な例ですが、このように本来取得が終わってからreturnしたい処理が、取得が終わる前に進んでしまうのが非同期処理の動きです。

これをちゃんと取得が終わってから次の処理に進めるためにあるのが、Promiseになります。
直訳すると「約束」という事になりますが、ちゃんと約束が結ばれて(もしくは裏切られて)初めて話が進むっていうイメージです。

then関数などを使って、取得後の情報をコールバックで受け取って行く方法もありますが、ここでは割愛します。

今回、このPromiseを解消するasync/awaitで詰まった事があったので書いておこうと思います。

async関数の返り値

asyncは、ファンクション名の前につき、async functionという感じで使います。
コールバック関数にもasyncをつける事はできます。

awaitはasync functionの中でしか使えず、取得を「待つ」ということになります。

この時、async functionの中でreturnしている場合、Promiseとして返されます。
先ほどの例を参考に、以下のような感じ

async function getUserAge() {
  const user = await getUser(1)
  return user.age
}

idがreturnされていますが、このidはPromiseで包まれた状態で返されます。
参考:
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function

ここで気付いた人もいるかもしれないですが、
awaitはasync関数の中でしか使えず、promiseはawaitで解消しなくてはいけない。
これはつまり、thenによるチェーンのように解消をthenして返されたpromiseでさらにthenしていく連なりと同じですね。

内部的にジェネレーターのように実行する順番をとっている感じです。

returnで返されるPromiseのジェネリクス

async内でreturnされるPromiseのジェネリクスは、その関数が返す型になるようです。
つまり、stringを返す場合、Promise<string>となります。

先ほどの例を使うと以下のようになります。

async function getUserAge(): Promise<number> {
  const user = await getUser(1)
  return user.age
}

async関数内で行うこと

順番をとって、順に実行していく関数として使う分には普通にreturnすればいいのですが
returnしないasync functionもありPromise<void>として定義します。
参考:
https://newbedev.com/typescript-async-function-return-type-void-vs-promise-void

returnしないasync関数の中でやる事として主に行う事として
・外部通信
・グローバルステートの更新
があると思います。

その中でもグローバルステートの更新がメインになるかと思います。
基本的に、フロントエンドでasync functionを使い回していく手法は取らず
グローバルステートを使う事で一つ一つのfunctionで完結していくような手法を取る事が多いかと思います。
そういった時に、Promiseの解消のためasync functionにするという考えがメインになりそうです。

自分としては元々asyncは、awaitを使いpromiseを解消するために使っていたのですが
仕様的にasyncでreturnする場面があり、asyncでのreturnだとPromiseでラップされるという事を知らなかったため、少し戸惑ったという経緯がありました。

参考

https://qiita.com/jun1s/items/ecf6965398e00b246249

Discussion