我希望这有意义: 我需要在javascript中创建一个foreach函数,它将像这样使用:
foreach(["A", "B", "C"], function(letter, done) {
// do something async with 'letter'
doSomthing(letter, done); // ***
}, function () {
// final callback that is called after all array has been visted.
// do some final work
});
所以我在考虑以下实施:
var foreach = function(array, func, ready) {
if (!array.length)
ready();
var that = this;
func(array[0], function(){
that.foreach(array.slice(1, array.length), func, ready);
});
}
它似乎确实有效!非常酷。
但我在想是否有一个不使用递归的解决方案? 我想不出一个......
答案 0 :(得分:1)
您的方法在技术上是正确的,但以这种方式做是不好的。 请在javasript中使用promise模式实现。 我建议你使用when.js在git上提供一个开源js来实现promise模式请参考下面的代码
var service = {
fetch: function (query) {
// return a promise from the function
return when(["A", "B", "C"].forEach(function (name) {
alert(name);
}));
}
};
service.fetch("hello world").then(function () {
alert("work has been completed");
});
答案 1 :(得分:1)
我现在再次出于学术目的而来到这里。现在,我建议每个人首先了解Aadit在优雅和简洁方面的方法,因为对于我来说,使用bind的技术对我来说是一个很好的学习经验,因为我不知道这一点,也不知道你可以在setTimeout之后放置的其他参数
在了解了这些内容后,我将代码缩减为:
var foreach = function(array,doSomething,onComplete) {
var i = 0, len = array.length, completeCount = 0;
for(;i < len; i++) {
window.setTimeout(function() {
doSomething(arguments[0]);
completeCount++;
if (completeCount === len) {
onComplete();
}
},0,array[i]);
}
};
我认为你必须有一个“completeCount”,因为虽然Aadit的代码是一个很好的简洁工作解决方案,它会自动减少数组,但它并不是真正的异步,因为在数组中每个方法完成后调用“next()”线性。 “completeCount”允许代码以任何顺序完成执行,这是我认为的重点。在Aadit的代码中,还有修改输入数组以及需要更改Function原型的副作用,我认为这不是必需的。 “吊装”也没有在他的代码中实践,我认为应该这样做,因为这种风格可以减少错误。
同样,我非常尊重Aadit的代码,并且花时间再次回来尝试根据我通过其他聪明人和Aadit学到的东西来提供更好的解决方案。我欢迎任何批评和更正,因为我将尝试从中学习。
仅供参考:这是一种通用的延期方法
var deferred = function(methods,onComplete) {
var i = 0, len = methods.length, completeCount = 0,
partialComplete = function() {
completeCount++;
if (completeCount === len) {
onComplete();
}
};
for(;i < len; i++) {
window.setTimeout(function() {
arguments[0](partialComplete);
},0,methods[i]);
}
};
// how to call it
deferred([
function (complete) {
// this could easily be ajax that calls "complete" when ready
complete();
},
function (complete) {
complete();
}
], function() {
alert('done');
});
答案 2 :(得分:0)
查看async库。它有几个不同的功能,并且得到了积极的支持。
答案 3 :(得分:0)
如果我错了,请纠正我但是从我的问题中我理解我认为你想要一个数组,在该数组的每个成员上异步地串行调用一个函数,然后在数组的每个成员执行一个回调函数已被处理。
现在要在浏览器环境中异步执行一个函数,我们会做this之类的事情:
Function.prototype.async = function () {
setTimeout.bind(window, this, 0).apply(window, arguments);
};
alert.async(5);
alert(6);
在上面的示例中,setTimeout
函数用于异步调用给定函数,因为我们首先看到值6
,然后警告值5
。
接下来,要使foreach
函数异步,我们会执行follows的操作:
function forEach(array, funct, callback) {
if (array.length)
funct.async(array[0], forEach.bind(null, array.slice(1), funct, callback));
else callback.async();
}
以上解决方案不使用recusrion。当然forEach
函数在其自身内被引用,但它仅在funct
函数中被调用,该函数被异步调用。因此forEach
函数在funct
函数中再次调用之前返回。
我在每段代码之前都包含了JS fiddles的链接。如果您有任何疑问,我很乐意回答。
修改强>
如果您不想修改prototype
Function
(@ kitgui.com),那么您可以使用此modified code:
var async = Function.prototype.call.bind(function () {
setTimeout.bind(null, this, 0).apply(null, arguments);
});
async(alert, 5);
alert(6);
由于我在上述代码中没有引用window
,因此它也适用于非浏览器环境。
然后我们可以按如下方式重写forEach
函数:
function forEach(array, funct, callback) {
if (array.length)
async(funct, array[0], forEach.bind(null, array.slice(1), funct, callback));
else async(callback);
}
我们有它。无需修改prototype
的{{1}}。 Function
的函数体几乎相同。我们使用async
为它创建了一个unbound wrapper。您可能会为自己看到live demo。
<强>加成:强>
您可以使用上述模式创建错误,如下所示(参见live demo):
call.bind
这相当于10行代码中caolan async库中的function forEach(array, funct, callback, error) {
if (array.length && !error)
async(funct, array[0], forEach.bind(null, array.slice(1), funct, callback));
else async(callback, error || null);
}
函数。
答案 4 :(得分:0)
假设您想要进行原始计算,并且希望它是异步的,因此它不会阻止浏览器。
我一直在使用“setTimeout(Func,0);”大约一年的伎俩。这是我最近写的一些研究,以解释如何加快它的速度。如果您只想要答案,请跳至步骤4.步骤1 2和3解释推理和机制;
// In Depth Analysis of the setTimeout(Func,0) trick.
//////// setTimeout(Func,0) Step 1 ////////////
// setTimeout and setInterval impose a minimum
// time limit of about 2 to 10 milliseconds.
console.log("start");
var workCounter=0;
var WorkHard = function()
{
if(workCounter>=2000) {console.log("done"); return;}
workCounter++;
setTimeout(WorkHard,0);
};
// this take about 9 seconds
// that works out to be about 4.5ms per iteration
// Now there is a subtle rule here that you can tweak
// This minimum is counted from the time the setTimeout was executed.
// THEREFORE:
console.log("start");
var workCounter=0;
var WorkHard = function()
{
if(workCounter>=2000) {console.log("done"); return;}
setTimeout(WorkHard,0);
workCounter++;
};
// This code is slightly faster because we register the setTimeout
// a line of code earlier. Actually, the speed difference is immesurable
// in this case, but the concept is true. Step 2 shows a measurable example.
///////////////////////////////////////////////
//////// setTimeout(Func,0) Step 2 ////////////
// Here is a measurable example of the concept covered in Step 1.
var StartWork = function()
{
console.log("start");
var startTime = new Date();
var workCounter=0;
var sum=0;
var WorkHard = function()
{
if(workCounter>=2000)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: sum=" + sum + " time=" + ms + "ms");
return;
}
for(var i=0; i<1500000; i++) {sum++;}
workCounter++;
setTimeout(WorkHard,0);
};
WorkHard();
};
// This adds some difficulty to the work instead of just incrementing a number
// This prints "done: sum=3000000000 time=18809ms".
// So it took 18.8 seconds.
var StartWork = function()
{
console.log("start");
var startTime = new Date();
var workCounter=0;
var sum=0;
var WorkHard = function()
{
if(workCounter>=2000)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: sum=" + sum + " time=" + ms + "ms");
return;
}
setTimeout(WorkHard,0);
for(var i=0; i<1500000; i++) {sum++;}
workCounter++;
};
WorkHard();
};
// Now, as we planned, we move the setTimeout to before the difficult part
// This prints: "done: sum=3000000000 time=12680ms"
// So it took 12.6 seconds. With a little math, (18.8-12.6)/2000 = 3.1ms
// We have effectively shaved off 3.1ms of the original 4.5ms of dead time.
// Assuming some of that time may be attributed to function calls and variable
// instantiations, we have eliminated the wait time imposed by setTimeout.
// LESSON LEARNED: If you want to use the setTimeout(Func,0) trick with high
// performance in mind, make sure your function takes more than 4.5ms, and set
// the next timeout at the start of your function, instead of the end.
///////////////////////////////////////////////
//////// setTimeout(Func,0) Step 3 ////////////
// The results of Step 2 are very educational, but it doesn't really tell us how to apply the
// concept to the real world. Step 2 says "make sure your function takes more than 4.5ms".
// No one makes functions that take 4.5ms. Functions either take a few microseconds,
// or several seconds, or several minutes. This magic 4.5ms is unattainable.
// To solve the problem, we introduce the concept of "Burn Time".
// Lets assume that you can break up your difficult function into pieces that take
// a few milliseconds or less to complete. Then the concept of Burn Time says,
// "crunch several of the individual pieces until we reach 4.5ms, then exit"
// Step 1 shows a function that is asyncronous, but takes 9 seconds to run. In reality
// we could have easilly incremented workCounter 2000 times in under a millisecond.
// So, duh, that should not be made asyncronous, its horrible. But what if you don't know
// how many times you need to increment the number, maybe you need to run the loop 20 times,
// maybe you need to run the loop 2 billion times.
console.log("start");
var startTime = new Date();
var workCounter=0;
for(var i=0; i<2000000000; i++) // 2 billion
{
workCounter++;
}
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
// prints: "done: workCounter=2000000000 time=7214ms"
// So it took 7.2 seconds. Can we break this up into smaller pieces? Yes.
// I know, this is a retarded example, bear with me.
console.log("start");
var startTime = new Date();
var workCounter=0;
var each = function()
{
workCounter++;
};
for(var i=0; i<20000000; i++) // 20 million
{
each();
}
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
// The easiest way is to break it up into 2 billion smaller pieces, each of which take
// only several picoseconds to run. Ok, actually, I am reducing the number from 2 billion
// to 20 million (100x less). Just adding a function call increases the complexity of the loop
// 100 fold. Good lesson for some other topic.
// prints: "done: workCounter=20000000 time=7648ms"
// So it took 7.6 seconds, thats a good starting point.
// Now, lets sprinkle in the async part with the burn concept
console.log("start");
var startTime = new Date();
var workCounter=0;
var index=0;
var end = 20000000;
var each = function()
{
workCounter++;
};
var Work = function()
{
var burnTimeout = new Date();
burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
while((new Date()) < burnTimeout)
{
if(index>=end)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
return;
}
each();
index++;
}
setTimeout(Work,0);
};
// prints "done: workCounter=20000000 time=107119ms"
// Sweet Jesus, I increased my 7.6 second function to 107.1 seconds.
// But it does prevent the browser from locking up, So i guess thats a plus.
// Again, the actual objective here is just to increment workCounter, so the overhead of all
// the async garbage is huge in comparison.
// Anyway, Lets start by taking advice from Step 2 and move the setTimeout above the hard part.
console.log("start");
var startTime = new Date();
var workCounter=0;
var index=0;
var end = 20000000;
var each = function()
{
workCounter++;
};
var Work = function()
{
if(index>=end) {return;}
setTimeout(Work,0);
var burnTimeout = new Date();
burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
while((new Date()) < burnTimeout)
{
if(index>=end)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
return;
}
each();
index++;
}
};
// This means we also have to check index right away because the last iteration will have nothing to do
// prints "done: workCounter=20000000 time=52892ms"
// So, it took 52.8 seconds. Improvement, but way slower than the native 7.6 seconds.
// The Burn Time is the number you tweak to get a nice balance between native loop speed
// and browser responsiveness. Lets change it from 4.5ms to 50ms, because we don't really need faster
// than 50ms gui response.
console.log("start");
var startTime = new Date();
var workCounter=0;
var index=0;
var end = 20000000;
var each = function()
{
workCounter++;
};
var Work = function()
{
if(index>=end) {return;}
setTimeout(Work,0);
var burnTimeout = new Date();
burnTimeout.setTime(burnTimeout.getTime() + 50); // burnTimeout set to 50ms in the future
while((new Date()) < burnTimeout)
{
if(index>=end)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
return;
}
each();
index++;
}
};
// prints "done: workCounter=20000000 time=52272ms"
// So it took 52.2 seconds. No real improvement here which proves that the imposed limits of setTimeout
// have been eliminated as long as the burn time is anything over 4.5ms
///////////////////////////////////////////////
//////// setTimeout(Func,0) Step 4 ////////////
// The performance numbers from Step 3 seem pretty grim, but GUI responsiveness is often worth it.
// Here is a short library that embodies these concepts and gives a descent interface.
var WilkesAsyncBurn = function()
{
var Now = function() {return (new Date());};
var CreateFutureDate = function(milliseconds)
{
var t = Now();
t.setTime(t.getTime() + milliseconds);
return t;
};
var For = function(start, end, eachCallback, finalCallback, msBurnTime)
{
var i = start;
var Each = function()
{
if(i==-1) {return;} //always does one last each with nothing to do
setTimeout(Each,0);
var burnTimeout = CreateFutureDate(msBurnTime);
while(Now() < burnTimeout)
{
if(i>=end) {i=-1; finalCallback(); return;}
eachCallback(i);
i++;
}
};
Each();
};
var ForEach = function(array, eachCallback, finalCallback, msBurnTime)
{
var i = 0;
var len = array.length;
var Each = function()
{
if(i==-1) {return;}
setTimeout(Each,0);
var burnTimeout = CreateFutureDate(msBurnTime);
while(Now() < burnTimeout)
{
if(i>=len) {i=-1; finalCallback(array); return;}
eachCallback(i, array[i]);
i++;
}
};
Each();
};
var pub = {};
pub.For = For; //eachCallback(index); finalCallback();
pub.ForEach = ForEach; //eachCallback(index,value); finalCallback(array);
WilkesAsyncBurn = pub;
};
///////////////////////////////////////////////
//////// setTimeout(Func,0) Step 5 ////////////
// Here is an examples of how to use the library from Step 4.
WilkesAsyncBurn(); // Init the library
console.log("start");
var startTime = new Date();
var workCounter=0;
var FuncEach = function()
{
if(workCounter%1000==0)
{
var s = "<div></div>";
var div = jQuery("*[class~=r1]");
div.append(s);
}
workCounter++;
};
var FuncFinal = function()
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
};
WilkesAsyncBurn.For(0,2000000,FuncEach,FuncFinal,50);
// prints: "done: workCounter=20000000 time=149303ms"
// Also appends a few thousand divs to the html page, about 20 at a time.
// The browser is responsive the entire time, mission accomplished
// LESSON LEARNED: If your code pieces are super tiny, like incrementing a number, or walking through
// an array summing the numbers, then just putting it in an "each" function is going to kill you.
// You can still use the concept here, but your "each" function should also have a for loop in it
// where you burn a few hundred items manually.
///////////////////////////////////////////////
答案 5 :(得分:-1)
我发现本教程可以准确解释我的需求。 我希望这也能帮助别人。 http://nodetuts.com/tutorials/19-asynchronous-iteration-patterns.html#video