[javascript] JavaScript에서주기를위한 비동기

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

for ( /* ... */ ) {

  someFunction(param1, praram2, function(result) {

    // Okay, for cycle could continue

  })

}

alert("For cycle ended");

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



답변

스크립트를 차단하면 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


답변

나는 이것을 단순화했다.

함수:

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/


답변

@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 라는 비동기 메서드 대기열이 이미 있습니다 .


답변

또한이 멋진 라이브러리 caolan / async를보십시오 . 귀하의 for루프를 사용하여 쉽게 수행 할 수 있습니다 mapSeries 또는 시리즈 .

예제에 더 자세한 내용이 있으면 샘플 코드를 게시 할 수 있습니다.


답변

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)}}
    ]
  })
}


답변

“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.  
///////////////////////////////////////////////


답변

루프를 계속할지 여부 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를 콜백으로 사용 하여 전체 표현식을 비동기 함수로 캡슐화 할 수 있습니다 .