非同期の理解

JavaScriptの非同期って話になると、コールバック、Promise、async/await が出てくると思います。解説記事とかもたくさんあるけど、ちょっと理解が難しいかもしれないので改めて使う側の視点で必要なことを整理してみる。

今時の async/await で書かれているコードに対して、なんでなの?とか理解できてないなぁという時には、promiseのことやコールバックのことを理解するとスッキリするからね。

非同期ってそもそも何がしたいのか。

待ちが必要な処理を待っている間に他の処理をしたい。

コールバック

これは非同期処理をトリガーする側が、「おわったらこの関数を実行してね。」とするパターン。

See the Pen post174-1 by 男メイド (@danmaid) on CodePen.

上の例では、setTimeoutという非同期処理に、「終わったら then を実行してね」と伝えて実行している。

気にしておくべきは、呼ぶ側と、thenは直接的な関係を持たないので、値の受け渡しはそのままではできないってこと。つまり、setTimeoutの本来欲しい結果はthenには渡せるけど、呼び出し元では使えないってこと。

コールバックが嫌われるのは、非同期を多用した場合、ネストがどんどん深くなるか、いちいちfunctionを分けて書く事になって、可読性がひどい事になっちゃうから。コールバック地獄ってやつ。

ちなみに、callbackへ渡す引数は、ある程度パターンが決められている。それは、エラーが発生した結果なのか、正常終了した結果なのか。を別々の引数として渡すことが多い。

Promise

非同期やりたい奴多いし、コールバックの結果は正常終了かエラーかなんだし、ここらで統一のルール決めちゃったら幸せになれる人いるんじゃない? と、いうのがPromise。

ルール

  • 非同期関数は呼び出されたときの戻り値にPromiseオブジェクトを返します。
  • 正常終了時はresolve(値)、エラー時はreject(値)を呼びます。(厳密には第1引数と第2引数。)
  • resolveの時はthen(値)を実行します。rejectの時はcatch(値)を実行します。

これらを約束します!(だからPromiseっていう名前なのだと思う。)

See the Pen post174-2 by 男メイド (@danmaid) on CodePen.

そう、Promiseっていうのは、コールバックの時は眼中になかった、実行時にすぐ帰ってくる時の戻り値をつかおーぜ!っていう発想。そこに関数の状態を管理する仕組みを組み込んだものである。

まぁ、さらにPromiseの凄いところは、複数の非同期関数を束ねて、全部終わったらとか、順番にとか、制御する仕組みを最初から用意してくれているあたりにグッときますね。Promise.allとかPromise.raceとかいくつかある。

たぶん、Promiseがあると便利だよなぁってなってきたきっかけは、コールバック地獄が目に余るようになってきたからじゃないかな。allとかraceの需要からかもしれないけど。なんでコールバック地獄が目に余るようになったかというと、アローの登場じゃないかと思っている。func(function cb() {}) だったものが func(() => {}) で書けるようになって多用されてきたんじゃないかと。もちろん従来からちゃんと考えている人はfunc(function cb() {}) って書き方して地獄っていたのだろうけど。一方ではメソッドチェーンの流行なんかもあるかもねぇ。

async/await

たぶん、他の言語がasync/await構文を取り入れてから真似したんだろうと思う(私はC#で初めてasync/awaitに出会って感動しました)。 でもね、コールバック = Promise = async/await で書き方の違いだけやろ!って思っていたらやけどするので注意です。

何がわかりにくくしているかというと、たぶん、await は async 関数の中でしか使えない。ってことだと思います。まぁこれほかの言語も大体そんなもんです。つまり、今までの例では async/await 使えないんです。ここが混乱するポイントだと思ってます。

今までの例では、非同期処理であるsetTimeoutが終わった後にthenを実行していました。でも、実際は非同期処理が終わった後にその結果をもって処理を行いたいケースが多いと思います。そのケースを見てみます。

非同期処理結果を使う コールバック版

See the Pen post174-3 by 男メイド (@danmaid) on CodePen.


非同期処理結果を使う Promise版

See the Pen post174-4 by 男メイド (@danmaid) on CodePen.

なんでこう書けるかというと、Promise.then っていうのは Promise を返すからなんです。.then(func) の時、funcの戻り値を Promise にして返してくれる。だからメソッドチェーンができるんですね。

async/await はどう使うかっていうと、async は promise を返す。 await は async の中で promise を待機する。ってものなので、真ん中のところで使うことができる。図で言うと処理3のところでしか使えませんね。

See the Pen post174-5 by 男メイド (@danmaid) on CodePen.

全然便利じゃないじゃん!!と思うかもしれないけど、実際に使おうと思ったら結構便利に使えるものなんです。それは、非同期系が軒並み Promise に対応してくれているからなので、実例を挙げてみます。

async/await の実例

fetchで何かとってきてごにょる

async/await 版

See the Pen post174-6 by 男メイド (@danmaid) on CodePen.

Promise 版

See the Pen post174-7 by 男メイド (@danmaid) on CodePen.

まとめ

何を使うかはお好きにどうぞ。try/catch と組み合わせるとか、用途によって何がいいってのは色々ですから。あとは Jest や express といったフレームワークと組み合わせたときにどうするかとかね。async/await も基本は Promise だし、Promise も基本はコールバックなので、ある程度しっかり理解しておいたほうが困らないと思います。

以上、参考になれば。