forEach循环内的异步函数和回调

时间:2016-10-31 15:12:43

标签: javascript node.js

在此功能内:

function Example(array, callback){
    var toReturn = [];

    // variable 'array' has 2 elements in this case
    array.forEach(function(el){
        MongooseModel.AsyncFunction(el, function(err, doc){
            if(err || !doc){ return callback('Error'); }
            toReturn.push(doc)
        });
    });

    return callback(null, toReturn)
}

有几点需要注意:

  1. toReturn.push(doc)没有工作,最后一次回调是空的 返回数组。这是为什么?
  2. 如果出现错误,或者!docs为true,则永远不会调用return callback('Error')。始终会调用return callback(null, toReturn)
  3. 另外,如果我在异步函数上面放回调,就像这样:

    function Example(array, callback){
        var toReturn = [];
    
        // variable 'array' has 2 elements in this case
        array.forEach(function(el){
            // another callback here
            return callback('Error')
            MongooseModel.AsyncFunction(el, function(err, doc){
                if(err || !doc){ return callback('Error'); }
                toReturn.push(doc)
            });
        });
    
        return callback(null, toReturn)
    }
    

    脚本崩溃,因为已经调用了多个回调。为什么每个人都这样做以及如何避免它?

3 个答案:

答案 0 :(得分:8)

您的问题

  

toReturn.push(doc)不起作用,在最后一个回调中返回一个空数组。那是为什么?

您在callback有机会执行MongooseModel.AsyncFunction之前致电function(err,doc)

  

如果出现错误,或者!docs为true,则永远不会调用return callback('Error')。始终调用return callback(null,toReturn)。

在安排异步功能后立即调用成功返回。 callback('Error')之后可能会稍后调用MongooseModel.AsyncFunction

  

脚本崩溃,因为已经调用了多个回调

这正是你要求的行为!您的第二个代码字面上说:“对于每个元素,请调用一次回调”。

一些指针

总而言之,我认为你唯一错误的是调用回调有点类似于从函数返回。

事实并非如此!您的函数Example应该安排一些异步事件发生,然后立即返回(不返回任何内容),但承诺callback(error)callback(null, success)将在稍后的某个时间被称为

因此,永远不要说return callback() - 仅callback()会这样做。当您有一些数据要调用callback时,函数Example将已经完成执行。最终调用callback的函数将匿名函数传递给MongooseModel.AsyncFunction,而不是Example本身。

现在是什么?

您可以尝试这样的方法:

function Example(array, callback){
    var toReturn = [];
    var previousError = false;

    array.forEach(function(el){
        MongooseModel.AsyncFunction(el, function(err, doc){
            if (previousError) {
                return;
            }
            if(err || !doc) {
                previousError = true;
                callback('Error');
            }
            toReturn.push(doc)
            if (toReturn.length === array.length) {
                // that was the last push - we have completed
                callback(null, toReturn);
            }
        });
    });
}

这里发生的事情:

  • 每次AsyncFunction完成时,都会汇总结果。如果那是最后一个,您最终可以调用callback并传递整个数据。 (否则,我们正在等待更多功能,所以没有callback

  • 如果在此过程中某处出现错误,我们会立即报告错误,但请注意已经报告错误,以便进一步执行已安排的AsyncFunction特别做任何事情。这可以防止您callback被调用两次或更多次。

  • 请注意 - toReturn内的元素顺序是随机的,具体取决于首先完成的异步任务。

但这很混乱

哦,是的。这就是为什么我们不再做回调了。有一个名为promises的模式使得使用异步回调意大利面更加容易,我建议在继续前进之前先阅读一下这个主题。

让它变得整洁

使用promises,这种代码看起来像:

function Example(array) {
    var promises = array.map(function(el) {
        return MongooseModel.AsyncFunction(el);
    });
    return Promise.all(promises);
}

它更短,并且在输出项的错误处理或排序方面也没有任何问题,就像我们之前的例子一样。

用法很简单 - 而不是这个

Example([1, 2, 3], function(error, results) {
    if (error !== null) {
        // ...
    } else { 
        // ...
    }
})
你打电话是这样的:

Example([1, 2, 3]).then(
    function(results) {
        // ...
    },
    function(error) {
        // ...
    }
);

哦,这依赖于MongooseModel.AsyncFunction返回一个承诺,但Mongoose already does this大多数库也是如此。如果库没有,您可以轻松添加承诺支持(例如,请参阅NodeRedis)。

如何在返回之前对AsyncFunction的每个结果执行某些操作?

很容易!

function Example(array) {
    var promises = array.map(function(el) {
        return MongooseModel.AsyncFunction(el).then(function(doc) {
            return doc + "some modifications";
        });
    });
    return Promise.all(promises);
}

或者如果您需要其他错误处理:

function Example(array) {
    var promises = array.map(function(el) {
        return MongooseModel.AsyncFunction(el).then(function(doc) {
            if (theresSomethingWrongWith(doc) {
                return Promise.reject(new Error("ouch!"));
            }
            return doc + "some modifications";
        });
    });
    return Promise.all(promises);
}

想一想将被拒绝的承诺归类为类似于提出异常的东西 - 它会自然地冒出一直到Example的回归承诺。

答案 1 :(得分:2)

基本上,您的功能按以下顺序执行:

  1. 触发array
  2. 中每个元素的异步操作
  3. 触发所有异步调用后立即返回
  4. 异步调用返回结果,但您的函数已经返回 - 因此它们最终无处可去。
  5. 要执行多个异步调用并返回结果,您需要等待所有这些调用完成。我通常在库async的帮助下解决这个问题,它甚至可以控制异步调用的执行方式(串行或并行)。例如,您可以使用异步函数数组调用async.parallel

    async.parallel(array.map(function() {
      return MongooseModel.AsyncFunction(...)
    }), function(err, results) {
      // all the async calls are finished, do something with the results
    });
    

答案 2 :(得分:0)

你应该使用promises,因为你需要在返回结果之前等待所有异步调用。这是一个例子:

function Example(array, callback){
    new Promise(function(resolve, reject) {
        var toReturn = [];

        array.forEach(function(el){
            MongooseModel.AsyncFunction(el, function(err, doc){
                if(err || !doc) {
                    return reject();
                }
                toReturn.push(doc);
                if (toReturn.length === array.length) {
                    resolve(toReturn);
                }
            });
        });
    })
        .then(callback)
        .catch(function() {
            callback('error');
        });
}

在这里,您可以阅读有关承诺的更多信息:https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise