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


266

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

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 /事件驱动”方式是什么?我希望我的函数等待,直到调用了回调,然后返回传递给它的值。


3
还是我在这里完全以错误的方式进行操作,是否应该调用另一个回调,而不是返回响应?
克里斯,

我认为为什么繁忙循环无法正常工作的最佳SO解释。
bluenote10 '17

不要试图等待。只需在回调本身结束时调用下一个函数(取决于回调)
Atul,

Answers:


282

做到这一点的“良好的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
});

5
好的太棒了。如果myApi.exec从未调用过回调该怎么办?我将如何使回调在说10秒钟后被调用,并显示一个错误值,表明它计时了我们或某物?
克里斯,

5
或更好的方法(添加了一项检查,因此回调函数
Jakob

148
显然,非阻塞是node / js中的标准,但是肯定有一些时候需要阻塞(例如,在stdin上阻塞)。偶数节点具有“阻止”方法(请参阅所有fs sync*方法)。因此,我认为这仍然是一个有效的问题。除了繁忙的等待之外,还有没有一种很好的方法可以在节点中实现阻塞?
nategood 2012年

7
@nategood对评论的最新回答:我可以想到几种方法。太多,无法在此评论中进行解释,但将其谷歌搜索。请记住,并不是要使Node处于阻塞状态,所以这些都不是完美的。将它们视为建议。无论如何,这里有:(1)使用C来实现您的功能并将其发布到NPM以便使用。这就是sync方法的作用。(2)使用fibregithub.com/laverdet/node-fibers,(3)使用Promise,例如Q库,(4)在javascript之上使用一薄层,看起来很阻塞,但编译为异步,像maxtaco.github.com/coffee-script
Jakob

106
当人们回答“您不应该那样做”的问题时,这真令人沮丧。如果您想提供帮助并回答问题,那是站不住脚的事情。但是明确地告诉我我不应该做某事只是不友好。某人想要同步或异步调用例程的原因有百万种。这是一个有关如何做的问题。如果您在提供答案的同时提供有关api性质的有用建议,那将很有帮助,但是如果您未提供答案,那么为什么还要回答。(我想我应该真正听取我的建议。)
Howard Swope

45

实现此目的的一种方法是将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 经常使用它,而我通常不需要此功能,因此在记住如何处理这种情况时会遇到麻烦,我将其复制为个人笔记/参考。
罗伯特·阿尔勒


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. 

});

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


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

5

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

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

请注意,这种方法使Node.js的整个目的无效,即使所有内容异步和无阻塞。另外,它可能会大大增加您的调用堆栈深度,因此您最终可能会出现堆栈溢出。如果递归运行这样的功能,肯定会遇到麻烦。

查看有关如何重新设计代码以“正确”完成操作的其他答案。

此解决方案仅在进行测试和esp时才有用。想要同步和串行代码。


5

从节点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));

1

假设您有一个功能:

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

这违反了非阻塞IO的目的-您在不需要阻塞时就将其阻塞 :)

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

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


我怀疑我有这种倒退。
克里斯,

31
可能是,我只想为http.get()一些URL console.log()及其内容编写一个快速脚本。为什么我必须在Node上向后跳过呢?
Dan Dascalescu 2013年

6
@DanDascalescu:为什么我必须声明类型签名才能使用静态语言?为何我必须将其放在类似C语言的主方法中?为何我必须使用编译语言进行编译?您要质疑的是Node.js中的基本设计决策。该决定有利有弊。如果您不喜欢它,则可以使用其他更适合您的风格的语言。这就是为什么我们有多个。
雅各布2014年

@Jakob:您列出的解决方案确实不是最理想的。这并不意味着没有好的,例如Meteor在光纤中使用服务器端的Node,从而消除了回调地狱的问题。
Dan Dascalescu 2014年

13
@Jakob:如果最好的答案是“为什么生态系统X会使共同任务Y不必要地变得困难?” 表示“如果您不喜欢它,请不要使用生态系统X”,这是一个强有力的信号,表明生态系统X的设计者和维护者将自己的自我优先考虑在其生态系统的实际可用性之上。根据我的经验,Node社区(与Ruby,Elixir甚至PHP社区相反)会竭尽全力使常见任务变得困难。非常感谢您提供自己作为该反模式的生动示例。
爵士乐

-1

使用异步并等待它要容易得多。

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不会返回承诺,因此您需要先将其包装起来……就像两年前的答案一样。
昆汀
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.