JavaScript에서주기를위한 비동기


87

계속하기 전에 비동기 호출을 기다리는 루프가 필요합니다. 다음과 같은 것 :

for ( /* ... */ ) {

  someFunction(param1, praram2, function(result) {

    // Okay, for cycle could continue

  })

}

alert("For cycle ended");

어떻게 할 수 있습니까? 아이디어가 있습니까?


128
와우 ( /* ... */ )는 괴물처럼 보이고 나는 지금 무서워 :(
Pointy

답변:


182

스크립트를 차단하면 JavaScript에서 동기와 비동기를 혼합 할 수 없으며 브라우저를 차단합니다.

여기서 전체 이벤트 중심의 길을 가야합니다. 운 좋게도 추악한 것들을 숨길 수 있습니다.

편집 : 코드를 업데이트했습니다.

function asyncLoop(iterations, func, callback) {
    var index = 0;
    var done = false;
    var loop = {
        next: function() {
            if (done) {
                return;
            }

            if (index < iterations) {
                index++;
                func(loop);

            } else {
                done = true;
                callback();
            }
        },

        iteration: function() {
            return index - 1;
        },

        break: function() {
            done = true;
            callback();
        }
    };
    loop.next();
    return loop;
}

이것은 우리에게 비동기를 제공 loop할 것입니다. 물론 루프 조건 등을 확인하는 함수를 사용하기 위해 훨씬 더 수정할 수 있습니다.

이제 테스트를 시작합니다.

function someFunction(a, b, callback) {
    console.log('Hey doing some stuff!');
    callback();
}

asyncLoop(10, function(loop) {
    someFunction(1, 2, function(result) {

        // log the iteration
        console.log(loop.iteration());

        // Okay, for cycle could continue
        loop.next();
    })},
    function(){console.log('cycle ended')}
);

그리고 출력 :

Hey doing some stuff!
0
Hey doing some stuff!
1
Hey doing some stuff!
2
Hey doing some stuff!
3
Hey doing some stuff!
4
Hey doing some stuff!
5
Hey doing some stuff!
6
Hey doing some stuff!
7
Hey doing some stuff!
8
Hey doing some stuff!
9
cycle ended

28
아마도 내가 뭔가를 놓치고 있지만 이것이 실제로 어떻게 비동기 적인지 이해하지 못합니다. setTimeout 같은 것이 필요하지 않습니까? 나는 당신의 코드를 시도하고, console.log를 꺼내고, 카운트를 많이 올렸고 그것은 단지 브라우저를 멈췄습니다.
rocketsarefast 2013 년

무엇을해야하는지 혼란 스럽 loop.break()습니까? 당신이 원한다면 강제로 나가는 방법일까요?
whitfin 2014 년

2
rocketsarefast가 위에서 말했듯이이 대답은 비동기 적이 지 않으므로 완전히 잘못되었습니다!
kofifus

loop.next처럼 loop.break는 done이 참일 때 no-op으로 만들어야합니다 :`break : function () {if (! done) {done = true; 콜백 (); }}`
db-inf

4
죄송합니다. 실제로 비동기가 아니기 때문에 반대 투표를해야했습니다.
Rikaelus

44

나는 이것을 단순화했다.

함수:

var asyncLoop = function(o){
    var i=-1;

    var loop = function(){
        i++;
        if(i==o.length){o.callback(); return;}
        o.functionToLoop(loop, i);
    } 
    loop();//init
}

용법:

asyncLoop({
    length : 5,
    functionToLoop : function(loop, i){
        setTimeout(function(){
            document.write('Iteration ' + i + ' <br>');
            loop();
        },1000);
    },
    callback : function(){
        document.write('All done!');
    }    
});

예 : http://jsfiddle.net/NXTv7/8/


+1 이와 비슷한 작업을 수행했지만 라이브러리 함수에 setTimeout 부분을 넣었습니다.
rocketsarefast 2013 년

기본적으로 재귀 아닌가요?
Pavel

7

@Ivo가 제안한 것의 더 깨끗한 대안 은 컬렉션에 대해 하나의 비동기 호출 만 수행하면된다는 가정하에 Asynchronous Method Queue 입니다.

( 더 자세한 설명은 Dustin Diaz 의이 게시물 참조 )

function Queue() {
  this._methods = [];
  this._response = null;
  this._flushed = false;
}

(function(Q){

  Q.add = function (fn) {
    if (this._flushed) fn(this._response);
    else this._methods.push(fn);
  }

  Q.flush = function (response) {
    if (this._flushed) return;
    this._response = response;
    while (this._methods[0]) {
      this._methods.shift()(response);
    }
    this._flushed = true;
  }

})(Queue.prototype);

의 새 인스턴스를 만들고 Queue필요한 콜백을 추가 한 다음 비동기 응답으로 대기열을 비우면됩니다.

var queue = new Queue();

queue.add(function(results){
  for (var result in results) {
    // normal loop operation here
  }
});

someFunction(param1, param2, function(results) {
  queue.flush(results);
}

이 패턴의 또 다른 이점은 하나가 아닌 여러 함수를 대기열에 추가 할 수 있다는 것입니다.

반복자 함수를 포함하는 객체가있는 경우이 큐에 대한 지원을 백그라운드에서 추가하고 동 기적으로 보이지만 그렇지 않은 코드를 작성할 수 있습니다.

MyClass.each(function(result){ ... })

단순히 쓰기 each즉시 실행하는 대신에 큐에 익명 함수를 넣어하고 비동기 호출이 완료되면 다음 큐를 세척하십시오. 이것은 매우 간단하고 강력한 디자인 패턴입니다.

추신 : jQuery를 사용하는 경우 jQuery.Deferred 라는 비동기 메서드 대기열이 이미 있습니다 .


1
질문을 올바르게 이해하면 원하는 동작을 얻지 못할 것입니다 someFunction. 나머지 루프를 지연시키는 콜백을 원하는 것으로 보입니다. 패턴은 순서대로 실행되고 모두 수신 할 함수 목록을 설정합니다. 의 결과를 하나의 다른 함수 호출. 좋은 패턴이지만 문제의 질문과 일치하지 않는다고 생각합니다.
Ivo Wetzel

@Ivo 더 많은 정보가 없으면 확실히 알 수는 없지만 일반적으로 말하면 동기 코드가 계속하기 전에 비동기 작업을 기다리도록 만드는 것은 나쁜 디자인이라고 생각합니다. 내가 시도한 모든 경우에 JS가 단일 스레드이기 때문에 눈에 띄는 UI 지연이 발생했습니다. 작업이 너무 오래 걸리면 브라우저에 의해 스크립트가 강제로 중지 될 위험이 있습니다.
Adam Lassek

@Ivo는 또한에 의존하는 코드를 매우 경계합니다 setTimeout. 코드가 예상보다 빠르게 실행되면 의도하지 않은 동작이 발생할 위험이 있습니다.
Adam Lassek

@Adam 어떤 식으로 setTimeout콜백이 절반의 시간 만 걸리면 코드가 다시 빠르게 실행되는 경우 의도하지 않은 동작이 발생할 위험이 있습니다 . 그래서 요점이 무엇입니까? "루프"의 "코드"는 여전히 순서가 있습니다. 완전한 콜백 전에 문제가 발생하기 전에 외부에서 일부 작업을 수행하면 이미 문제가 발생하지만 다시 단일 스레드이므로 문제가 발생합니다. setTimeout더 이상 잘못 설계하지 않고 무언가를 깨뜨리는 시나리오 .
Ivo Wetzel

또한 그는 다른 질문에서 이와 같은 Node.js 모듈을 요청했습니다. 저는 일반적으로 그러한 "비동기-동기"루프에 대한 일반적인 솔루션을 갖는 것이 좋지 않다고 말했습니다. 차라리 내가 이루고자하는 모든 것의 정확한 요구 사항과 일치하는 것을 선택하고 싶습니다.
Ivo Wetzel


2

jquery.Deferred의 도움말을 사용할 수도 있습니다. 이 경우 asyncLoop 함수는 다음과 같습니다.

asyncLoop = function(array, callback) {
  var nextElement, thisIteration;
  if (array.length > 0) nextElement = array.pop();
  thisIteration = callback(nextElement);
  $.when(thisIteration).done(function(response) {
    // here we can check value of response in order to break or whatever
    if (array.length > 0) asyncLoop(array, collection, callback);
  });
};

콜백 함수는 다음과 같습니다.

addEntry = function(newEntry) {
  var deferred, duplicateEntry;
  // on the next line we can perform some check, which may cause async response.
  duplicateEntry = someCheckHere();
  if (duplicateEntry === true) {
    deferred = $.Deferred();
    // here we launch some other function (e.g. $.ajax or popup window) 
    // which based on result must call deferred.resolve([opt args - response])
    // when deferred.resolve is called "asyncLoop" will start new iteration
    // example function:
    exampleFunction(duplicateEntry, deferred);
    return deferred;
  } else {
    return someActionIfNotDuplicate();
  }
};

지연을 해결하는 예제 함수 :

function exampleFunction(entry, deffered){
  openModal({
    title: "what should we do with duplicate"
    options: [
       {name:"Replace", action: function(){replace(entry);deffered.resolve(replace:true)}},
       {name: "Keep Existing", action: function(){deffered.resolve(replace:false)}}
    ]
  })
}

2

"setTimeout (Func, 0);"을 사용하고 있습니다. 약 1 년 동안 속임수. 다음은 속도를 높이는 방법을 설명하기 위해 작성한 최근 연구입니다. 답을 원하면 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.  
///////////////////////////////////////////////

2

루프를 계속할지 여부 someFunction를 나타내는 result인수 와 함께 결과 함수를 다시 호출 하는 비동기 작업자 함수 가 제공됩니다 .

// having:
// function someFunction(param1, praram2, resultfunc))
// function done() { alert("For cycle ended"); }

(function(f){ f(f) })(function(f){
  someFunction("param1", "praram2", function(result){
    if (result)
      f(f); // loop continues
    else
      done(); // loop ends
  });
})

루프 종료 여부를 확인하기 위해 작업자 함수 someFunction는 결과 함수를 다른 비동기 작업에 전달할 수 있습니다. 또한 함수 done를 콜백으로 사용 하여 전체 표현식을 비동기 함수로 캡슐화 할 수 있습니다 .


1

wilsonpage의 답변을 좋아하지만 async.js의 구문을 사용하는 데 더 익숙하다면 다음과 같은 변형이 있습니다.

function asyncEach(iterableList, callback, done) {
  var i = -1,
      length = iterableList.length;

  function loop() {
      i++;
      if (i === length) {
        done(); 
        return;
      }
      callback(iterableList[i], loop);
  } 
  loop();
}


asyncEach(['A', 'B', 'C'], function(item, callback) {
    setTimeout(function(){
    document.write('Iteration ' + item + ' <br>');
    callback();
  }, 1000);
}, function() {
  document.write('All done!');
});

데모는 여기에서 찾을 수 있습니다 -http : //jsfiddle.net/NXTv7/8/


1

다음은 다른 것보다 더 읽기 쉽다고 생각하는 또 다른 예입니다. 여기서는 done함수, 현재 루프 인덱스 및 이전 비동기 호출의 결과 (있는 경우)를받는 함수 안에 비동기 함수를 래핑합니다 .

function (done, i, prevResult) {
   // perform async stuff
   // call "done(result)" in async callback 
   // or after promise resolves
}

일단 done()호출 되면 다음 비동기 호출을 트리거하고 done 함수, 현재 인덱스 및 이전 결과를 다시 전달합니다. 전체 루프가 완료되면 제공된 루프 callback가 호출됩니다.

실행할 수있는 스 니펫은 다음과 같습니다.

asyncLoop({
  limit: 25,
  asyncLoopFunction: function(done, i, prevResult) {
    setTimeout(function() {
      console.log("Starting Iteration: ", i);
      console.log("Previous Result: ", prevResult);
      var result = i * 100;
      done(result);
    }, 1000);
  },
  initialArgs: 'Hello',
  callback: function(result) {
    console.log('All Done. Final result: ', result);
  }
});

function asyncLoop(obj) {
  var limit = obj.limit,
    asyncLoopFunction = obj.asyncLoopFunction,
    initialArgs = obj.initialArgs || {},
    callback = obj.callback,
    i = 0;

  function done(result) {
    i++;
    if (i < limit) {
      triggerAsync(result);
    } else {
      callback(result);
    }
  }

  function triggerAsync(prevResult) {
    asyncLoopFunction(done, i, prevResult);
  }

  triggerAsync(initialArgs); // init
}


1

async awaitES7에 도입 된 것을 사용할 수 있습니다 .

for ( /* ... */ ) {
    let result = await someFunction(param1, param2);
}
alert("For cycle ended");

이것은 someFunction약속을 반환하는 경우에만 작동합니다 !

경우 someFunction약속을 반환하지 않습니다, 당신은 이런 식으로 자신이 약속을 반환 할 수 있습니다 :

function asyncSomeFunction(param1,praram2) {
  return new Promise((resolve, reject) => {
    someFunction(praram1,praram2,(result)=>{
      resolve(result);
    })
  })
}

그런 다음이 줄 await someFunction(param1, param2);await asynSomeFunction(param1, param2);

async await코드 를 작성하기 전에 약속을 이해하십시오 !


이것은 Unexpected await inside loop.
Reyraa

@Reyraa는 javascript문제 가 아닙니다 . 해당 경고는 구성에서 나타납니다 eslint. 나는 항상 비활성화에서 규정하는 eslint대부분의 장소에서 내가 진정으로 루프 내부에 await를 필요로하기 때문에
Praveena

0

http://cuzztuts.blogspot.ro/2011/12/js-async-for-very-cool.html

편집하다:

github에서 링크 : https://github.com/cuzzea/lib_repo/blob/master/cuzzea/js/functions/core/async_for.js

function async_for_each(object,settings){
var l=object.length;
    settings.limit = settings.limit || Math.round(l/100);
    settings.start = settings.start || 0;
    settings.timeout = settings.timeout || 1;
    for(var i=settings.start;i<l;i++){
        if(i-settings.start>=settings.limit){
            setTimeout(function(){
                settings.start = i;
                async_for_each(object,settings)
            },settings.timeout);
            settings.limit_callback ? settings.limit_callback(i,l) : null;
            return false;
        }else{
            settings.cbk ? settings.cbk(i,object[i]) : null;
        }
    }
    settings.end_cbk?settings.end_cbk():null;
    return true;
}

이 함수를 사용하면 settings.limit를 사용하여 for 루프에서 백분율 중단을 만들 수 있습니다. limit 속성은 정수이지만 array.length * 0.1로 설정하면 settings.limit_callback이 10 %마다 호출됩니다.

/*
 * params:
 *  object:         the array to parse
 *  settings_object:
 *      cbk:            function to call whenwhen object is found in array
 *                          params: i,object[i]
 *      limit_calback:  function to call when limit is reached
 *                          params: i, object_length
 *      end_cbk:        function to call when loop is finished
 *                          params: none
 *      limit:          number of iteration before breacking the for loop
 *                          default: object.length/100
 *      timeout:        time until start of the for loop(ms)
 *                          default: 1
 *      start:          the index from where to start the for loop
 *                          default: 0
 */

예 :

var a = [];
a.length = 1000;
async_for_each(a,{
    limit_callback:function(i,l){console.log("loading %s/%s - %s%",i,l,Math.round(i*100/l))}
});

0

약속 라이브러리 기반 솔루션 :

/*
    Since this is an open question for JS I have used Kris Kowal's Q promises for the same
*/

var Q = require('q');
/*
    Your LOOP body
    @success is a parameter(s) you might pass
*/
var loopBody = function(success) {
    var d = Q.defer(); /* OR use your favorite promise library like $q in angular */
    /*
        'setTimeout' will ideally be your node-like callback with this signature ... (err, data) {}
        as shown, on success you should resolve 
        on failure you should reject (as always ...) 
    */
    setTimeout(function(err, data) {
        if (!err) {
            d.resolve('success');
        } else {
            d.reject('failure');
        }
    }, 100); //100 ms used for illustration only 
    return d.promise;
};

/*
    function to call your loop body 
*/
function loop(itr, fn) {
    var def = Q.defer();
    if (itr <= 0) {
        def.reject({ status: "un-successful " });
    } else {
        var next = loop.bind(undefined, itr - 1, fn); // 'next' is all there is to this 
        var callback = fn.bind(undefined /*, a, b, c.... */ ); // in case you want to pass some parameters into your loop body
        def.promise = callback().then(def.resolve, next);
    }
    return def.promise;
}
/*
    USAGE: loop(iterations, function(){})
    the second argument has to be thenable (in other words return a promise)
    NOTE: this loop will stop when loop body resolves to a success
    Example: Try to upload file 3 times. HURRAY (if successful) or log failed 
*/

loop(4, loopBody).then(function() {
    //success handler
    console.log('HURRAY')
}, function() {
    //failed 
    console.log('failed');
});

0

비동기 함수 X시간 을 호출해야했고 , 각 반복은 이전 반복이 완료된 후에 발생 했어야하므로 다음 과 같이 사용할 수 있는 litte 라이브러리 를 작성했습니다 .

// https://codepen.io/anon/pen/MOvxaX?editors=0012
var loop = AsyncLoop(function(iteration, value){
  console.log("Loop called with iteration and value set to: ", iteration, value);

  var random = Math.random()*500;

  if(random < 200)
    return false;

  return new Promise(function(resolve){
    setTimeout(resolve.bind(null, random), random);
  });
})
.finished(function(){
  console.log("Loop has ended");
});

사용자 정의 루프 함수가 호출 될 때마다 두 개의 인수, 반복 인덱스 및 이전 호출 반환 값이 있습니다.

다음은 출력의 예입니다.

"Loop called with iteration and value set to: " 0 null
"Loop called with iteration and value set to: " 1 496.4137048207333
"Loop called with iteration and value set to: " 2 259.6020382449663
"Loop called with iteration and value set to: " 3 485.5400568702862
"Loop has ended"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.