如何同步JavaScript回调?
我已经用JavaScript开发了很长一段时间了,但是net还是一个牛仔开发人员,因为我经常遇到的事情之一就是同步JavaScript的回调 当这个问题被提出时,我将描述一个通用场景:我有一堆操作要通过for循环执行多次,每个操作都有一个回调。在for循环之后,我需要执行另一个操作,但是这个操作只有在for循环的所有回调都完成后才能成功执行 代码示例:如何同步JavaScript回调?,javascript,multithreading,callback,synchronization,Javascript,Multithreading,Callback,Synchronization,我已经用JavaScript开发了很长一段时间了,但是net还是一个牛仔开发人员,因为我经常遇到的事情之一就是同步JavaScript的回调 当这个问题被提出时,我将描述一个通用场景:我有一堆操作要通过for循环执行多次,每个操作都有一个回调。在for循环之后,我需要执行另一个操作,但是这个操作只有在for循环的所有回调都完成后才能成功执行 代码示例: for ... in ... { myFunc1(callback); // callbacks are executed asynch
for ... in ... {
myFunc1(callback); // callbacks are executed asynchly
}
myFunc2(); // can only execute properly if all the myFunc1 callbacks are done
建议的解决方案:
for ... in ... {
myFunc1(callback); // callbacks are executed asynchly
}
myFunc2(); // can only execute properly if all the myFunc1 callbacks are done
在循环的开始处启动一个计数器,保存循环的长度,每个回调将递减该计数器。当计数器达到0时,执行myFunc2。这本质上是让回调知道它是否是序列中的最后一个回调,如果是,则在完成后调用myFunc2
问题:
for ... in ... {
myFunc1(callback); // callbacks are executed asynchly
}
myFunc2(); // can only execute properly if all the myFunc1 callbacks are done
for ... in ... {
myFunc1(callback); // callbacks are executed asynchly
}
myFunc2(); // can only execute properly if all the myFunc1 callbacks are done
有更好的解决方案吗?第二个问题不是真正的问题,只要它们都在一个单独的函数中,并且变量声明正确(使用
var
);函数中的局部变量不会相互干扰
第一个问题更像是一个问题。其他人也很恼火,最终制作了一些库来为您包装这种模式。我喜欢。使用它,您的代码可能如下所示:
async.each(someArray, myFunc1, myFunc2);
function whenAllDone() { ... }
for (... in ...) {
myFunc1(afterAll(callback,whenAllDone));
}
function myFunc1(value, callback)
{
console.log(value);
callback();
}
const arrayOfArg = ["one", "two", "three"];
const context = {}; // can be any kind of object, this is the threadish context
for(let arg of arrayOfArg) {
synchronizeCall(aPromise, arg); // synchronize the calls in the given context
}
join(context).then(() => { // join will resolve when all calls in the context are finshed
// carry on
});
它还提供了许多其他异步构建块。如果你正在做大量的异步工作,我建议你看看它。你可以通过使用jQuery延迟对象来实现这一点
var deferred = $.Deferred();
var success = function () {
// resolve the deferred with your object as the data
deferred.resolve({
result:...;
});
};
使用此辅助功能:
function afterAll(callback,what) {
what.counter = (what.counter || 0) + 1;
return function() {
callback();
if(--what.counter == 0)
what();
};
}
function serializeTasks(arr, fn, done)
{
var current = 0;
fn(function iterate() {
if (++current < arr.length) {
fn(iterate, arr[current]);
} else {
done();
}
}, arr[current]);
}
function loopFn(nextTask, value) {
myFunc1(value, nextTask);
}
function parallelizeTasks(arr, fn, done)
{
var total = arr.length,
doneTask = function() {
if (--total === 0) {
done();
}
};
arr.forEach(function(value) {
fn(doneTask, value);
});
}
您的循环将如下所示:
async.each(someArray, myFunc1, myFunc2);
function whenAllDone() { ... }
for (... in ...) {
myFunc1(afterAll(callback,whenAllDone));
}
function myFunc1(value, callback)
{
console.log(value);
callback();
}
const arrayOfArg = ["one", "two", "three"];
const context = {}; // can be any kind of object, this is the threadish context
for(let arg of arrayOfArg) {
synchronizeCall(aPromise, arg); // synchronize the calls in the given context
}
join(context).then(() => { // join will resolve when all calls in the context are finshed
// carry on
});
在这里
毕竟
为回调创建了代理函数,它还递减计数器。并在所有回调完成时调用whenaldone函数。好消息是JavaScript是单线程的;这意味着解决方案通常可以很好地处理“共享”变量,即不需要互斥锁
如果要序列化异步任务,然后执行完成回调,则可以使用以下帮助函数:
function afterAll(callback,what) {
what.counter = (what.counter || 0) + 1;
return function() {
callback();
if(--what.counter == 0)
what();
};
}
function serializeTasks(arr, fn, done)
{
var current = 0;
fn(function iterate() {
if (++current < arr.length) {
fn(iterate, arr[current]);
} else {
done();
}
}, arr[current]);
}
function loopFn(nextTask, value) {
myFunc1(value, nextTask);
}
function parallelizeTasks(arr, fn, done)
{
var total = arr.length,
doneTask = function() {
if (--total === 0) {
done();
}
};
arr.forEach(function(value) {
fn(doneTask, value);
});
}
传递的第一个参数是将执行下一个任务的函数,它将传递给异步函数。第二个参数是值数组的当前条目
假设异步任务如下所示:
async.each(someArray, myFunc1, myFunc2);
function whenAllDone() { ... }
for (... in ...) {
myFunc1(afterAll(callback,whenAllDone));
}
function myFunc1(value, callback)
{
console.log(value);
callback();
}
const arrayOfArg = ["one", "two", "three"];
const context = {}; // can be any kind of object, this is the threadish context
for(let arg of arrayOfArg) {
synchronizeCall(aPromise, arg); // synchronize the calls in the given context
}
join(context).then(() => { // join will resolve when all calls in the context are finshed
// carry on
});
它打印值,然后调用回调;简单
然后,要启动整个过程:
serializeTasks([1,2, 3], loopFn, function() {
console.log('done');
});
要并行化它们,您需要一个不同的函数:
function afterAll(callback,what) {
what.counter = (what.counter || 0) + 1;
return function() {
callback();
if(--what.counter == 0)
what();
};
}
function serializeTasks(arr, fn, done)
{
var current = 0;
fn(function iterate() {
if (++current < arr.length) {
fn(iterate, arr[current]);
} else {
done();
}
}, arr[current]);
}
function loopFn(nextTask, value) {
myFunc1(value, nextTask);
}
function parallelizeTasks(arr, fn, done)
{
var total = arr.length,
doneTask = function() {
if (--total === 0) {
done();
}
};
arr.forEach(function(value) {
fn(doneTask, value);
});
}
循环函数将如下所示(仅参数名称更改):
单线程并不总是得到保证。不要误会 案例1: 例如,如果我们有两个函数,如下所示
var count=0;
function1(){
alert("this thread will be suspended, count:"+count);
}
function2(){
//anything
count++;
dump(count+"\n");
}
然后在function1返回之前,也会调用function2,如果保证有1个线程,那么在function1返回之前不会调用function2。你可以试试这个。当你被提醒时,你会发现计数在上升
案例2:对于Firefox,chrome代码,在1个函数返回之前(内部没有警报),还可以调用另一个函数
因此,确实需要一个互斥锁。实现这一点的方法很多,我希望这些建议能有所帮助 首先,我要将回调转换为承诺!有一种方法可以做到这一点:
function aPromise(arg) {
return new Promise((resolve, reject) => {
aCallback(arg, (err, result) => {
if(err) reject(err);
else resolve(result);
});
})
}
接下来,使用reduce逐个处理数组的元素
const arrayOfArg = ["one", "two", "three"];
const promise = arrayOfArg.reduce(
(promise, arg) => promise.then(() => aPromise(arg)), // after the previous promise, return the result of the aPromise function as the next promise
Promise.resolve(null) // initial resolved promise
);
promise.then(() => {
// carry on
});
如果要同时处理数组的所有元素,请使用map-an-Promise.all
const arrayOfArg = ["one", "two", "three"];
const promise = Promise.all(arrayOfArg.map(
arg => aPromise(arg)
));
promise.then(() => {
// carry on
});
如果您能够使用async/await,那么您只需执行以下操作:
const arrayOfArg = ["one", "two", "three"];
for(let arg of arrayOfArg) {
await aPromise(arg); // wow
}
// carry on
您甚至可以像这样使用我非常酷的库:
async.each(someArray, myFunc1, myFunc2);
function whenAllDone() { ... }
for (... in ...) {
myFunc1(afterAll(callback,whenAllDone));
}
function myFunc1(value, callback)
{
console.log(value);
callback();
}
const arrayOfArg = ["one", "two", "three"];
const context = {}; // can be any kind of object, this is the threadish context
for(let arg of arrayOfArg) {
synchronizeCall(aPromise, arg); // synchronize the calls in the given context
}
join(context).then(() => { // join will resolve when all calls in the context are finshed
// carry on
});
最后但并非最不重要的一点是,如果你真的不想使用承诺,请使用fine库
const arrayOfArg = ["one", "two", "three"];
async.each(arrayOfArg, aCallback, err => {
if(err) throw err; // handle the error!
// carry on
});
有趣的图书馆;它是否总是假定函数接受两个参数,第二个是回调?感谢共享,但我认为第二个问题可能是个问题,因为所有函数都在递减同一个共享变量(在我的例子中是计数器)但不是它们自己的局部变量。@Xavier_Ex:只要您的计数器是在与
for
循环相同的函数中声明的,它就是函数中唯一的计数器(具有该名称),当您在回调中引用它时,您就是在引用该计数器,您应该会没事的。@icktoofay谢谢,但我不太明白你的解释。尽管计数器是函数中的局部变量,但它被传递给所有回调函数,因此它们实际上是在修改共享变量。你介意举个例子说明什么是问题吗?@Xavier_Ex:当然,它在所有回调中都是共享的,但不会与其他函数混淆。没有问题,;我之所以称之为“问题”,是因为您在问题中将其标记为“问题”。感谢您的分享,我可能需要先看看jquery“延迟”是如何工作的。我认为这与使用承诺是相同的解决方案。需要阅读承诺。谢谢分享,但这最终和柜台是一样的。我不知道公众的情况,但我确实觉得没有意义的计数器很烦人…@Xavier_Ex我不理解你的评论,任何任务的解决方案都会使用某种计数器。考虑<代码>毕竟<代码>作为库函数,在需要的地方使用它。是的,这就是我想知道的,如果使用计数器是唯一的方法。我知道一些并发问题可以通过协作例程来解决,因此我想在这里探讨一下可能性。最终,计数器不是我喜欢的解决方案