node.jsを使用してコールバックが呼び出されるまで関数を待機させる方法


266

私は次のような単純化された関数を持っています:

function(query) {
  myApi.exec('SomeCommand', function(response) {
    return response;
  });
}

基本的にはを呼び出してmyApi.exec、コールバックラムダで指定された応答を返します。ただし、上記のコードは機能せず、ただちに戻ります。

非常にハッキーな試みのために、私はうまくいかなかった以下を試しましたが、少なくともあなたは私が達成しようとしていることを理解しています:

function(query) {
  var r;
  myApi.exec('SomeCommand', function(response) {
    r = response;
  });
  while (!r) {}
  return r;
}

基本的に、これを行うための「node.js /イベント駆動型」の良い方法は何ですか?コールバックが呼び出されるまで関数が待機し、渡された値を返すようにしたいのですが。


3
または、ここでそれを完全に間違った方法で行っていますか?応答を返すのではなく、別のコールバックを呼び出す必要がありますか?
クリス

これは、ビジーループが機能しない理由についての最良の説明です。
bluenote10

待ってはいけません。コールバック自体の終わりに次の関数(コールバック依存)を呼び出すだけ
Atul

回答:


282

これを行う「適切なnode.js /イベント駆動型」の方法は、待機ないことです。

nodeのようなイベント駆動型システムで作業する場合、他のほとんどすべてのように、関数は、計算が完了したときに呼び出されるコールバックパラメーターを受け入れる必要があります。呼び出し元は、通常の意味で値が「返される」のを待つのではなく、結果の値を処理するルーチンを送信する必要があります。

function(query, callback) {
  myApi.exec('SomeCommand', function(response) {
    // other stuff here...
    // bla bla..
    callback(response); // this will "return" your value to the original caller
  });
}

したがって、次のように使用しないでください。

var returnValue = myFunction(query);

しかし、このように:

myFunction(query, function(returnValue) {
  // use the return value here instead of like a regular (non-evented) return value
});

5
わかりました。myApi.execがコールバックを呼び出さなかった場合はどうなりますか?コールバックが10秒後に呼び出されるようにするにはどうすればよいですか?
クリス

5
またはより良い(チェックを追加してコールバックを2回呼び出せ
Jakob

148
node / jsでは非ブロッキングが標準であることは明らかですが、ブロッキングが必要な場合もあります(例:stdinでのブロッキング)。ノードにも「ブロッキング」メソッドがあります(すべてのfs sync*メソッドを参照)。そのため、これは依然として有効な質問だと思います。ビジー待機以外にノードでブロッキングを達成する良い方法はありますか?
nategood

7
@nategoodのコメントに対する遅い回答:いくつかの方法が考えられます。このコメントで説明するには多すぎますが、ググってください。ノードはブロックされるように作成されていないため、これらは完全ではないことに注意してください。それらを提案として考えてください。とにかく、ここに行きます:(1)Cを使用して関数を実装し、それを使用するためにNPMに公開します。それがsyncメソッドが行うことです。(2)ファイバーを使用する、github.com/laverdet/node-fibers、(3)たとえばQライブラリなどのpromiseを使用する、(4)javascriptの上に薄いレイヤーを使用する。以下のようなmaxtaco.github.com/coffee-script
ヤコブ

106
人々が「それをしてはいけない」という質問に答えるのはとてもイライラします。助けになり、質問に答えたい場合、それは立ち上がることです。しかし、私が何かをすべきでないことを明確に言うと、単に友好的ではありません。誰かがルーチンを同期的または非同期的に呼び出したい理由は、100万通りあります。これはどうやってやるかという質問でした。回答を提供する際にAPIの性質について役立つアドバイスを提供する場合、それは役立ちますが、回答を提供しない場合、なぜわざわざ返信する必要があります。(私は本当に私自身のアドバイスを率いるべきだと思います。)
ハワード・スウォープ

46

これを実現する1つの方法は、API呼び出しをpromiseにラップし、それを使用awaitして結果を待つことです。

// let's say this is the API function with two callbacks,
// one for success and the other for error
function apiFunction(query, successCallback, errorCallback) {
    if (query == "bad query") {
        errorCallback("problem with the query");
    }
    successCallback("Your query was <" + query + ">");
}

// myFunction wraps the above API call into a Promise
// and handles the callbacks with resolve and reject
function apiFunctionWrapper(query) {
    return new Promise((resolve, reject) => {
        apiFunction(query,(successResponse) => {
            resolve(successResponse);
        }, (errorResponse) => {
            reject(errorResponse)
        });
    });
}

// now you can use await to get the result from the wrapped api function
// and you can use standard try-catch to handle the errors
async function businessLogic() {
    try {
        const result = await apiFunctionWrapper("query all users");
        console.log(result);

        // the next line will fail
        const result2 = await apiFunctionWrapper("bad query");
    } catch(error) {
        console.error("ERROR:" + error);
    }
}

// call the main function
businessLogic();

出力:

Your query was <query all users>
ERROR:problem with the query

これはコールバックで関数をラップする非常によくできた例ですので、async/await 私はこれを必要としないことが多いので使用できます。したがって、この状況の処理方法を思い出すのに問題があります。これを私の個人的なメモ/参照用にコピーしています。
ロバートアルル


10

コールバックを使用したくない場合は、「Q」モジュールを使用できます。

例えば:

function getdb() {
    var deferred = Q.defer();
    MongoClient.connect(databaseUrl, function(err, db) {
        if (err) {
            console.log("Problem connecting database");
            deferred.reject(new Error(err));
        } else {
            var collection = db.collection("url");
            deferred.resolve(collection);
        }
    });
    return deferred.promise;
}


getdb().then(function(collection) {
   // This function will be called afte getdb() will be executed. 

}).fail(function(err){
    // If Error accrued. 

});

詳細については、これを参照してください:https : //github.com/kriskowal/q


9

他のコードを実行する前に、ノードでコールバック関数が実行されるのを待機するという、非常にシンプルで簡単な、派手なライブラリが不要な場合は、次のようになります。

//initialize a global var to control the callback state
var callbackCount = 0;
//call the function that has a callback
someObj.executeCallback(function () {
    callbackCount++;
    runOtherCode();
});
someObj2.executeCallback(function () {
    callbackCount++;
    runOtherCode();
});

//call function that has to wait
continueExec();

function continueExec() {
    //here is the trick, wait until var callbackCount is set number of callback functions
    if (callbackCount < 2) {
        setTimeout(continueExec, 1000);
        return;
    }
    //Finally, do what you need
    doSomeThing();
}

5

注:この回答は、本番用コードでは使用しないでください。これはハックであり、その影響について知っておく必要があります。

ありuvrunの(新しいNodejsバージョンの更新モジュールここにあなたが(Nodejsメインループである)libuvメインイベントループの単一ループのラウンドを実行することができます)。

コードは次のようになります。

function(query) {
  var r;
  myApi.exec('SomeCommand', function(response) {
    r = response;
  });
  var uvrun = require("uvrun");
  while (!r)
    uvrun.runOnce();
  return r;
}

(代わりにを使用するuvrun.runNoWait()こともできます。これにより、ブロッキングに関するいくつかの問題を回避できますが、CPUが100%使用されます。)

このアプローチは、Nodejsの目的全体を無効にすることに注意してください。つまり、すべてを非同期で非ブロックにすることです。また、コールスタックの深さが大幅に増えるため、スタックオーバーフローが発生する可能性があります。このような関数を再帰的に実行すると、間違いなく問題が発生します。

コードを再設計して「正しく」行う方法については、他の回答を参照してください。

ここでのこのソリューションは、おそらくテストやespを行う場合にのみ役立ちます。同期コードとシリアルコードが必要です。


5

ノード4.8.0以降、ジェネレーターと呼ばれるES6の機能を使用できます。より深い概念については、この記事に従うことができます。ただし、基本的には、ジェネレーターとプロミスを使用してこの作業を完了することができます。私はブルーバードを使用してジェネレーターを約束し、管理しています。

あなたのコードは以下の例のようにうまくいくはずです。

const Promise = require('bluebird');

function* getResponse(query) {
  const r = yield new Promise(resolve => myApi.exec('SomeCommand', resolve);
  return r;
}

Promise.coroutine(getResponse)()
  .then(response => console.log(response));

1

あなたが関数を持っていると仮定します:

var fetchPage(page, callback) {
   ....
   request(uri, function (error, response, body) {
        ....
        if (something_good) {
          callback(true, page+1);
        } else {
          callback(false);
        }
        .....
   });


};

次のようなコールバックを利用できます。

fetchPage(1, x = function(next, page) {
if (next) {
    console.log("^^^ CALLBACK -->  fetchPage: " + page);
    fetchPage(page, x);
}
});

-1

これは非ブロックIOの目的に反します-ブロックする必要がない場合はブロックします :)

node.jsに強制的に待機させるのではなく、コールバックをネストするか、コールバック内での結果が必要な別のコールバックを呼び出す必要がありますr

おそらく、強制的にブロックする必要がある場合は、アーキテクチャが間違っていると考えています。


私はこれを後方に持っていた疑いがありました。
クリス

31
たぶん、http.get()URLとconsole.log()そのコンテンツに簡単なスクリプトを書きたいだけです。Nodeでそれを行うためになぜ後方にジャンプしなければならないのですか?
Dan Dascalescu 2013

6
@DanDascalescu:静的言語で型シグネチャを宣言する必要があるのはなぜですか?そして、なぜそれをCのような言語のmainメソッドに入れなければならないのですか?そして、なぜそれをコンパイルされた言語でコンパイルしなければならないのですか?質問しているのは、Node.jsの基本的な設計上の決定です。その決定には長所と短所があります。気に入らない場合は、自分のスタイルに合った別の言語を使用できます。それが私たちが複数ある理由です。
Jakob 2014

@ヤコブ:あなたが挙げた解決策は実際には次善のものです。これは、Meteorのサーバー側のファイバーでのNodeの使用など、コールバックの地獄の問題を解消するような優れた機能がないという意味ではありません。
Dan Dascalescu、2014

13
@Jakob:「エコシステムXが共通のタスクYを不必要に困難にする理由」に対する最良の答えが得られた場合。「それが気に入らない場合は、エコシステムXを使用しないでください」ということは、エコシステムXの設計者と保守者が、エコシステムの実際のユーザビリティよりも自分のエゴを優先していることを強く示しています。Nodeコミュニティー(Ruby、Elixir、さらにはPHPコミュニティーとは対照的)が一般的なタスクを困難にするために邪魔になるのは私の経験です。アンチパターンの生きた模範として自分自身を提供してくれてありがとう。
ジャズ

-1

asyncとawaitを使用する方がはるかに簡単です。

router.post('/login',async (req, res, next) => {
i = await queries.checkUser(req.body);
console.log('i: '+JSON.stringify(i));
});

//User Available Check
async function checkUser(request) {
try {
    let response = await sql.query('select * from login where email = ?', 
    [request.email]);
    return response[0];

    } catch (err) {
    console.log(err);

  }

}

質問で使用されたAPIはプロミスを返さないため、最初に1つにラップする必要があります… 2年前のこの回答のように。
クエンティン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.