如何让一个函数等待,直到一个回调已被调用使用node.js

我有一个简化的函数,看起来像这样:

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

基本上,我想让它调用myApi.exec,并返回回调lambda中给出的响应。然而,上面的代码不能工作,只是立即返回。

只是为了一个非常hack的尝试,我尝试了下面的工作,但至少你知道我想要实现什么:

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

基本上,“node.js/事件驱动”的好方法是什么?我希望我的函数等待,直到回调被调用,然后返回传递给它的值。

569548 次浏览

这违背了非阻塞IO的目的——你在不需要阻塞:)时阻塞了它

你应该嵌套回调,而不是强迫node.js等待,或者在需要r结果的回调中调用另一个回调。

如果您需要强制阻塞,那么很可能您对体系结构的想法是错误的。

“好的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
});

假设你有一个函数:

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);
}
});
< p >检查: https://github.com/luciotato/waitfor-ES6 < / p >

您的代码与等待。对于:(需要生成器,——和谐旗)

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

注意:这个答案可能不应该在产品代码中使用。这是一个黑客,你应该知道它的含义。

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的整个目的,即让所有内容都是异步和非阻塞的。此外,它可能会大大增加调用堆栈的深度,因此可能会导致堆栈溢出。如果你递归地运行这样的函数,你肯定会遇到麻烦。

请参阅其他关于如何重新设计代码以使其“正确”的答案。

这里的解决方案可能只在您进行测试时有用,特别是当您想要同步和串行代码时。

如果你不想使用回调,那么你可以使用“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

如果你想让它非常简单,没有花哨的库,在执行一些其他代码之前,等待回调函数在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();
}
从节点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));

实现这一点的一种方法是将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 + ">");
}


// Next function 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要容易得多。

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


}


}

现在是2020年,API可能已经有了一个基于承诺的版本,可以与await一起工作。然而,一些接口,特别是事件发射器将需要这个解决方案:

// doesn't wait
let value;
someEventEmitter.once((e) => { value = e.value; });
// waits
let value = await new Promise((resolve) => {
someEventEmitter.once('event', (e) => { resolve(e.value); });
});

在这种特殊情况下,它将是:

let response = await new Promise((resolve) => {
myAPI.exec('SomeCommand', (response) => { resolve(response); });
});

Await在过去的3年里一直出现在新的Node.js版本中(从7.6版本开始)。