Как сделать функцию до тех пор, пока не будет вызван обратный вызов с помощью 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/event driven" способ обойти это? Я хочу, чтобы моя функция дождалась вызова callback, а затем вернет значение, которое было передано ему.

Ответ 1

"Хороший node.js/event driven" способ сделать это - не ждать.

Как и все остальное при работе с системами, управляемыми событиями, такими как 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
});

Ответ 2

проверьте это: https://github.com/luciotato/waitfor-ES6

ваш код с wait.for: (требует генераторов, --harmony flag)

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

Ответ 3

Если вы не хотите использовать обратный вызов, вы можете использовать модуль "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

Ответ 4

Примечание. Этот ответ, вероятно, не должен использоваться в производственном коде. Это взлом, и вы должны знать о последствиях.

Существует модуль uvrun (обновлен для новых версий Nodejs здесь), где вы можете выполнить один цикл цикла главного цикла событий libuv (который является основным циклом Nodejs).

Ваш код будет выглядеть так:

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, т.е. иметь все асинхронные и неблокирующие. Кроме того, это может значительно увеличить глубину вызова, так что вы можете столкнуться с переполнением стека. Если вы запустите такую ​​функцию рекурсивно, вы обязательно столкнетесь с проблемами.

См. другие ответы о том, как изменить свой код, чтобы сделать это "правильным".

Это решение, вероятно, полезно только при тестировании и особенно. хотите синхронизировать и серийный код.

Ответ 5

Это побеждает цель неблокирующего ввода-вывода - вы блокируете его, когда ему не нужно блокировать :)

Вы должны вложить свои обратные вызовы вместо принудительного ожидания node.js или вызвать другой обратный вызов внутри обратного вызова, где вам нужен результат r.

Скорее всего, если вам нужно принудительно блокировать, вы неправильно думаете о своей архитектуре.

Ответ 6

Если вы хотите, чтобы это было очень просто и просто, никакие фантастические библиотеки, чтобы ожидать, что функции обратного вызова, которые будут выполняться в node, перед выполнением какого-либо другого кода, выглядят следующим образом:

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

Ответ 7

Так как node 4.8.0 вы можете использовать функцию ES6, называемую генератором. Вы можете следовать этой статье для более глубоких концепций. Но в основном вы можете использовать генераторы и promises, чтобы выполнить эту работу. Я использую 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));

Ответ 8

Предположим, что у вас есть функция:

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);
}
});

Ответ 9

Мне это работало, чтобы использовать

JSON.parse(result)['key']

на ожидаемый результат. Это не может быть "супер общим", но в конце "JSON.parse" управлял ожиданием асинхронного вызова, а result['key'] не выполнял.

Ответ 10

exports.dbtest = function (req, res) {
  db.query('SELECT * FROM users', [], res, renderDbtest);
};

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

Вот как я это сделал, вам просто нужно передать "res" с ним, чтобы вы могли сделать позже