Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9309

async/await は Promise を置き換えることは出来ると思う

$
0
0

この記事は、以下の記事を読んだ自分の感想です。割と反対意見が多めです。

async/await は Promise を置き換えない

あと、元々 C# 畑の人間なので、この記事で TypeScript を使ってますが、もしかしたら冗長な書き方や、そもそも勘違いをしてしまっている可能性があります。その場合はコメントなどで教えてください。

この記事では Promise を使ったコードを thencatchfinallyなどのメソッドチェーンを使って非同期処理を書いているコードという認識で書いています。

本文

該当記事では、async/await では 書けない処理とされている fan-in/fan-out の例として以下のようなコードが紹介されています。

constfetchProfileImageUrl=(username:string):Promise<URL>=>{...};constdownloadUrl=(url:URL):Promise<ArrayBuffer>=>{...};constcacheUrl=(username:string,url:URL):Promise<void>=>{...};constimageUrl=fetchProfileImageUrl('okapies');constimage=imageUrl.then(url=>downloadUrl(url));imageUrl.then(url=>cacheUrl('okapies',url));

ついでに、これを async/await で書くことで性能が劣化してしまうという指摘されているコードはこれです。

constimageUrl=awaitfetchProfileImageUrl('okapies');constimage=awaitdownloadUrl(imageUrl);awaitcacheUrl('okapies',imageUrl);

そもそもこのコード例は仕様が違います。元の Promise を使った方のコードは fetchProfileImageUrl の戻り値に対して downloadUrl と cacheUrl を並列で呼び出すです。async/await の方は fetchProfileImageUrl を呼び出したら downloadUrl メソッドを呼び出して完了を待ってから cacheUrl を呼び出すです。

async/await を使って fetchProfileImageUrl の処理が終わったあと downloadUrl と cacheUrlを並行して呼び出すならこんな感じです。

constimageUrl=awaitfetchProfileImageUrl('okapies');constresult=awaitPromise.all([downloadUrl(imageUrl),cacheUrl('okapies',imageUrl),]);

ただ、これでも元のコードとはちょっと違って downloadUrl, cacheUrl がどちらかが失敗すると例外が飛んでしまう感じになっています。元の Promise を使ったコードのメソッドはサンプルなので例外処理は入れてないのでしょうが、きっと .cacheや場合によっては .finallyを追加して堅牢な感じに書いていくのだと思います。then や cache や finally が続いて後続処理があるなら async/await の場合はいつもやってるメソッドに切り出してあげるのが素直だと思います。

constdownloadUrlAndSomething=async(imageUrl:URL)=>{try{constimage=awaitdownloadUrl(imageUrl);// downloadUrl 正常終了時の続きの処理// もちろん、ここでも await 使える}catch{// 何かエラー処理// もちろん、ここでも await 使える}finally{// 何か後始末したければ// もちろん、ここでも await 使える}};constcacheUrlAndSomething=async(username:string,imageUrl:URL)=>{try{awaitcacheUrl('okapies',imageUrl);// cacheUrl 正常終了時の続きの処理// もちろん、ここでも await 使える}catch{// 何かエラー処理// もちろん、ここでも await 使える}finally{// 何か後始末したければ// もちろん、ここでも await 使える}};constimageUrl=awaitfetchProfileImageUrl('okapies');awaitPromise.all([downloadUrlAndSomething(imageUrl),cacheUrlAndSomething('okapies',imageUrl),]);

追記

Twiter で教えてもらったのですが ES2020 なら Promise.allSettledを使って全部の非同期処理が終わる(成功・失敗問わず)まで待つとかもできるみたいです。

Promise を使わないと書けない例

個人的には Promise じゃないと書けない例としては constructor の中や index.ts のようなエントリーポイントの中に直接書くようなときは Promise じゃないと書けません。(コンストラクター内で非同期処理呼ぶのどうよ?というのは置いといて)

その場合でも、メソッドに切り出してしまえばメソッド内では await 使えるので最終的には async/await にします。

index.ts
asyncfunctionmain(){// ここは await 使える}main();

個人的な感想

.then.cache.finallyを使った方が素直に書けるケースであれば、使えばいいと思いますが個人的には非常に限られる(自分的には思いつかない…)ので、async/await で置き換えれるところは積極的に async/await を使うといいと思っています。

その上で、元記事で性能が悪いのでダメと言われていたケースのように並行して実行したほうが効率が良いものを並行して実行していないといったケースは Promise.allPromise.anyPromise.raceなどを使って書く方法を覚えるほうが良いと思っています。

fire and forget で例外握り潰しを明示したいときとかは Promise のほうが直感的かも?というのを思ったので最後に書いておきます。

downloadUrl(newURL('https://example.com')).catch(functionignore(){});

fire and forget あたりは、この Stack Overflow あたりが参考になると思います。(void(await xxxx());知らなかった)

Can I fire and forget a promise in nodejs (ES7)?

以上です。


Viewing all articles
Browse latest Browse all 9309

Trending Articles