AsyncはJavaScriptの後退を待っていますか?





2015年の終わりに、JavaScriptの世界に侵入して約束チェーンの地獄から私たちを救うこのキーワードのペアについて耳にしました。 非同期/待機状態になった方法を理解するために、いくつかの例を見てみましょう。



APIに取り組んでおり、一連の非同期操作でリクエストに応答する必要があるとします。

-ユーザーの有効性を確認する

-データベースからデータを収集する

-外部サービスからデータを取得する

-データを変更してデータベースに書き戻す



また、時間をさかのぼり、コールバック関数を使用してリクエストを処理するため、Promiseの知識がないと仮定します。 ソリューションは次のようになります。



function handleRequestCallbacks(req, res) { var user = req.user isUserValid(user, function (err) { if (err) { res.error('An error ocurred!') return } getUserData(user, function (err, data) { if (err) { res.error('An error ocurred!') return } getRate('service', function (err, rate) { if (err) { res.error('An error ocurred!') return } const newData = updateData(data, rate) updateUserData(user, newData, function (err, savedData) { if (err) { res.error('An error ocurred!') return } res.send(savedData) }) }) }) }) }
      
      





そして、これはいわゆるコールバック地獄です。 あなたは今彼に精通しています。 読むこと、デバッグすること、変更することは難しいため、誰もが彼を嫌っています。 ネストの深化、エラー処理は各レベルで繰り返されます。



有名な非同期ライブラリを使用して、コードを少しクリアできます。 エラー処理は少なくとも1つの場所にあるため、コードは改善されます。



 function handleRequestAsync(req, res) { var user = req.user async.waterfall([ async.apply(isUserValid, user), async.apply(async.parallel, { data: async.apply(getUserData, user), rate: async.apply(getRate, 'service') }), function (results, callback) { const newData = updateData(results.data, results.rate) updateUserData(user, newData, callback) } ], function (err, data) { if (err) { res.error('An error ocurred!') return } res.send(data) }) }
      
      





その後、私たちは約束の使い方を学び、世界はもう怒っていないと考えました。 ますます多くのライブラリも約束の世界に移行しているため、コードを再度リファクタリングする必要があると感じました。



 function handleRequestPromises(req, res) { var user = req.user isUserValidAsync(user).then(function () { return Promise.all([ getUserDataAsync(user), getRateAsync('service') ]) }).then(function (results) { const newData = updateData(results[0], results[1]) return updateUserDataAsync(user, newData) }).then(function (data) { res.send(data) }).catch(function () { res.error('An error ocurred!') }) }
      
      





以前よりもずっと良く、ずっと短く、ずっときれいです! ただし、大量のthen()呼び出し、関数(){...}ブロック、および複数のreturnステートメントをどこにでも追加する必要があるという形で、オーバーヘッドが大きすぎました。



最後に、JavaScriptで導入されたこれらすべての新しい機能について、ES6について耳にします:矢印関数のように(そしてそれを少し楽しくするために少し破壊します)。 美しいコードにもう一度チャンスを与えることにしました。



 function handleRequestArrows(req, res) { const { user } = req isUserValidAsync(user) .then(() => Promise.all([getUserDataAsync(user), getRateAsync('service')])) .then(([data, rate]) => updateUserDataAsync(user, updateData(data, rate))) .then(data => res.send(data)) .catch(() => res.error('An error ocurred!')) }
      
      





そしてここにある! この要求ハンドラーはクリーンで読みやすくなりました。 ストリームに何かを追加、削除、またはスワップする必要がある場合は、簡単に変更できることを理解しています! さまざまな非同期操作を使用して収集するデータを1つずつ変更する関数のチェーンを形成しました。 この状態を保存するための中間変数は定義していません。エラー処理は理解できる1つの場所にあります。 これでJavaScriptの高さに間違いなく到達したと確信できます。 まだですか?



そして、非同期/待機が来る



数か月後、非同期/待機がシーンに入ります。 彼はES7仕様に入るつもりだったので、アイデアは延期されましたが、 バベルがあり、電車に飛び乗った。 関数を非同期としてマークできること、およびこのキーワードにより、コードが再び同期して見えると約束が決定するまで、関数内の実行フローを「停止」できることがわかりました。 さらに、非同期関数は常にプロミスを返し、try / catchブロックを使用してエラーを処理できます。



メリットにあまり自信がないので、コードに新しいチャンスを与え、最終的な再編成を行います。



 async function asyncHandleRequest(req, res) { try { const { user } = req await isUserValidAsync(user) const [data, rate] = await Promise.all([getUserDataAsync(user), getRateAsync('service')]) const savedData = await updateUserDataAsync(user, updateData(data, rate)) res.send(savedData) } catch (err) { res.error('An error ocurred!') } }
      
      





そして今、コードは再び古い通常の命令型同期コードのように見えます。 人生はいつものように続きましたが、あなたの頭の奥深くから何かがここで間違っていることがわかります...



関数型プログラミングのパラダイム



関数型プログラミングは私たちの周りに40年以上存在しますが、最近ではパラダイムが勢いを増し始めているようです。 そしてごく最近、機能的アプローチの利点を理解し始めました。



その原則のいくつかを教え始めます。 ファンクタ、モナド、モノイドなどの新しい単語を学習します。そして、突然、開発者の友人は、これらの奇妙な単語を頻繁に使用するため、私たちをクールだと考え始めます。



関数型プログラミングのパラダイムの海への航海を続け、その真の価値を見始めます。 関数型プログラミングのこれらの支持者は、ただ狂っただけではありませんでした。 彼らはおそらく正しかった!



状態を保存または変更しない、単純な関数を組み合わせて複雑なロジックを作成する、ループ制御と言語インタープリター自体によって行われるすべての魔法を回避する、不変の利点を理解しているため、本当に重要なことに集中でき、分岐を避けることができますより多くの機能を単に組み合わせることによるエラー処理。



しかし...待って!



これらの機能モデルはすべて過去に見てきました。 状態を管理したり、コードを分岐したり、エラーを命令的なスタイルで管理したりせずに、Promiseをどのように使用し、機能変換を1つずつ組み合わせたかを覚えています。 私たちは過去に約束のモナドをすべての付随的な利点とともにすでに使用していましたが、その時点ではその言葉を知らなかっただけです!



そして、非同期/待機ベースのコードが奇妙に見える理由を突然理解します。 結局、80年代のように、通常の命令型コードを作成しました。 90年代のように、try / catchでエラーを処理しました。 同期のように見えるが、突然停止し、非同期操作が完了すると自動的に再開するコードを使用して非同期操作を行うことにより、内部状態と変数を制御しました(認知的不協和?)。



最後の考え



誤解しないでください、非同期/待機は世界のすべての悪の源ではありません。 私は実際に数ヶ月使用した後にそれを愛することを学びました。 強制的なコードを書くことに抵抗がない場合、非同期操作を制御するためにasync / awaitを使用する方法を学ぶことは良い動きです。



しかし、約束が好きで、より多くの機能的なプログラミングの原則を適用する方法を学びたい場合は、非同期/待機をスキップし、命令的な思考をやめ、新しい機能的なパラダイムに進むことができます。



こちらもご覧ください



async / awaitはそれほど良いことではないという別の意見



All Articles