web-dev-qa-db-ja.com

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

このような単純化された機能があります。

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 /イベント駆動'の方法は何ですか?私は自分の関数にコールバックが呼ばれるまで待ってからそれに渡された値を返すようにしたいです。

234
Chris

これを行うための「良い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
});
261
Jakob

これを確認してください。 https://github.com/luciotato/waitfor-ES6

wait.for:でコードを生成します(ジェネレータが必要、 - ハーモニーフラグ)。

function* (query) {
  var r = yield wait.for( myApi.exec, 'SomeCommand');
  return r;
}
23
Lucio M. Tato

これを実現する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
16
Timo

あなたがコールバックを使いたくないならば、あなたは "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

10
vishal patel

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

//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();
}
9
Marquinho Peli

注:この回答は、プロダクションコードではおそらく使用しないでください。それはハックであり、あなたはその影響について知っておくべきです

uvrun モジュールがあります(新しいNodejsバージョン はこちら ) libuvメインイベントループ(Nodejsメインループ)を1回実行することができます。

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

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

(代わりにuvrun.runNoWait()を使うかもしれません。それはブロッキングに関するいくつかの問題を避けることができますが、100%CPUを使います。)

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

「正しい」ようにコードを再設計する方法に関する他の回答を参照してください。

ここでのこの解決策はおそらくテストやespをするときにだけ役に立つでしょう。同期とシリアルコードが欲しいのですが。

5
Albert

ノード4.8.0以降、ジェネレータと呼ばれるES6の機能を使用できます。より深い概念については、この 記事 に従ってください。しかし基本的にあなたはこの仕事を成し遂げるためにジェネレータと約束を使うことができます。私はジェネレータを約束し管理するために bluebird を使っています。

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

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));
4
Douglas Soares

私にはそれが使用するために働いた

JSON.parse(result)['key']

私は期待した結果に。これは「超一般的」ではないかもしれませんが、結局「JSON.parse」は非同期呼び出しの待機を管理しましたが、result['key']はそうしませんでした。

1
MrMuc Mic

関数があるとします。

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
Z0LtaR
exports.dbtest = function (req, res) {
  db.query('SELECT * FROM users', [], res, renderDbtest);
};

function renderDbtest(result, res){
  res.render('db', { result: result });
}

これが私がしたやり方です、あなたはそれと共に "res"を渡す必要があるので、後でレンダリングすることができます

0
harmony

これは、非ブロックIOの目的を無効にします。ブロックする必要がないときにブロックしている:)

Node.jsに待機させる代わりにコールバックをネストするか、rの結果が必要なコールバック内で別のコールバックを呼び出す必要があります。

ブロックを強制する必要がある場合は、アーキテクチャが間違っていると考えている可能性があります。

0
user193476