6

可能重复:
javascript:使用一个回调执行一堆异步方法

几天来我一直在努力解决这个问题,但我就是想不出一种优雅的方式来处理它。这就是问题所在。

我正在运行一个 forEach 循环,我需要知道一切何时完成。由于 forEach 循环是阻塞的,因此在 forEach 循环之后删除 a 应该很容易console.log,它将在 forEach 循环完成时运行。但是,如果 forEach 循环中有任何不同步的函数,整个系统很快就会崩溃,除了非常奇怪的 hacky 计数器和 if 语句之外,我无法知道循环的内容何时完成。我的问题是在 javascript 中是否有一种优雅的方式来处理这种情况。

这里有几个例子:

在这种情况下,forEach 循环运行得非常好,因为循环中的所有内容都是同步的。

[1,2,3].forEach(function(num){
  console.log("we're on " + num);
});
console.log('done counting!')

//=> we're on 1
//=> we're on 2
//=> we're on 3
//=> done counting!

这是我们开始遇到问题的地方,作为一个基本示例:

[1,2,3].forEach(function(num){
  setTimeout(function(){ console.log(num) }, 200);
});
console.log('done counting!');

//=> done counting!
//=> we're on 1
//=> we're on 2
//=> we're on 3

尽管发生这种情况确实是有道理的,但我现在手头有一个不幸的问题,当我们完成计数时我需要回调,而且除了像这样恶心的东西之外,我没有其他顺利的方法:

var counter = 0;
var numbers = [1,2,3]

var log_number = function(num){
  console.log("we're on " + num);
  counter++;
  if (counter == numbers.length) {
    console.log("done counting!")
  }
}

numbers.forEach(function(num){
  setTimeout(log_number(num), 200);
});

//=> we're on 1
//=> we're on 2
//=> we're on 3
//=> done counting!

天哪,这是一个非常多的开销。有没有更顺畅的方法来实现这一点?我已经检查了承诺、延迟和序列,但我还没有真正能够以一种更简洁的方式实现它们中的任何一个:(

需要明确的是,我的问题并不是 setTimeout,它通常在同步函数中运行异步函数——这只是最简单的例子。

4

6 回答 6

7
Array.prototype.forEachDone = function(fn, scope, lastfn) {
    for(var i = 0, c = 0, len = this.length; i < len; i++) {
        fn.call(scope, this[i], i, this, function() {
            ++c === len && lastfn();
        });
    }
};

[1,2,3].forEachDone(function(num, i, arr, done){
  setTimeout(function(){ console.log(num); done() }, 200);
}, this, function() {
    console.log('done counting!');
});

这将满足您的需求。forEachDone 将采用与 forEach 完全相同的参数,最后还有一个额外的参数,它是在应用于数组的每个元素的函数完成时调用的回调。将应用于数组中每个元素的函数也采用与 forEach 完全相同的参数,但还有一个额外的参数,该函数应该在函数完成时调用。

PS:就个人而言,我永远不会接触原生对象的原型,但如果你正在寻找漂亮,就是这样。

于 2012-11-04T02:54:45.917 回答
4
var list = ["your", "mom"];
var count = 0;

for(var i = 0; i < list.length; ++i) {
    getSomething(list[i], function() {
     if(++count == list.length)
      imDone();
     });
}

另一种方式是这样的

var biscuits = ["cheddar", "butter milk"];

(function eatBiscuits(location) {
  eatIt(biscuits[++location], whenAllEaten);
  typeof biscuits[location] != "undefined" && eatBiscuits(location);
}(-1))

function eatIt(biscuit, cb) {
 if (typeof biscuit == "undefined") {
  cb();
 } else {
  stuffMyFace();
 }
}
于 2012-11-04T00:47:03.017 回答
1

我倾向于使用这样的小实用功能:

​function asyncCallback( iterations, onComplete ){
  var total = iterations.length || iterations, count = 0;
  return function( func ){
    func();
    if ( ++count == total ) onComplete();
  };
}

var arr = [1,2,3];

var async = asyncCallback(arr, function(){
  console.log('donezo!');
});

arr.forEach(function(num){
  setTimeout(function(){
    async(function(){
      console.log(num);
    });
  }, 200);
});

​</p>

于 2012-11-04T02:21:24.753 回答
1

如果您愿意使用 lib,那么async将是一个不错的选择(适用于浏览器以及 node.js)!

async.parallel([
    function(){ ... },
    function(){ ... }
], callback);

async.series([
    function(){ ... },
    function(){ ... }
]);
于 2012-11-04T01:36:27.500 回答
0

实际上,没有“聪明”的方法可以做到这一点。您需要使用类似setTimeout, setInterval, Worker

于 2012-11-04T00:31:31.277 回答
-1
[1,2,3,4,5,6,7,8].forEach(function(n){
 setTimeout(function(){
  console.log(arguments[0]);
 },1000*n,n);
});

这对我来说很好

于 2012-11-04T00:30:20.293 回答