⭐️🎀 JavaScript Visualized: Promises & Async/Await
JavaScriptのコードを扱っていて、思った通りに動かなかった経験はありますか?関数がランダムな、予測不能なタイミングで実行されたり、実行が遅延したりするように感じたことはありませんか?もしかしたら、ES6によって導入されたクールな新機能、Promisesと関わっている場合があります!
何年か前の私の好奇心が報われて、またしても睡眠不足の夜を利用してアニメーションを作成する時間を得ることができました。今回はなぜPromisesを使うのか、バックエンドでどのように動作するのか、そして最もモダンな方法でPromisesを書くにはどうするかについて話しましょう。
もしまだ私の「JavaScriptのイベントループ」についての前の投稿を読んでなければ、先にそれを読むのが役立つかもしれません!コールスタック、Web API、それにキューについての基本的な知識を前提にしつつ、再びイベントループについてカバーしますが、今回はちょっとワクワクする追加機能についても触れます🤩
既にPromisesに少し慣れている人のために、スクロール時間を節約するためのショートカットをいくつか紹介します。
はじめに
JavaScriptを書くとき、他のタスクに依存するタスクを扱うことがよくあります!例えば、画像を取得して、それを圧縮し、フィルターを適用して保存したいとしましょう 📸
最初にしなければならないことは、編集したい画像を_取得_することです。getImage
関数がこれを処理できます!画像が正常にロードされたら、その値をresizeImage
関数に渡すことができます。画像が正常にリサイズされたら、applyFilter
関数で画像にフィルターを適用します。画像が圧縮されフィルターが追加されたら、画像を保存し、ユーザーにすべてが正しく動作したことを知らせたくなります!🥳
最終的に、こんな感じになります。
うーん...何か気づきますか?それは..._まあまあ_なのですが、素晴らしいわけではありません。多くのネストされたコールバック関数が先のコールバック関数に依存しているため、コードを読むのがかなり難しくなってしまいます!
幸いなことに、今ではPromisesというものがあり、こうしたシチュエーションで助けてくれます!それではPromisesが何なのか、そしてこのような状況でどのように役立つのかを見てみましょう!😃
Promiseの構文
ES6でPromisesが導入されました。多くのチュートリアルではこんなことが書かれています:
「Promiseは、将来的に解決されるか、拒否されるかもしれない値のプレースホルダーです」
はい...その説明は、私に事が明確になるどころか、Promiseが奇妙で曖昧で予測不可能な魔法の一部に思えてきました。それでは、Promisesが_実際に_何なのかを見てみましょう。
Promise
コンストラクタを使用して、コールバックを受け取ることでPromiseを作成できます。オーケー、試してみましょう!
待ってほら、何が返ってきたの?
Promise
は、状態([[PromiseStatus]]
)と値([[PromiseValue]]
)を含むオブジェクトです。上記の例では、[[PromiseStatus]]
の値が"pending"
であり、Promiseの値はundefined
です。
心配しないでください - このオブジェクトとやり取りする必要はありませんし、[[PromiseStatus]]
と[[PromiseValue]]
のプロパティにアクセスすることさえできません!しかし、これらのプロパティの値は、Promisesを使うときに重要です。
PromiseStatus
の値、つまり状態は以下の3つのうちの1つです:
- ✅
fulfilled
: Promiseがresolved
されています。すべてが順調で、Promise内でエラーは発生していません🥳 - ❌
rejected
: Promiseがrejected
されています。ああ、何かが間違っています.. - ⏳
pending
: Promiseはまだ解決も拒否もされていません(まだ)、Promiseはpending
状態です。
このすべては素晴らしく聞こえますが、_いつ_Promiseの状態が"pending"
、"fulfilled"
、または"rejected"
になるのでしょうか?そしてその状態はなぜ重要なのでしょうか?
上記の例では、Promise
コンストラクタに単純なコールバック関数() => {}
を渡しました。しかし、このコールバック関数は実際には2つの引数を受け取ります。最初の引数の値、よくresolve
やres
と呼ばれるものは、Promiseを解決するときに呼び出されるべきメソッドです。2番目の引数の値、よくreject
やrej
と呼ばれるものは、何かが間違っていてPromiseが拒否されるべきときに呼び出されるメソッドです。
resolve
かreject
メソッドを呼び出すとどうなるのかを見てみましょう!私の例ではresolve
メソッドをres
、reject
メソッドをrej
と呼びました。
素晴らしい!ついに"pending"
状態とundefined
値を無くす方法を知りました!resolve
メソッドを呼び出した場合のPromiseの状態は"fulfilled"
であり、rejected
メソッドを呼び出した場合のPromiseの状態は"rejected"
です。
Promiseの値、[[PromiseValue]]
の値、はresolved
またはrejected
メソッドの引数として渡される値です。
興味深い事実ですが、この記事を校正してもらったジェイク・アーカイバルドが、現在Chromeには状態が
"resolved"
ではなく"fulfilled"
と表示されるというバグがあると指摘してくれました。マティアス・ビネンスのおかげで、現在Canaryでは修正されています!🥳🕺🏼
ChromeとSafariではこれを"resolved"とするPromiseを呼びますが、これは真実ですが、少し誤解を招くかもしれません…
09:21 AM - 09 Apr 2020
さて、もう少し具体的に制御可能なあやふやなPromise
オブジェクトについて知ることができました。しかし、これは何に使われるのでしょうか?
はじめに紹介したセクションでは、画像を取得し、圧縮し、フィルターを適用し、保存する例を見ました!最終的に、これはネストされたコールバックの混乱になりました。
幸いにも、Promisesはこれを解決するのに役立ちます!まず、コードブロック全体を書き直して、それぞれの関数がPromise
を返すようにしましょう。
画像がロードされてすべてがうまくいった場合は、ロードされた画像でPromiseを解決しましょう!それ以外の場合、ファイルをロード中にどこかでエラーが発生したら、発生したエラーでPromiseを拒否しましょう。
ターミナルでこれを実行して何が起こるか見てみましょう!
クール!期待通りに解析されたデータの値を持つPromiseが返されました。
しかし...これでどうすれば良いのでしょう?私たちはそのすべてのPromiseオブジェクトに興味がありません、私たちはデータの値だけに興味があります!幸い、Promiseの値を取得するための組み込みメソッドがあります。以下の3つのメソッドをPromiseにアタッチできます:
.then()
: Promiseが_解決_された後に呼び出されます。.catch()
: Promiseが_拒否_された後に呼び出されます。.finally()
: _常に_呼び出されます。Promiseが解決されたか拒否されたかに関わらず。
.then
メソッドは、resolve
メソッドに渡された値を受け取ります。
.catch
メソッドは、rejected
メソッドに渡された値を受け取ります。
ついに、その全てのPromiseオブジェクトなしで解決された値を得ることができました!これで、この値を自由に使えます。
FYI、Promiseが常に解決されるか、常に拒否されることがわかっている場合は、Promise.resolve
またはPromise.reject
を、
こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke