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

Chr*_*ris 246 javascript multithreading callback node.js

我有一个简化的功能,如下所示:

function(query) {
  myApi.exec('SomeCommand', function(response) {
    return response;
  });
}
Run Code Online (Sandbox Code Playgroud)

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

只是为了一个非常hackish尝试,我尝试了下面没有工作,但至少你明白了我想要实现的目标:

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

基本上,什么是一个好的'node.js /事件驱动'的方式来解决这个问题?我希望我的函数等到调用回调,然后返回传递给它的值.

Jak*_*kob 267

"good node.js/event driven"这样做的方法就是不要等待.

与使用像节点这样的事件驱动系统几乎所有其他内容一样,您的函数应该接受一个回调参数,该参数将在计算完成时调用.调用者不应该等待正常意义上的"返回"值,而是发送将处理结果值的例程:

function(query, callback) {
  myApi.exec('SomeCommand', function(response) {
    // other stuff here...
    // bla bla..
    callback(response); // this will "return" your value to the original caller
  });
}
Run Code Online (Sandbox Code Playgroud)

所以你不要这样使用它:

var returnValue = myFunction(query);
Run Code Online (Sandbox Code Playgroud)

但是像这样:

myFunction(query, function(returnValue) {
  // use the return value here instead of like a regular (non-evented) return value
});
Run Code Online (Sandbox Code Playgroud)

  • 很明显,非阻塞是node/js中的标准,但是肯定有时候需要阻塞(例如阻塞stdin).甚至节点都有"阻塞"方法(参见所有`fs``sync*`方法).因此,我认为这仍然是一个有效的问题.除了忙碌的等待之外,还有一种很好的方法可以实现节点阻塞吗? (137认同)
  • 当人们以"你不应该这样做"回答问题时,这是非常令人沮丧的.如果一个人想要提供帮助并回答一个问题,那么这是一个很好的事情.但毫不含糊地告诉我,我不应该做些什么只是不友好.有些人会想要同步或异步调用例程,这有很多种原因.这是一个关于如何做到这一点的问题.如果您在提供答案的同时提供有关api性质的有用建议,那么这很有帮助,但如果您没有提供答案,为什么还要回答.(我想我应该提出自己的建议.) (82认同)
  • 对@nategood评论的回答很晚:我可以想到几个方面; 在这篇评论中解释得太多了,但谷歌他们.请记住,Node不会被阻止,所以这些并不完美.将它们视为建议.无论如何,这里是:(1)使用C实现您的功能并将其发布到NPM以便使用它.这就是`sync`方法的作用.(2)使用光纤,https://github.com/laverdet/node-fibers,(3)使用promises,例如Q-library,(4)在javascript上使用薄层,看起来像阻塞,但是编译为异步,如http://maxtaco.github.com/coffee-script (7认同)
  • 太好了.如果myApi.exec从未调用回调怎么办?我怎么做才能让回调在说出10秒之后被调用,并带有一个错误值,说它是我们的什么时间? (5认同)
  • 或者更好(添加一个检查,以便回调不能被调用两次):http://jsfiddle.net/LdaFw/1/ (5认同)
  • 我看到你的观点@HowardSwope.由于这是早些时候提出的,我在三年前添加了评论和建议.我希望你和其他人发现有用.直到今天,人们常常误解了应该如何处理异步,而不是他们实际上有实际阻止的情况.我本可以以一种更有帮助的友好方式,但同意. (2认同)

Tim*_*imo 26

实现此目的的一种方法是将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();
Run Code Online (Sandbox Code Playgroud)

输出:

Your query was <query all users>
ERROR:problem with the query
Run Code Online (Sandbox Code Playgroud)

  • 这是一个用回调包装函数的很好的例子,这样你就可以将它与“async/await”一起使用,我不经常需要这个,所以很难记住如何处理这种情况,我复制这个作为我的个人笔记/参考。 (3认同)

Luc*_*ato 23

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

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

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


vis*_*tel 10

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

});
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅:https://github.com/kriskowal/q


Mar*_*eli 9

如果你想要它非常简单和容易,没有花哨的库,等待在节点中执行回调函数,在执行其他代码之前,是这样的:

//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();
}
Run Code Online (Sandbox Code Playgroud)


Mac*_*zyk 7

现在是 2020 年,API 很可能已经有了一个可与 wait 配合使用的基于 Promise 的版本。但是,某些接口,尤其是事件发射器将需要此解决方法:

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

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

let response = await new Promise((resolve) => {
  myAPI.exec('SomeCommand', (response) => { resolve(response); });
});
Run Code Online (Sandbox Code Playgroud)

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


小智 6

从节点 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));
Run Code Online (Sandbox Code Playgroud)


Alb*_*ert 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;
}
Run Code Online (Sandbox Code Playgroud)

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

请注意,这种方法会使Nodej的整个目的无效,即使所有内容都异步和非阻塞.此外,它可能会大大增加您的callstack深度,因此最终可能会出现堆栈溢出.如果你递归地运行这样的函数,你肯定会遇到麻烦.

请参阅有关如何重新设计代码以"正确"执行此操作的其他答案.

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