【问题标题】:JS wait for callback to finish inside a loopJS 等待回调在循环内完成
【发布时间】:2014-07-03 01:31:28
【问题描述】:

我的节点 js 应用程序中有一个 for 循环;在每次迭代的这个循环中,可以执行一个 mysql 查询(并不总是,取决于);查询是异步的,我在成功回调中得到结果;但我需要 for 循环的每次迭代都等待回调完成(如果需要):

function calculate() {
    var resArray = [];

    //[...]

    for (i = 0; i < tables.length; i++) {
        if (id !== undefined) {
            var queryString = ...  //build query
            sequelize.query(queryString).success(function(result) {   //executes query
                id = result[0].id;

                //do stuff with id returned by the query
                //...
                //resArray.push( //push in resArray the result of stuff done above )
            });  
       }
       else {
           //do other kind of stuff and push result in resArray
       }
   }
   return resArray;
}

如果 id != undefined,则执行查询,但 for 循环不等待成功回调,并且函数返回不完整的 resArray。

我该怎么做(或如何重新组织代码)来完成这项工作? (我正在使用 node js,我没有可能使用 jQuery)。

【问题讨论】:

  • 你是否意识到 node.js 是单线程的,它会冻结所有其他请求?
  • 不,循环不会等待。你需要让你的应用异步。
  • 学习 promise 的好时机
  • 您可以使用 Google Traceur,它可以访问 ES6 功能,例如 yield。然后你可以有异步的for循环,里面有yield。更多细节:stackoverflow.com/a/23234019/1768303

标签: javascript asynchronous


【解决方案1】:

感谢 Amadan 的建议,我使用混合方法(带有承诺和计数器)摆脱了这个问题;

我真的很想听听您对此的看法以及您是否有改进建议;

首先,我在项目中添加了 q 库 (npm install q) 以使用 Promise。 感谢 q 我已经能够将 sequelize.query (基于回调)包装在一个返回承诺的函数中; Promise 的计数器会在内存中保存未决的 Promise 数量,并且包装函数 executeQuery 在每个 Promise 被解决时将其递减;

(cycle()函数中的for循环执行4次循环;执行2次查询和2次简单日志; 目标是“完成”日志仅在 for 循环结束并且所有承诺都解决后才会发送到屏幕

var id = req.params.id;
var counter = 0;
var endCycle = false;

var queryString = 'SELECT * FROM offers WHERE id = ' + id;

function query () {
    var deferred = Q.defer();
    sequelize.query(queryString).success(function(result) {
        console.log('obtained result');
        deferred.resolve(result);
    });
    return deferred.promise;
}

function executeQuery() {
    query().then(function() {
        console.log('after');
        counter --;
        console.log(counter);
        finished();
    });
}

function finished() {
    if ((counter === 0) && (endCycle)) {
        console.log('finished');
        endCycle = false;
    }
}

function cycle() {
    var result;

    for (i = 0; i <= 3; i ++) {
        if (i > 1) {
            counter ++;
            executeQuery();
        }
        else {
            console.log('else');
        }
    }

    endCycle = true;
    finished();
}

cycle();

------------------ 更新 ------------- --------------------------------

根据hugomg 的建议,我更新了代码,使其更简洁: 我将每个 promise 推送到一个数组中,然后使用 Q.all 等待它们都被解决

var id = req.params.id;
var promisesArray = [];
var endCycle = false;

var queryString = 'SELECT * FROM offers WHERE id = ' + id;
function query () {
    var deferred = Q.defer();
    sequelize.query(queryString).success(function(result) {
        console.log('obtained result');
        deferred.resolve(result);
        finished();
    });
    promisesArray.push(deferred.promise);
}

function finished() {
    if (endCycle) {
        endCycle = false;
        Q.all(promisesArray).then(function() {
            console.log('finished');
        });            
    }
}

function cycle() {
    var result;

    for (i = 0; i <= 3; i ++) {
        if (i > 1) {
            query();
        }
        else {
            console.log('else');
        }
    }
    endCycle = true;
    finished();
}

cycle();

【讨论】:

  • 您可以使用Q.all 而不是自己跟踪该计数器。此外,到目前为止,您所做的并没有真正利用承诺。您的代码与等效的回调版本非常相似。
  • 你能提供一个在这种情况下使用 Q.all 的例子吗?我试图了解如何做到这一点,但没有成功......
  • 使用query 函数返回的承诺创建一个数组。将这个承诺传递给Q.all,它会返回一个承诺,当所有查询都解决时,它就会解决。
  • 好的,我已经使用 Q.all 更新了答案;你还说我并没有真正利用承诺;你能解释更多吗?即使阅读了很多关于 Promise 的文档,我仍然有点困惑......哪个是使用它们的正确方法?
【解决方案2】:

你需要改变你的思维方式。如果calculate 调用一个异步方法,那么您获得其结果的唯一方法不是通过return,而是通过调用另一个回调。在 node.js 中,您不能将函数视为接受输入并返回输出的黑盒;您需要将其视为具有一定未来的过程中的一个步骤。你需要问自己的问题是——“我得到了这些结果;然后呢?”

就像sequelize.query,你告诉它“做这个查询,然后用结果做这个”,你需要能够调用calculate并告诉它“去做这些查询,然后用他们的结果做this”。你没有从sequelize.query 得到回报——你也不应该试图从calculate 得到回报。

Promises 会很好用,但不依赖它们,你可以数数。以下代码有四处变化:

// HERE: take a callback parameter
function calculate(callback) {
    var outstandingRequests = 0;
    var resArray = [];

    //[...]

    for (i = 0; i < tables.length; i++) {
        if (id !== undefined) {
            var queryString = ...  //build query

            // HERE: count how many responses you are expecting
            outstandingRequests++;

            sequelize.query(queryString).success(function(result) {   //executes query
                id = result[0].id;

                //do stuff with id returned by the query
                //...
                //resArray.push( //push in resArray the result of stuff done above )

                // HERE: check if all requests are done
                // if so, the array is as full as it will ever be
                // and we can pass the results on
            }).done(function() {
                if (!(--outstandingRequests)) {
                    callback(resArray);
                }
            });  
       }
       else {
           //do other kind of stuff and push result in resArray
       }
   }
   // HERE: return is useless in asynchronous code
}

然后你可以替换假设的和非功能性的

var resArray = calculate();
console.log("Here's your results:", resArray);

calculate(function(resArray) {
    console.log("Here's your results:", resArray);
});

编辑:将倒计时放入done 处理程序以解决可能的错误。

【讨论】:

    猜你喜欢
    • 2015-07-02
    • 1970-01-01
    • 2020-04-01
    • 1970-01-01
    • 2015-04-14
    • 2020-08-02
    • 1970-01-01
    • 2012-09-30
    • 1970-01-01
    相关资源
    最近更新 更多