我在JavaScript开发了相当长的一段时间,但仍然净牛仔开发商,如的很多事情总是困扰着我的synching JavaScript的回调之一。
我将描述一个普通的场景时,这一问题将得到提升:我有一大堆的操作由一个for循环执行多次,并且每个操作都有一个回调。 之后的for循环,我需要执行其他操作,但如果所有从回调的循环完成此操作只能成功执行。
代码示例:
for ... in ... { myFunc1(callback); // callbacks are executed asynchly } myFunc2(); // can only execute properly if all the myFunc1 callbacks are done
建议的解决方案:
发起在环形保持环的长度的开始的计数器,和每个回调递减该计数器。 当计数器命中0,执行myfunc2所。 这主要是为了让回调知道,如果它在序列中的最后一个回调,如果是,请拨打myfunc2所当它这样做。
问题:
- 需要计数器代码中的每一个这样的序列,且无处不在无意义的柜台是不是一个好的做法。
- 如果你还记得如何在经典的同步问题线程冲突,当多个线程都在同一个变种叫var--,会出现不良后果。 做同样在JavaScript中发生的呢?
最终的问题:
有没有更好的解决办法?
好消息是,JavaScript是单线程; 这意味着,解决方案通常会与“共享”变量工作良好,即不互斥需要锁。
如果你想序列非同步的任务,然后完成回调,你可以使用这个辅助函数:
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 myFunc1(value, callback)
{
console.log(value);
callback();
}
它打印的价值,此后,它调用回调; 简单。
然后,设置在运动的整个事情:
serializeTasks([1,2, 3], loopFn, function() {
console.log('done');
});
演示
并行它们,你需要不同的功能:
function parallelizeTasks(arr, fn, done)
{
var total = arr.length,
doneTask = function() {
if (--total === 0) {
done();
}
};
arr.forEach(function(value) {
fn(doneTask, value);
});
}
和你的循环功能,将是(唯一的参数名称更改):
function loopFn(doneTask, value) {
myFunc1(value, doneTask);
}
演示
The second problem is not really a problem as long as every one of those is in a separate function and the variable is declared correctly (with var
); local variables in functions do not interfere with each other.
The first problem is a bit more of a problem. Other people have gotten annoyed, too, and ended up making libraries to wrap that sort of pattern for you. I like async
. With it, your code might look like this:
async.each(someArray, myFunc1, myFunc2);
It offers a lot of other asynchronous building blocks, too. I'd recommend taking a look at it if you're doing lots of asynchronous stuff.
您可以通过使用一个jQuery Deferred对象实现这一目标。
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 whenAllDone() { ... }
for (... in ...) {
myFunc1(afterAll(callback,whenAllDone));
}
这里afterAll
创造了回调代理功能,它也递减计数器。 并调用whenAllDone功能时,所有的回调完成。
单个线程并不总是保证。 不要把它错了。
案例1:例如,如果我们有2个功能如下。
var count=0;
function1(){
alert("this thread will be suspended, count:"+count);
}
function2(){
//anything
count++;
dump(count+"\n");
}
然后功能1返回前,函数2也将被调用时,如果1个线程保证,那么函数2不会FUNCTION1返回之前调用。 你可以试试这个。 你会发现数量正在上升,而你是被警告。
案例2:使用Firefox,Chrome的代码,前1点函数返回(无警报内),其他功能也可以被调用。
因此,我们确实需要一个互斥锁。
有很多很多的方式来实现这一点,我希望这些建议帮助!
首先,我将改变回调到一个承诺! 这里是做的一个方法:
function aPromise(arg) {
return new Promise((resolve, reject) => {
aCallback(arg, (err, result) => {
if(err) reject(err);
else resolve(result);
});
})
}
接下来,使用减少由一个处理一个阵列的一个的元素!
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
});
如果你想在同一时间处理一个数组的所有元素,使用地图的Promise.all!
const arrayOfArg = ["one", "two", "three"];
const promise = Promise.all(arrayOfArg.map(
arg => aPromise(arg)
));
promise.then(() => {
// carry on
});
如果你能使用异步/等待那么你可以只是简单的做到这一点:
const arrayOfArg = ["one", "two", "three"];
for(let arg of arrayOfArg) {
await aPromise(arg); // wow
}
// carry on
你甚至可以用我的非常酷的同步,异步库是这样的:
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
});
最后但并非最不重要的,用细异步库,如果你真的不想使用的承诺。
const arrayOfArg = ["one", "two", "three"];
async.each(arrayOfArg, aCallback, err => {
if(err) throw err; // handle the error!
// carry on
});