コールバックからの「麺」-より簡単に

最近のトピックをきっかけに、「コールバックから麺が作られていたため、私のスタートアップは離陸しませんでした」というスタイルの絶え間ない話を聞きました。



つい最近、私は小さなプロジェクトを完成させました(リンクを提供しないので、誰がプロファイルを見る必要があるかを疑わないようにします)。 もちろん、悪名高い「麺」の問題に遭遇しました。 そして、あなたはそれを信じないでしょう、私はフレームワークやトリックなしで冷静にそれを解決しました。



そこで、データベースから書籍の数を非同期で選択し、データベースから必要な書籍のスタックを非同期で選択し、データベースから書籍のメタデータを非同期で選択し、これをすべて単一のデータセットに減らしてテンプレートをレンダリングするというタスクがあるとします。 それは通常どのように見えますか?







exports.processRequest = function (request, response) { db.query('SELECT COUNT(id) FROM books', function (res1) { // do something db.query('SELECT * FROM books LIMIT ' + Number(limit) + ' OFFSET' + Number(offset), function (res2) { // do something 2 db.query('SELECT * FROM bookData WHERE bookId IN (' + ids.join(', ') + ')', function (res3) { //     - dataset response.write(render(dataset)); }); }); }); }
      
      







さらに2、3の中間ステップを実行すると、すべてが非常に悪くなります。

では、簡単な質問をしてみましょう。なぜこの麺を書いたのですか? ここでは、3つのネストされたクロージャーが本当に必要ですか?



いいえ、もちろんです。 3番目の匿名関数から、2番目と1番目のクロージャーにアクセスする必要はまったくありません。 少しのコードを書き直してください:



 exports.processRequest = function (request, response) { var dataset = {}; getBookCount(); function getBookCount () { db.query('SELECT COUNT(id) FROM books', onBookCountReady); } function onBookCountReady (res) { // ... dataset getBooks(); } function getBooks () { db.query('SELECT * FROM books LIMIT ' + dataset.limit + ' OFFSET' + dataset.offset, onBooksReady); } function onBooksReady (res) { // ...  dataset getMetaData(); } function getMetaData () { db.query('SELECT * FROM bookData WHERE bookId IN (' + dataset.ids.join(', ') + ')', onMetaDataReady); } function onMetaDataReady (res) { // ...  dataset finish(); } function finish () { response.write(render(dataset)); } }
      
      







お願いします。 コードは完全に線形になり、重要なことに、より構造化されました。 プログラムフロー全体が目の前にあるため、コードの論理ブロックには個別の関数が装飾されています。 フレームワークやトリッキーな構文はありません。 落とし穴はありません。リクエストとレスポンスのペアを処理するコンテキストでデータセットが閉じられているため、リクエスト間で共有されているデータに誤ってアクセスすることはできません。



何かを並列化する必要がある場合、すべてが少し複雑になります。 このようなタスクはありませんでしたが、もしあれば(たとえば、メタデータのセットが2つある場合)、次のように解決します。



  function getMetaData () { var parallelExecutor = new ParallelExecutor({ meta1: getMetaData1, meta2: getMetaData2 }); function getMetaData1 () { db.query('smthng', onMetaData1Ready); } function getMetaData2 () { db.query('smthng', onMetaData2Ready); } function onMetaData1Ready (res) { //  dataset parallelExecutor.ready('meta1'); } function onMetaData2Ready (res) { //  dataset parallelExecutor.ready('meta2'); } parallelExecutor.start(onMetaDataReady); } function onMetaDataReady () { }
      
      







意味は同じです-関数のセットに対して個別のクロージャーを作成し、通常は「ヌードル」に結合し、それらを順番にペイントします。



この形式では、非同期コールバックはコードを乱雑にするだけでなく、逆にコードを構造化して読みやすくするようです。



All Articles