如何使函数等到使用 node.js 调用回调

How to make a function wait until a callback has been called using node.js

提问人:Chris 提问时间:2/16/2011 最后编辑:Jonathan HallChris 更新时间:9/13/2022 访问量:592943

问:

我有一个简化的函数,如下所示:

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

基本上我希望它调用 ,并返回回调 lambda 中给出的响应。但是,上面的代码不起作用,只是立即返回。myApi.exec

只是为了一个非常黑客的尝试,我尝试了以下不起作用的方法,但至少你明白了我想要实现的目标:

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

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

JavaScript 多线程 回调 节点 .js

评论

4赞 Chris 2/16/2011
还是我在这里完全错误地处理它,我是否应该调用另一个回调,而不是返回响应?
0赞 bluenote10 5/21/2017
在我看来,这是为什么繁忙循环不起作用的最佳 SO 解释。
0赞 Atul 8/5/2019
不要试图等待。只需在回调本身的末尾调用下一个函数(callback-dependent)

答:

-1赞 user193476 2/16/2011 #1

这违背了非阻塞 IO 的目的 -- 当它不需要阻塞时,你就要阻塞它:)

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

很有可能,如果你需要强制阻塞,你就把架构想错了。

评论

0赞 Chris 2/16/2011
我怀疑我把这个倒过来了。
33赞 Dan Dascalescu 12/21/2013
很有可能,我只想为某些 URL 及其内容编写一个快速脚本。为什么我必须向后跳转才能在 Node 中执行此操作?http.get()console.log()
6赞 Jakob 12/17/2014
@DanDascalescu:为什么我必须声明类型签名才能在静态语言中执行此操作?为什么我必须把它放在类 C 语言的 main 方法中?为什么我必须用编译语言编译它?你质疑的是 Node.js 中的基本设计决策。这一决定有利有弊。如果你不喜欢它,你可以使用另一种更适合你风格的语言。这就是为什么我们有不止一个。
0赞 Dan Dascalescu 12/17/2014
@Jakob:您列出的解决方案确实是次优的。这并不意味着没有好的,比如 Meteor 在服务器端使用 Node,这消除了回调地狱问题。
18赞 Jazz 5/25/2016
@Jakob:如果“为什么生态系统X使共同任务Y变得不必要地困难?”的最佳答案是“如果你不喜欢它,就不要使用生态系统X”,那么这是一个强烈的迹象,表明生态系统X的设计者和维护者将自己的自我置于生态系统的实际可用性之上。根据我的经验,Node 社区(与 Ruby、Elixir 甚至 PHP 社区相比)不遗余力地使常见任务变得困难。非常感谢您将自己作为这种反模式的活生生的例子。
307赞 Jakob 2/16/2011 #2

“好的节点.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赞 Chris 2/16/2011
好的,太好了。如果 myApi.exec 从未调用回调怎么办?我将如何使回调在 10 秒后被调用,并带有一个错误值,说它计时了我们的或其他东西?
5赞 Jakob 2/16/2011
或者更好的是(添加了一个检查,因此回调不能被调用两次): jsfiddle.net/LdaFw/1
179赞 nategood 5/13/2012
很明显,非阻塞是 node/js 的标准,但是有时肯定需要阻塞(例如,阻塞 stdin)。甚至节点也有“阻塞”方法(查看所有方法)。因此,我认为这仍然是一个有效的问题。除了繁忙的等待之外,有没有一种很好的方法可以在节点中实现阻塞?fssync*
7赞 Jakob 3/21/2013
对@nategood评论的迟到回答:我能想到几种方法;太多了,无法在此评论中解释,但谷歌它们。请记住,Node 不是为了被阻止而生的,所以这些并不完美。将它们视为建议。无论如何,这里是这样的:(1)使用C来实现你的函数,并将其发布到NPM以使用它。(2)使用纤维,github.com/laverdet/node-fibers,(3)使用promise,例如Q库,(4)在javascript之上使用薄层,看起来很阻塞,但可以编译为异步,就像 maxtaco.github.com/coffee-scriptsync
163赞 Howard Swope 2/5/2016
当人们用“你不应该那样做”来回答一个问题时,这真是太令人沮丧了。如果一个人想提供帮助并回答问题,那就是要做的一件站立的事情。但是毫不含糊地告诉我我不应该做某事是不友好的。有人想要同步或异步调用例程的原因有一百万种。这是一个关于如何做到这一点的问题。如果您在提供答案时提供有关 api 性质的有用建议,那很有帮助,但如果您不提供答案,为什么还要回复。(我想我真的应该提出自己的建议。
1赞 Z0LtaR 7/11/2013 #3

假设你有一个函数:

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

评论

0赞 Raul Luna 7/1/2022
在我阅读这段代码之前,我以为我理解了递归
25赞 Lucio M. Tato 12/6/2013 #4

检查一下: https://github.com/luciotato/waitfor-ES6

你的代码与 wait.for: (需要生成器, --harmony 标志)

function* (query) {
  var r = yield wait.for( myApi.exec, 'SomeCommand');
  return r;
}
5赞 Albert 3/27/2014 #5

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

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

(您可以替代使用.这可以避免一些阻塞问题,但需要 100% 的 CPU。uvrun.runNoWait()

请注意,这种方法使 Nodejs 的整个目的无效,即让一切都异步和非阻塞。此外,它可能会大大增加您的调用堆栈深度,因此您最终可能会遇到堆栈溢出。如果你递归地运行这样的函数,你肯定会遇到麻烦。

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

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

12赞 vishal patel 4/17/2015 #6

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

5赞 Douglas Soares 3/9/2017 #7

从节点 4.8.0 开始,您可以使用 ES6 的 generator 功能。 您可以按照本文了解更深入的概念。 但基本上你可以使用生成器和承诺来完成这项工作。 我正在使用 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));
129赞 Timo 7/4/2018 #8

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

// 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

评论

9赞 robert arles 4/20/2020
这是一个用回调包装函数的非常好的示例,因此您可以使用它 我不经常需要这个,所以很难记住如何处理这种情况,我正在复制它作为我的个人笔记/参考。async/await
0赞 Ravi Yadav 9/27/2021
写得很好的例子。对于像我这样的初学者来说很容易理解。很高兴从 async/await 回调地狱中恢复
0赞 Jax Teller 10/12/2021
干得好。这正是我所需要的,因为我得到了一个使用回调的 API 函数调用,我不知道如何“等待”它的结果。
0赞 Tibor Takács 2/15/2023
当人们想要等到调用回调时,这是确切的用例:围绕回调机制创建一个异步 API。很棒的地方,很棒的描述。
0赞 SaiSurya 5/2/2020 #9

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

  }

}

评论

2赞 Quentin 5/2/2020
问题中使用的 API 不返回 promise,因此您需要先将其包装在一个 ...就像两年前的这个答案一样。
7赞 Maciej Krawczyk 12/19/2020 #10

现在是 2020 年,API 很可能已经有一个基于 promise 的版本,可以与 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 版本中(从 v7.6 开始)。

评论

1赞 lys 11/8/2022
这正是我所需要的 - 出于某种原因,当只需要将事件发射器包装在 Promise 中这样简单的事情时,很容易迷失在思考如何使用回调来做到这一点时