正如标题所示。我怎么做呢?
我想在foreach循环遍历每个元素并完成一些异步处理后调用whenAllDone()。
[1, 2, 3].forEach(
function(item, index, array, done) {
asyncFunction(item, function itemDone() {
console.log(item + " done");
done();
});
}, function allDone() {
console.log("All done");
whenAllDone();
}
);
有可能让它这样工作吗?当forEach的第二个参数是一个回调函数,当它经过所有迭代时运行?
预期的输出:
3 done
1 done
2 done
All done!
数组中。forEach并没有提供这样的细节(如果可以的话),但是有几种方法可以实现你想要的:
使用一个简单的计数器
function callback () { console.log('all done'); }
var itemsProcessed = 0;
[1, 2, 3].forEach((item, index, array) => {
asyncFunction(item, () => {
itemsProcessed++;
if(itemsProcessed === array.length) {
callback();
}
});
});
这种方法保证在调用"done"回调之前处理所有项。您需要使用一个在回调中更新的计数器。依赖于索引参数的值并不能提供相同的保证,因为异步操作的返回顺序是不能保证的。
使用ES6承诺
(一个承诺库可以用于较旧的浏览器):
Process all requests guaranteeing synchronous execution (e.g. 1 then 2 then 3)
function asyncFunction (item, cb) {
setTimeout(() => {
console.log('done with', item);
cb();
}, 100);
}
let requests = [1, 2, 3].reduce((promiseChain, item) => {
return promiseChain.then(() => new Promise((resolve) => {
asyncFunction(item, resolve);
}));
}, Promise.resolve());
requests.then(() => console.log('done'))
Process all async requests without "synchronous" execution (2 may finish faster than 1)
let requests = [1,2,3].map((item) => {
return new Promise((resolve) => {
asyncFunction(item, resolve);
});
})
Promise.all(requests).then(() => console.log('done'));
使用异步库
还有其他异步库,async是最流行的,它们提供了表达所需内容的机制。
Edit
问题的主体已被编辑以删除之前的同步示例代码,因此我更新了我的答案以澄清。
最初的例子使用了类似于同步的代码来模拟异步行为,因此应用了以下内容:
数组中。forEach是同步的,res.write也是同步的,所以你可以简单地把你的回调放在你调用forEach之后:
posts.foreach(function(v, i) {
res.write(v + ". index " + i);
});
res.end();
希望这将解决你的问题,我通常与此工作时,我需要执行forEach与异步任务。
foo = [a,b,c,d];
waiting = foo.length;
foo.forEach(function(entry){
doAsynchronousFunction(entry,finish) //call finish after each entry
}
function finish(){
waiting--;
if (waiting==0) {
//do your Job intended to be done after forEach is completed
}
}
与
function doAsynchronousFunction(entry,callback){
//asynchronousjob with entry
callback();
}
奇怪的是,异步情况下有多少错误的答案!
可以简单地表明,检查索引没有提供预期的行为:
// INCORRECT
var list = [4000, 2000];
list.forEach(function(l, index) {
console.log(l + ' started ...');
setTimeout(function() {
console.log(index + ': ' + l);
}, l);
});
输出:
4000 started
2000 started
1: 2000
0: 4000
如果我们检查index === array。长度- 1,回调将在第一次迭代完成时被调用,而第一个元素仍然悬而未决!
要在不使用async等外部库的情况下解决这个问题,我认为最好的方法是保存列表的长度,并在每次迭代后递减。因为只有一个线程,我们确定没有竞争条件的机会。
var list = [4000, 2000];
var counter = list.length;
list.forEach(function(l, index) {
console.log(l + ' started ...');
setTimeout(function() {
console.log(index + ': ' + l);
counter -= 1;
if ( counter === 0)
// call your callback here
}, l);
});
我的解决方案:
//Object forEachDone
Object.defineProperty(Array.prototype, "forEachDone", {
enumerable: false,
value: function(task, cb){
var counter = 0;
this.forEach(function(item, index, array){
task(item, index, array);
if(array.length === ++counter){
if(cb) cb();
}
});
}
});
//Array forEachDone
Object.defineProperty(Object.prototype, "forEachDone", {
enumerable: false,
value: function(task, cb){
var obj = this;
var counter = 0;
Object.keys(obj).forEach(function(key, index, array){
task(obj[key], key, obj);
if(array.length === ++counter){
if(cb) cb();
}
});
}
});
例子:
var arr = ['a', 'b', 'c'];
arr.forEachDone(function(item){
console.log(item);
}, function(){
console.log('done');
});
// out: a b c done
我没有承诺的解决方案(这可以确保每个动作在下一个动作开始之前结束):
Array.prototype.forEachAsync = function (callback, end) {
var self = this;
function task(index) {
var x = self[index];
if (index >= self.length) {
end()
}
else {
callback(self[index], index, self, function () {
task(index + 1);
});
}
}
task(0);
};
var i = 0;
var myArray = Array.apply(null, Array(10)).map(function(item) { return i++; });
console.log(JSON.stringify(myArray));
myArray.forEachAsync(function(item, index, arr, next){
setTimeout(function(){
$(".toto").append("<div>item index " + item + " done</div>");
console.log("action " + item + " done");
next();
}, 300);
}, function(){
$(".toto").append("<div>ALL ACTIONS ARE DONE</div>");
console.log("ALL ACTIONS ARE DONE");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="toto">
</div>
在这个帖子中有许多解决方案和方法来实现这一点!
但是,如果你需要用map和async/await来做这个,那么这里就是
// Execution Starts
console.log("start")
// The Map will return promises
// the Execution will not go forward until all the promises are resolved.
await Promise.all(
[1, 2, 3].map( async (item) => {
await asyncFunction(item)
})
)
// Will only run after all the items have resolved the asynchronous function.
console.log("End")
输出将如下所示!可能根据异步函数而有所不同。
start
2
3
1
end
注意:如果你在map中使用await,它总是会返回promises数组。
//First Example
save() {
this.holdImageData.forEach((val,i) => {
this.UploadToMinio(val.file, val.index, res => {
if (res && i+1 == this.holdImageData.length) {
this.FinallySave();
}
})
})
}
UploadToMinio(files, index, callback) {
this._fileUploadService.uploadFile(files[0], files[0].name, 'rms').subscribe(data => {
if (data) {
console.log('data >>> ', data);
callback(true);
}
})
}
FinallySave() {}
//Second Example
var sum = 0; // It can be global variable
startFunction() {
this.sumFunction(2, 4, res => {
if (res == true && sum == 6) {
this.saveFunction();
} else {
//call to another function
}
})
}
sumFunction(num1, num2, callback) {
if ((num1 + num2) == 6) {
callback(true)
}
callback(false);
}