Node.jsで非同期関数の長いネストを回避する方法


158

DBからのデータを表示するページを作成したいので、DBからデータを取得する関数をいくつか作成しました。私はNode.jsの初心者なので、理解している限り、すべてを1つのページ(HTTP応答)で使用したい場合は、すべてをネストする必要があります。

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

そのような関数がたくさんあると、入れ子が問題になります

これを回避する方法はありますか?複数の非同期関数を組み合わせる方法に関係していると思います。これは基本的なことのようです。


12
では、10個の非同期関数がある場合、10レベルのインデントがありますか?
ケイペール

このリンクが役立つ場合があります。stackoverflow.com/a/4631909/290340
Evan Plaice

1
別の問題:間に別の関数を挿入するgetSomeDategetSomeOtherDate、多くの行のインデントが変更されてgitの履歴が読みにくくなり(git blameこの後は役に立たなくなります)、これを手動で行うとバグが発生する可能性があります
Daniel Alder

回答:


73

興味深い観察。JavaScriptでは、通常、インラインの匿名コールバック関数を名前付きの関数変数に置き換えることができます。

以下:

http.createServer(function (req, res) {
   // inline callback function ...

   getSomeData(client, function (someData) {
      // another inline callback function ...

      getMoreData(client, function(moreData) {
         // one more inline callback function ...
      });
   });

   // etc ...
});

次のように書き直すことができます。

var moreDataParser = function (moreData) {
   // date parsing logic
};

var someDataParser = function (someData) {
   // some data parsing logic

   getMoreData(client, moreDataParser);
};

var createServerCallback = function (req, res) {
   // create server logic

   getSomeData(client, someDataParser);

   // etc ...
};

http.createServer(createServerCallback);

ただし、他の場所でコールバックロジックを再利用することを計画していない限り、例のように、インラインの匿名関数を読み取る方がはるかに簡単です。また、すべてのコールバックの名前を見つける必要がなくなります。

さらに、下のコメントで@pstが指摘しているように、内部関数内のクロージャー変数にアクセスしている場合、上記は単純な変換ではないことに注意してください。そのような場合は、インラインの無名関数を使用することがさらに望ましいです。


26
ただし、ネストされていない場合(そして実際にトレードオフを理解するためだけに)、変数に対するクロージャーセマンティクスの一部が失われる可能性があるため、直接変換ではありません。上記の例でgetMoreDataは、「res」へのアクセスが失われています。

2
私はあなたの解決策が壊れていると思います:someDataParserはも呼び出すので、実際にはすべてのデータを解析しますgetMoreData。その意味で、関数名は正しくなく、ネストの問題を実際に削除していないことが明らかになります。
Konstantin Schubert

63

ケイ、これらのモジュールの1つを使用してください。

これは次のようになります。

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', 'bobvance@potato.egg', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

これに:

flow.exec(
    function() {
        dbGet('userIdOf:bobvance', this);

    },function(userId) {
        dbSet('user:' + userId + ':email', 'bobvance@potato.egg', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);

9
flow-js、step、asyncをざっと見て、関数の実行順序のみを処理しているようです。私の場合、すべてのインデントでインラインクロージャ変数にアクセスできます。したがって、たとえば、関数は次のように機能します:HTTP要求を取得し、CookieのDBからユーザーIDを取得し、後のユーザーIDの電子メールを取得し、後の電子メールのデータを取得し、...、後のYにXを取得、...私が間違っていない場合、これらのフレームワークは非同期関数が正しい順序で実行されることを保証するだけですが、すべての関数本体で、クロージャーによって自然に提供される変数を取得する方法はありません(?)ありがとう:)
Kay Pale

9
これらのライブラリのランク付けに関しては、Githubでそれぞれの「スター」の数を確認しました。asyncは約3000で最も多く、Stepは約1000で次で、他は大幅に少なくなっています。もちろん、すべてが同じことを行うわけではありません:-)
kgilpin

3
@KayPale私はasync.waterfallを使用する傾向があり、次のステップに必要なものを渡す各ステージ/ステップごとに独自の関数を使用したり、async.METHOD呼び出しの前に変数を定義してダウンラインを利用できるようにします。また、METHODNAME.bind(...)を私のasync。*呼び出しに使用しますが、これもかなりうまくいきます。
Tracker1 2013年

簡単な質問:モジュールのリストで、最後の2つは同じですか?つまり、「async.js」と「async」
dari0h

18

ほとんどの場合、私はダニエル・ヴァッサロに同意します。複雑で深くネストされた関数を個別の名前付き関数に分割できる場合、それは通常良い考えです。単一の関数内で行うのが理にかなっている場合は、利用可能な多くのnode.js非同期ライブラリーの1つを使用できます。人々はこれに取り組むための多くの異なる方法を思いついたので、node.jsモジュールのページを見て、あなたの考えを見てください。

このためのモジュール、async.jsを自分で作成しました。これを使用して、上記の例を次のように更新できます。

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  async.series({
    someData: async.apply(getSomeDate, client),
    someOtherData: async.apply(getSomeOtherDate, client),
    moreData: async.apply(getMoreData, client)
  },
  function (err, results) {
    var html = "<h1>Demo page</h1>";
    html += "<p>" + results.someData + "</p>";
    html += "<p>" + results.someOtherData + "</p>";
    html += "<p>" + results.moreData + "</p>";
    res.write(html);
    res.end();
  });
});

このアプローチの良い点の1つは、「series」関数を「parallel」に変更することで、コードをすばやく変更してデータを並列でフェッチできることです。さらに、async.jsはブラウザー内でも機能するため、トリッキーな非同期コードが発生した場合は、node.jsと同じメソッドを使用できます。

お役に立てれば幸いです。


こんにちはCaolanと答えてくれてありがとう!私の場合、すべてのインデントでインラインクロージャ変数にアクセスできます。したがって、たとえば、関数は次のように機能します:HTTP要求を取得し、CookieのDBからユーザーIDを取得し、後のユーザーIDの電子メールを取得し、後の電子メールのデータを取得し、...、後のYにXを取得、...私が間違っていない場合、提案するコードは非同期関数が正しい順序で実行されることを保証するだけですが、すべての関数本体で、元のコードのクロージャーによって変数が自然に提供される方法はありません。それは事実ですか?
ケイペール

3
あなたが達成しようとしていることは、構造的にデータパイプラインと呼ばれています。このような場合は、非同期ウォーターフォールを使用できます。
Rudolf Meijering 2012年

18

このトリックは、ネストされた関数やモジュールではなく配列で使用できます。

目に優しいです。

var fs = require("fs");
var chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step3");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step4");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step5");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("done");
    },
];
chain.shift()();

並列プロセスまたは並列プロセスチェーンのイディオムを拡張できます。

var fs = require("fs");
var fork1 = 2, fork2 = 2, chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        var next = chain.shift();
        fs.stat("f2a.js",next);
        fs.stat("f2b.js",next);
    },
    function(err, stats) {
        if ( --fork1 )
            return;
        console.log("step3");
        var next = chain.shift();

        var chain1 = [
            function() { 
                console.log("step4aa");
                fs.stat("f1.js",chain1.shift());
            },
            function(err, stats) { 
                console.log("step4ab");
                fs.stat("f1ab.js",next);
            },
        ];
        chain1.shift()();

        var chain2 = [
            function() { 
                console.log("step4ba");
                fs.stat("f1.js",chain2.shift());
            },
            function(err, stats) { 
                console.log("step4bb");
                fs.stat("f1ab.js",next);
            },
        ];
        chain2.shift()();
    },
    function(err, stats) {
        if ( --fork2 )
            return;
        console.log("done");
    },
];
chain.shift()();

15

私はこの目的のためにasync.jsがとても好きです

この問題はウォーターフォールコマンドで解決されます。

ウォーターフォール(タスク、[コールバック])

関数の配列を直列に実行し、それぞれの結果を配列の次の関数に渡します。ただし、いずれかの関数がコールバックにエラーを渡した場合、次の関数は実行されず、メインコールバックがすぐにエラーで呼び出されます。

議論

tasks-実行する関数の配列。各関数には、完了時に呼び出す必要があるコールバック(err、result1、result2、...)が渡されます。最初の引数はエラー(nullの可能性があります)であり、それ以降の引数はすべて、次のタスクへの引数として渡されます。callback(err、[results])-すべての関数が完了すると実行されるオプションのコールバック。これには、最後のタスクのコールバックの結果が渡されます。

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'    
});

req、res変数については、async.waterfall呼び出し全体を囲んだfunction(req、res){}と同じスコープ内で共有されます。

それだけでなく、非同期は非常にクリーンです。私が意味することは、私はこのような多くのケースを変更するということです:

function(o,cb){
    function2(o,function(err, resp){
        cb(err,resp);
    })
}

最初に:

function(o,cb){
    function2(o,cb);
}

次にこれに:

function2(o,cb);

次にこれに:

async.waterfall([function2,function3,function4],optionalcb)

また、非同期用に準備された多くの既成の関数をutil.jsから非常に高速に呼び出すことができます。やりたいことをつなぎ合わせるだけで、o、cbが普遍的に処理されることを確認してください。これにより、コーディングプロセス全体が大幅にスピードアップします。


11

必要なのは、少し構文上の砂糖です。これをチェックしてください:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = ["<h1>Demo page</h1>"];
  var pushHTML = html.push.bind(html);

  Queue.push( getSomeData.partial(client, pushHTML) );
  Queue.push( getSomeOtherData.partial(client, pushHTML) );
  Queue.push( getMoreData.partial(client, pushHTML) );
  Queue.push( function() {
    res.write(html.join(''));
    res.end();
  });
  Queue.execute();
}); 

かなりすっきりしましたね。htmlが配列になったことに気付くでしょう。これは一部には文字列が不変であるため、より大きな文字列を破棄するよりも、出力を配列にバッファリングする方が得策です。もう1つの理由は、bind

Queueこの例では、ほんの一例にすぎず、partial次のように実装できます。

// Functional programming for the rescue
Function.prototype.partial = function() {
  var fun = this,
      preArgs = Array.prototype.slice.call(arguments);
  return function() {
    fun.apply(null, preArgs.concat.apply(preArgs, arguments));
  };
};

Queue = [];
Queue.execute = function () {
  if (Queue.length) {
    Queue.shift()(Queue.execute);
  }
};

1
Queue.execute()は、非同期呼び出しの結果を待たずに、単に部分的に次々と実行します。
ngn

ありがとうございます。答えを更新しました。ここにテストがあります:jsbin.com/ebobo5/edit(オプションlast機能付き)
gblazex 2010年

こんにちはgalambalazsと答えてくれてありがとう!私の場合、すべてのインデントでインラインクロージャ変数にアクセスできます。したがって、たとえば、関数は次のように機能します:HTTP要求を取得し、CookieのDBからユーザーIDを取得し、後のユーザーIDの電子メールを取得し、後の電子メールのデータを取得し、...、後のYにXを取得、...私が間違っていない場合、提案するコードは非同期関数が正しい順序で実行されることを保証するだけですが、すべての関数本体で、元のコードのクロージャーによって変数が自然に提供される方法はありません。それは事実ですか?
ケイペール

1
さて、あなたは間違いなくすべての答えで閉鎖を失います。できることは、共有データのグローバルスコープにオブジェクトを作成することです。したがって、たとえば、最初の関数は追加obj.emailし、次の関数はobj.emailそれを使用してから削除します(または単に割り当てるnull)。
gblazex 2010

7

私がそれを見つけてからずっとAsync.jsが大好きです。それは持っているasync.seriesあなたが長いネストを避けるために使用することができる機能を。

ドキュメンテーション:-


シリーズ(タスク、[コールバック])

関数の配列を連続して実行します。前の関数が完了すると、それぞれが実行されます。[...]

議論

tasks-実行する関数の配列。各関数には、完了時に呼び出す必要があるコールバックが渡されます。 callback(err, [results])-すべての関数が完了したら実行するオプションのコールバック。この関数は、配列で使用されるコールバックに渡されるすべての引数の配列を取得します。


これをサンプルコードに適用する方法は次のとおりです。

http.createServer(function (req, res) {

    res.writeHead(200, {'Content-Type': 'text/html'});

    var html = "<h1>Demo page</h1>";

    async.series([
        function (callback) {
            getSomeData(client, function (someData) { 
                html += "<p>"+ someData +"</p>";

                callback();
            });
        },

        function (callback) {
            getSomeOtherData(client, function (someOtherData) { 
                html += "<p>"+ someOtherData +"</p>";

                callback(); 
            });
        },

        funciton (callback) {
            getMoreData(client, function (moreData) {
                html += "<p>"+ moreData +"</p>";

                callback();
            });
        }
    ], function () {
        res.write(html);
        res.end();
    });
});

6

私が見た最も単純な構文上の砂糖はノードの約束です。

npm install node-promise || git clone https://github.com/kriszyp/node-promise

これを使用すると、非同期メソッドを次のようにチェーンできます。

firstMethod().then(secondMethod).then(thirdMethod);

それぞれの戻り値は、次の引数として利用できます。


3

ここで行ったことは、非同期パターンを取得し、それを順番に呼び出される3つの関数に適用します。各関数は、前の関数が完了する前に完了するのを待ってから開始します。つまり、関数を同期させます。非同期プログラミングのポイントは、一度に複数の関数を実行でき、それぞれが完了するのを待つ必要がないことです。

getSomeDate()がgetSomeOtherDate()に何も提供せず、getMoreData()に何も提供しない場合は、jsが許可するように非同期に呼び出したり、相互に依存している(非同期ではない)場合は、単機能?

フローを制御するためにネストを使用する必要はありません。たとえば、3つすべてが完了したことを判断して応答を送信する共通の関数を呼び出して、各関数を終了させます。


2

あなたがこれを行うことができると仮定します:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var html = "<h1>Demo page</h1>";
    chain([
        function (next) {
            getSomeDate(client, next);
        },
        function (next, someData) {
            html += "<p>"+ someData +"</p>";
            getSomeOtherDate(client, next);
        },
        function (next, someOtherData) {
            html += "<p>"+ someOtherData +"</p>";
            getMoreData(client, next);
        },
        function (next, moreData) {
            html += "<p>"+ moreData +"</p>";
            res.write(html);
            res.end();
        }
    ]);
});

chain()を実装するだけで、各関数が次の関数に部分的に適用され、すぐに最初の関数のみが呼び出されます。

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}

こんにちはngnと答えてくれてありがとう!私の場合、すべてのインデントでインラインクロージャ変数にアクセスできます。したがって、たとえば、関数は次のように機能します:HTTP要求を取得し、CookieのDBからユーザーIDを取得し、後のユーザーIDの電子メールを取得し、後の電子メールのデータを取得し、...、後のYにXを取得、...私が間違っていない場合、提案するコードは非同期関数が正しい順序で実行されることを保証するだけですが、すべての関数本体で、元のコードのクロージャーによって変数が自然に提供される方法はありません。それは事実ですか?
ケイペール

2

コールバック地獄は、クロージャー付きの純粋なJavaScriptで簡単に回避できます。以下のソリューションでは、すべてのコールバックがfunction(error、data)シグネチャに従うと想定しています。

http.createServer(function (req, res) {
  var modeNext, onNext;

  // closure variable to keep track of next-callback-state
  modeNext = 0;

  // next-callback-handler
  onNext = function (error, data) {
    if (error) {
      modeNext = Infinity;
    } else {
      modeNext += 1;
    }
    switch (modeNext) {

    case 0:
      res.writeHead(200, {'Content-Type': 'text/html'});
      var html = "<h1>Demo page</h1>";
      getSomeDate(client, onNext);
      break;

    // handle someData
    case 1:
        html += "<p>"+ data +"</p>";
        getSomeOtherDate(client, onNext);
        break;

    // handle someOtherData
    case 2:
      html += "<p>"+ data +"</p>";
      getMoreData(client, onNext);
      break;

    // handle moreData
    case 3:
      html += "<p>"+ data +"</p>";
      res.write(html);
      res.end();
      break;

    // general catch-all error-handler
    default:
      res.statusCode = 500;
      res.end(error.message + '\n' + error.stack);
    }
  };
  onNext();
});

1

私は最近、(ファイバーに基づいて)同期モードで非同期関数を呼び出すために、wait.forというより単純な抽象化を作成しました。それは初期段階ですが、動作します。それは:

https://github.com/luciotato/waitfor

wait.forを使用すると、同期関数であるかのように、標準のnodejs非同期関数を呼び出すことができます。

コードのwait.forの使用は次のようになります。

var http=require('http');
var wait=require('wait.for');

http.createServer(function(req, res) {
  wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning
}).listen(8080);


//in a fiber
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  var someData = wait.for(getSomeDate,client);
  html += "<p>"+ someData +"</p>";
  var someOtherData = wait.for(getSomeOtherDate,client);
  html += "<p>"+ someOtherData +"</p>";
  var moreData = wait.for(getMoreData,client);
  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
};

...または、より冗長にしたくない場合(およびエラーキャッチを追加する場合)

//in a fiber
function handleRequest(req, res) {
  try {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>"
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>"
    + "<p>"+ wait.for(getMoreData,client) +"</p>"
    );
    res.end();
  }
  catch(err) {
   res.end('error '+e.message); 
  }

};

すべての場合において、getSomeDategetSomeOtherDate、およびgetMoreData は標準の非同期関数であり、最後のパラメーターは関数callback(err、data)です。

のように:

function getMoreData(client, callback){
  db.execute('select moredata from thedata where client_id=?',[client.id],
       ,function(err,data){
          if (err) callback(err);
          callback (null,data);
        });
}

1

この問題を解決するために、私はnodent(https://npmjs.org/package/nodent)を作成しました。あなたのサンプルコードは(非同期、本当に-ドキュメントを読む)になります。

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  someData <<= getSomeDate(client) ;

  html += "<p>"+ someData +"</p>";
  someOtherData <<= getSomeOtherDate(client) ;

  html += "<p>"+ someOtherData +"</p>";
  moreData <<= getMoreData(client) ;

  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
});

明らかに他にも多くの解決策がありますが、前処理には実行時のオーバーヘッドがほとんどまたはまったくないという利点があり、source-mapサポートのおかげでデバッグも簡単です。


0

私も同じ問題を抱えていました。ノードの主要なライブラリが非同期関数を実行するのを見てきましたが、それらはコードをビルドするために非常に非自然な連鎖(3つ以上のメソッド設定を使用する必要があるなど)を示しています。

私は数週間かけて、シンプルで読みやすいソリューションを開発しました。EnqJSを試してみてください。すべての意見をいただければ幸いです。

の代わりに:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

EnqJSの場合:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";

  enq(function(){
    var self=this;
    getSomeDate(client, function(someData){
      html += "<p>"+ someData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getSomeOtherDate(client, function(someOtherData){ 
      html += "<p>"+ someOtherData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getMoreData(client, function(moreData) {
      html += "<p>"+ moreData +"</p>";
      self.return();
      res.write(html);
      res.end();
    });
  });
});

コードが以前よりも大きく見えることを確認します。しかし、以前のようにネストされていません。より自然に見えるように、チェーンはすぐに呼び出されます。

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

そして、それが戻ったと言うために、呼び出す関数内で:

this.return(response)

0

私はかなり原始的ですが効果的な方法でそれを行います。たとえば、親と子を持つモデルを取得する必要があり、それらに対して個別のクエリを実行する必要があるとします。

var getWithParents = function(id, next) {
  var getChildren = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      },
      getParents = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      }
      getModel = function(id, next) {
        /*... code ... */
        if (model) {
          // return next callbacl
          return next.pop()(model, next);
        } else {
          // return last callback
          return next.shift()(null, next);
        }
      }

  return getModel(id, [getParents, getChildren, next]);
}

0

ファイバーを使用するhttps://github.com/laverdet/node-fibersこれにより、非同期コードが同期のように見えます(ブロックなし)

私は個人的にこの小さなラッパーを使用していますhttp://alexeypetrushin.github.com/synchronize プロジェクトのコードのサンプル(すべてのメソッドは実際には非同期で、非同期ファイルIOで動作します)コールバックやasync-control-flowヘルパーライブラリ。

_update: (version, changesBasePath, changes, oldSite) ->
  @log 'updating...'
  @_updateIndex version, changes
  @_updateFiles version, changesBasePath, changes
  @_updateFilesIndexes version, changes
  configChanged = @_updateConfig version, changes
  @_updateModules version, changes, oldSite, configChanged
  @_saveIndex version
  @log "updated to #{version} version"

0

Task.jsはこれを提供します:

spawn(function*() {
    try {
        var [foo, bar] = yield join(read("foo.json"),
                                    read("bar.json")).timeout(1000);
        render(foo);
        render(bar);
    } catch (e) {
        console.log("read failed: " + e);
    }
});

これの代わりに:

var foo, bar;
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000);

var xhr1 = makeXHR("foo.json",
                   function(txt) { foo = txt; success() },
                   function(err) { failure() });
var xhr2 = makeXHR("bar.json",
                   function(txt) { bar = txt; success() },
                   function(e) { failure(e) });

function success() {
    if (typeof foo === "string" && typeof bar === "string") {
        cancelTimeout(tid);
        xhr1 = xhr2 = null;
        render(foo);
        render(bar);
    }
}

function failure(e) {
    xhr1 && xhr1.abort();
    xhr1 = null;
    xhr2 && xhr2.abort();
    xhr2 = null;
    console.log("read failed: " + e);
}

0

他の人が答えた後、あなたの問題はローカル変数であると述べました。これを行う簡単な方法は、これらのローカル変数を含む1つの外部関数を記述し、名前付きの内部関数の束を使用して、名前でそれらにアクセスすることです。この方法では、チェーンする必要のある関数の数に関係なく、2つだけの深さまでネストできます。

初心者がmysqlNode.jsモジュールをネストして使用する試みは次のとおりです。

function with_connection(sql, bindings, cb) {
    pool.getConnection(function(err, conn) {
        if (err) {
            console.log("Error in with_connection (getConnection): " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, function(err, results) {
            if (err) {
                console.log("Error in with_connection (query): " + JSON.stringify(err));
                cb(true);
                return;
            }
            console.log("with_connection results: " + JSON.stringify(results));
            cb(false, results);
        });
    });
}

以下は、名前付き内部関数を使用した書き換えです。外部関数with_connectionは、ローカル変数のホルダーとしても使用できます。(ここでは、同様に機能するパラメーター、、を用意していますがsqlbindingscb追加のローカル変数をいくつか定義するだけwith_connectionです。)

function with_connection(sql, bindings, cb) {

    function getConnectionCb(err, conn) {
        if (err) {
            console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, queryCb);
    }

    function queryCb(err, results) {
        if (err) {
            console.log("Error in with_connection/queryCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        cb(false, results);
    }

    pool.getConnection(getConnectionCb);
}

インスタンス変数を持つオブジェクトを作成し、これらのインスタンス変数をローカル変数の代わりとして使用することはおそらく可能だろうと私は考えていました。しかし、今度は、ネストされた関数とローカル変数を使用する上記のアプローチが単純で理解しやすいことに気づきました。OOの学習を解除するには時間がかかります。

これが、オブジェクトとインスタンス変数を含む私の以前のバージョンです。

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); });
}
DbConnection.prototype.query = function(err, results) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        self.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    self.cb(false, results);
}

function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); });
}

それはbindいくつかの利点に使用できることがわかりました。これにより、自分が作成した、メソッド呼び出しに転送する以外は何もしない、やや醜い匿名関数を取り除くことができます。の値が間違っているため、メソッドを直接渡すことができませんでしたthis。しかし、bindでは、必要な値を指定できますthis

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var f = this.query.bind(this);
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, f);
}
DbConnection.prototype.query = function(err, results) {
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    this.cb(false, results);
}

// Get a connection from the pool, execute `sql` in it
// with the given `bindings`.  Invoke `cb(true)` on error,
// invoke `cb(false, results)` on success.  Here,
// `results` is an array of results from the query.
function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    var f = dbc.getConnection.bind(dbc);
    pool.getConnection(f);
}

もちろん、これはNode.jsコーディングを使用した適切なJSではありません-私はそれに数時間費やしただけです。しかし、おそらく少し磨くことで、このテクニックは役に立ちますか?





0

ワイヤーを使用すると、コードは次のようになります。

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});

    var l = new Wire();

    getSomeDate(client, l.branch('someData'));
    getSomeOtherDate(client, l.branch('someOtherData'));
    getMoreData(client, l.branch('moreData'));

    l.success(function(r) {
        res.write("<h1>Demo page</h1>"+
            "<p>"+ r['someData'] +"</p>"+
            "<p>"+ r['someOtherData'] +"</p>"+
            "<p>"+ r['moreData'] +"</p>");
        res.end();
    });
});

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.