【问题标题】:How to make a function wait until a callback has been called using node.js如何使函数等到使用 node.js 调用回调
【发布时间】:2011-06-27 23:21:17
【问题描述】:

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

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

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

只是为了一个非常骇人听闻的尝试,我尝试了以下没有用的方法,但至少你明白我想要实现的目标:

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

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

【问题讨论】:

  • 或者我在这里完全错误的方式,我应该调用另一个回调,而不是返回响应?
  • This 在我看来是最好的 SO 解释 为什么忙循环不起作用。
  • 不要试图等待。只需在回调本身结束时调用下一个函数(依赖于回调)

标签: javascript multithreading callback node.js


【解决方案1】:

执行此操作的“良好的 node.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
});

【讨论】:

  • 好的,很好。如果 myApi.exec 从未调用回调怎么办?我将如何做到这一点,以便在 10 秒后调用回调,并显示一个错误值,说明它计时了我们或其他什么?
  • 或者更好(添加了一个检查,所以回调不能被调用两次):jsfiddle.net/LdaFw/1
  • 很明显,非阻塞是 node/js 中的标准,但是有时需要阻塞(例如阻塞标准输入)。甚至节点也有“阻塞”方法(参见所有fssync* 方法)。因此,我认为这仍然是一个有效的问题。除了忙于等待之外,有没有一种很好的方法来实现节点中的阻塞?
  • 对@nategood 评论的迟到回答:我可以想到几种方法;在此评论中解释太多了,但谷歌他们。请记住,Node 不是为了被阻塞而设计的,所以这些并不完美。将它们视为建议。无论如何,这里是:(1)使用C来实现你的功能并将其发布到NPM以便使用它。这就是sync 方法的作用。 (2) 使用纤程,github.com/laverdet/node-fibers,(3) 使用 Promise,例如 Q-library,(4) 在 javascript 之上使用薄层,看起来阻塞,但编译为异步,例如 maxtaco.github.com/coffee-script跨度>
  • 当人们用“你不应该那样做”来回答问题时,真是令人沮丧。如果一个人想提供帮助并回答问题,那是一件值得做的事情。但明确告诉我不应该做某事只是不友好。为什么有人想要同步或异步调用例程有上百万个不同的原因。这是一个关于如何做的问题。如果您在提供答案时提供有关 api 性质的有用建议,那很有帮助,但如果您不提供答案,为什么还要回复。 (我想我真的应该提出自己的建议。)
【解决方案2】:

实现此目的的一种方法是将 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

【讨论】:

  • 这是一个用回调包装函数的非常好的示例,因此您可以将它与async/await 一起使用我不经常需要这个,所以很难记住如何处理这种情况,我复制此作为我的个人笔记/参考。
  • 写得很好的例子。对于像我这样的初学者来说很容易理解。很高兴从异步/等待回调地狱中恢复
  • 干得好。这正是我所需要的,因为我得到了一个使用回调的 API 函数调用,但我不知道如何“等待”它的结果。
【解决方案3】:

检查这个: https://github.com/luciotato/waitfor-ES6

您的带有 wait.for 的代码:(需要生成器,--harmony 标志)

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

【讨论】:

    【解决方案4】:

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

      如果你希望它非常简单易用,没有花哨的库,等待回调函数在节点中执行,然后再执行一些其他代码,是这样的:

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

      【讨论】:

        【解决方案6】:

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

        uvrun 模块(更新为较新的 Nodejs 版本 here),您可以在其中执行 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 的全部目的无效,即让一切异步和非阻塞。此外,它可能会大大增加您的调用堆栈深度,因此您最终可能会出现堆栈溢出。如果你递归地运行这样的函数,你肯定会遇到麻烦。

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

        这里的解决方案可能仅在您进行测试和 esp 时才有用。想要同步和串行代码。

        【讨论】:

          【解决方案7】:

          从 node 4.8.0 开始,您可以使用 ES6 的生成器功能。 您可以关注article 以获得更深入的概念。 但基本上你可以使用生成器和承诺来完成这项工作。 我正在使用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】:

            现在是 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); });
            });
            

            过去 3 年(自 v7.6 起)Await 一直在新的 Node.js 版本中。

            【讨论】:

              【解决方案9】:

              假设你有一个函数:

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

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                • 问题中使用的 API 不会返回承诺,因此您需要先将其包装成一个……就像两年前 this answer 所做的那样。
                【解决方案11】:

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

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

                如果您需要强制阻止,您可能是在考虑您的架构错误。

                【讨论】:

                • 我怀疑我有这个倒退。
                • 机会是,我只想为http.get() 某个URL 和console.log() 其内容编写一个快速脚本。为什么我必须向后跳才能在 Node 中执行此操作?
                • @DanDascalescu:为什么我必须声明类型签名才能在静态语言中做到这一点?为什么我必须把它放在类 C 语言的主要方法中?为什么我必须用编译语言编译它?您所质疑的是 Node.js 中的基本设计决策。这个决定有利也有弊。如果你不喜欢它,你可以使用另一种更适合你风格的语言。这就是为什么我们有不止一个。
                • @Jakob:您列出的解决方案确实不是最理想的。但这并不意味着没有好的,比如 Meteor 在 Fiber 中使用 Node 的服务器端,它消除了回调地狱问题。
                • @Jakob:如果最好的答案是“为什么生态系统 X 会使常见任务 Y 变得不必要地困难?”是“如果你不喜欢它,就不要使用生态系统 X”,那么这是一个强烈的迹象,表明生态系统 X 的设计者和维护者将他们自己的自我置于其生态系统的实际可用性之上。根据我的经验,Node 社区(与 Ruby、Elixir 甚至 PHP 社区相比)不遗余力地让常见任务变得困难。非常感谢您将自己作为该反模式的活生生的例子。
                猜你喜欢
                • 2021-06-03
                • 2020-06-04
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-01-16
                • 1970-01-01
                相关资源
                最近更新 更多