it-swarm-ru.tech

Как заставить функцию ждать, пока не будет вызван обратный вызов, используя 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

"Хороший узел.js/управляемый событиями" способ сделать это не ждать.

Как и почти все остальное при работе с системами, управляемыми событиями, такими как узел, ваша функция должна принимать параметр обратного вызова, который будет вызван после завершения вычислений. Вызывающая сторона не должна ждать, пока значение будет "возвращено" в обычном смысле, а должна отправить подпрограмму, которая будет обрабатывать полученное значение:

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: (требуются генераторы, флаг --harmony)

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

Один из способов добиться этого - заключить вызов API в обещание, а затем использовать 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

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

Существует модуль vrun (обновлен для более новых версий 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% ЦП.)

Обратите внимание, что этот подход делает недействительным всю цель 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