node.jsコールバックピラミッドの処理


9

私はnodeを使い始めたばかりですが、すぐに気付いたのは、コールバックが愚かなレベルのインデントまでどれだけ速く構築できるかです。

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

公式のスタイルガイドは別の関数内の各コールバックを置くことを言いますが、それは、オブジェクトがすべてを通過する必要があるように、単一のオブジェクトは、利用可能ないくつかの層下にトップレベルで宣言されたクロージャの使用に過度に制限的と思われる、となってい中間コールバック。

ここで助けるために関数スコープを使用しても大丈夫ですか?グローバルっぽいオブジェクトへのアクセスを必要とするすべてのコールバック関数を、そのオブジェクトを宣言する関数の中に置いて、クロージャーに入りますか?

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

など、さらにいくつかのレイヤーについて...

または、すべてのコールバックごとに名前付き関数を宣言せずにインデントのレベルを下げるのに役立つフレームワークなどはありますか?コールバックピラミッドをどのように扱いますか?


2
クロージャーに関する追加の問題に注意してください-JSクロージャーでは、親コンテキスト全体をキャプチャします(他の言語では、使用された変数またはユーザーが具体的に要求したもののみをキャプチャします)コールバック階層が十分に深い場合、たとえば、コールバックはどこかに保持されます。
ユージーン

回答:


7

Promiseは非同期動作とインターフェース間の問題を明確に分離するため、コールバックなしで非同期関数を呼び出すことができ、コールバックの相互作用は汎用のPromiseインターフェースで実行できます。

「約束」には複数の実装があります。


たとえば、このネストされたコールバックを書き換えることができます

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

お気に入り

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

コールバックのコールバックの代わりに、a(b(c()))a().then(b()).then(c())。then」をチェーンします。


ここでの紹介:http : //howtonode.org/promises


これらのリソースの機能についての説明をお願いします。質問に答える際に、これらのリソースを推奨する理由を教えてください。「リンクのみの回答」はStack Exchangeで歓迎されていません
2013年

1
すみません。例と詳細情報を追加しました。
Fabien Sa

3

promisesの代わりに、EcmaScript 6で導入されるジェネレーター関数yield組み合わせたキーワードを確認する必要があります。どちらも現在Node.js 0.11.xビルドで利用可能ですが、Nodeの実行時にフラグをさらに指定する必要があります.js:--harmony

$ node --harmony app.js

これらの構造とTJ Holowaychukのcoなどのライブラリーを使用すると、非同期コードを同期コードのように見えるスタイルで記述できますが、それでも非同期に実行されます。基本的に、これらは一緒にNode.jsのコルーチンサポートを実装します。

基本的に、必要なことは、非同期のものを実行するコードのジェネレーター関数を記述し、そこで非同期のものを呼び出しますが、その前にyieldキーワードを付けます。したがって、最終的にコードは次のようになります。

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

このジェネレーター関数を実行するには、前述のcoなどのライブラリーが必要です。呼び出しは次のようになります。

co(run);

または、インラインで配置するには:

co(function * () {
  // ...
});

ジェネレーター関数から他のジェネレーター関数を呼び出すことができることに注意してください、あなたがする必要があるのはそれらにyield再びプレフィックスを付けることです。

このトピックの概要については、などの用語をGoogleで検索yield generators es6 async nodejsすると、大量の情報が見つかります。慣れるまで少し時間がかかりますが、一度慣れると二度と戻りたくありません。

これは、関数を呼び出すためのより良い構文を提供するだけでなく、forループやtry/ などの通常の(同期)制御フローロジックを使用できることに注意してくださいcatch。多くのコールバックとこれらすべてのものをいじる必要はもうありません。

頑張って楽しんでね :-)!


0

これで、&in Nodeの将来のネイティブサポートとなる構文に非常に近い構文で、パッケージasyncawaitができました。awaitasync

基本的には、同期的に見える非同期コードを記述して、LOCとインデントレベルを大幅に削減し、わずかなパフォーマンスの損失(パッケージ所有者の数は、未加工のコールバックと比較して79%の速度です)のトレードオフで、ネイティブサポートが利用可能になったときに削減できると期待しています。

パフォーマンスが主な関心事ではなく、同期書き込みスタイルがプロジェクトのニーズにより適している場合は、コールバック地獄/運命の悪夢のピラミッドIMOから抜け出すための良い選択です。

パッケージdocの基本的な例:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.