อะซิงโครนัสสำหรับวงจรใน JavaScript


87

ฉันต้องการลูปที่รอการเรียก async ก่อนดำเนินการต่อ สิ่งที่ต้องการ:

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 ออกและเพิ่มจำนวนขึ้นมากและมันก็หยุดเบราว์เซอร์
จรวดอาหารเช้า

ฉันสับสนว่าloop.break()กำลังทำอะไรอยู่? แค่วิธีบังคับถ้าคุณต้องการ?
whitfin

2
เช่นเดียวกับที่ rocketsarefast กล่าวไว้ข้างต้นคำตอบนี้ไม่เหมือนกันดังนั้นจึงผิดอย่างสิ้นเชิง!
kofifus

เช่น loop.next ควรทำให้ loop.break เป็น no-op เมื่อเสร็จแล้ว: `break: function () {if (! done) {done = true; โทรกลับ(); }} `
db-inf

4
ขออภัยต้องลงคะแนนเนื่องจากว่านี่ไม่ใช่ async จริงๆ
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 ในฟังก์ชันไลบรารี
จรวดอาหารเช้า

โดยพื้นฐานแล้วเป็นการเรียกซ้ำไม่ใช่หรือ?
Pavel

7

ทางเลือกที่สะอาดกว่าสำหรับสิ่งที่ @Ivo แนะนำคือAsynchronous Method Queueโดยสมมติว่าคุณต้องทำการเรียก async เพียงครั้งเดียวสำหรับคอลเลกชัน

(ดูโพสต์นี้โดย 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เพิ่มการเรียกกลับที่คุณต้องการจากนั้นล้างคิวด้วยการตอบสนองแบบ async

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เพื่อใส่ฟังก์ชันที่ไม่ระบุชื่อลงในคิวแทนที่จะดำเนินการทันทีจากนั้นจึงล้างคิวเมื่อการเรียก async ของคุณเสร็จสมบูรณ์ นี่เป็นรูปแบบการออกแบบที่เรียบง่ายและทรงพลัง

PS หากคุณกำลังใช้ jQuery คุณมีวิธีการคิว async ที่จำหน่ายของคุณเรียกว่าjQuery.Deferred


1
ถ้าเข้าใจคำถามอย่างถูกต้องสิ่งนี้จะไม่ทำให้เกิดพฤติกรรมที่ต้องการดูเหมือนว่าเธอต้องการโทรกลับบางอย่างเพื่อsomeFunctionหน่วงเวลาส่วนที่เหลือของลูปรูปแบบของคุณจะสร้างรายการฟังก์ชันที่จะดำเนินการตามลำดับและทั้งหมดจะได้รับ ผลของการหนึ่งในการเรียกใช้ฟังก์ชันอื่น ๆ เป็นรูปแบบที่ดี แต่ฉันคิดว่ามันไม่ตรงกับคำถามที่เป็นปัญหา
Ivo Wetzel

@Ivo หากไม่มีข้อมูลเพิ่มเติมเราจะไม่ทราบแน่ชัด แต่การพูดโดยทั่วไปฉันคิดว่าเป็นการออกแบบที่ไม่ดีที่จะทำให้รหัสซิงโครนัสรอการดำเนินการแบบ async ก่อนดำเนินการต่อ ในทุกกรณีที่ฉันได้ลองใช้มันส่งผลให้ UI ล่าช้าอย่างเห็นได้ชัดเนื่องจาก JS เป็นเธรดเดียว หากการดำเนินการใช้เวลานานเกินไปคุณอาจเสี่ยงต่อการที่สคริปต์ของคุณถูกเบราว์เซอร์บังคับให้หยุดทำงาน
Adam Lassek

@Ivo setTimeoutยังฉันมากระวังของรหัสที่อาศัย คุณเสี่ยงต่อพฤติกรรมที่ไม่ได้ตั้งใจหากโค้ดถูกดำเนินการเร็วกว่าที่คุณคาดไว้
Adam Lassek

@Adam ฉันจะเสี่ยงกับพฤติกรรมที่ไม่ได้ตั้งใจด้วยวิธีsetTimeoutใดหากการโทรกลับใช้เวลาเพียงครึ่งเดียวโค้ดจะดำเนินการเร็วขึ้นอีกครั้ง ... แล้วประเด็นคืออะไร? "รหัส" ใน "ลูป" ยังคงเป็นไปตามลำดับหากคุณทำบางอย่างนอกเหนือจากนั้นก่อนที่จะโทรกลับเสร็จสมบูรณ์คุณก็กำลังโทรหาปัญหา แต่อีกครั้งก็เป็นเธรดเดียวฉันมีช่วงเวลาที่ยากลำบากในการหา สถานการณ์ที่setTimeoutจะทำลายบางสิ่งบางอย่างโดยไม่ต้องออกแบบผิดพลาดอีก
Ivo Wetzel

นอกจากนี้เขายังขอโมดูล Node.js เช่นนี้ในอีกคำถามหนึ่งฉันกล่าวว่าโดยทั่วไปแล้วเป็นความคิดที่ดีที่จะมีวิธีแก้ปัญหาทั่วไปสำหรับลูป "async-sync" ฉันอยากจะไปกับสิ่งที่ตรงกับความต้องการที่แท้จริงของสิ่งที่ฉันพยายามจะบรรลุ
Ivo Wetzel

3

ดูที่ห้องสมุดcaolan / async ที่สวยงามนี้ด้วย คุณforห่วงสามารถทำได้โดยใช้mapSeriesหรือชุด

ฉันสามารถโพสต์โค้ดตัวอย่างได้หากตัวอย่างของคุณมีรายละเอียดเพิ่มเติม


2

เรายังสามารถใช้ความช่วยเหลือของ jquery ได้อีกด้วย ในกรณีนี้ฟังก์ชัน 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);" เคล็ดลับประมาณปี นี่คืองานวิจัยล่าสุดที่ฉันเขียนขึ้นเพื่ออธิบายวิธีเร่งความเร็วเล็กน้อย หากคุณต้องการคำตอบเพียงแค่ข้ามไปขั้นตอนที่ 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

นี่คืออีกตัวอย่างหนึ่งที่ฉันคิดว่าอ่านได้ง่ายกว่าอันอื่นโดยที่คุณรวมฟังก์ชัน async ไว้ในฟังก์ชันที่รับdoneฟังก์ชันดัชนีลูปปัจจุบันและผลลัพธ์ (ถ้ามี) ของการเรียก async ก่อนหน้านี้:

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

เมื่อdone()ถูกเรียกใช้มันจะทริกเกอร์การเรียก async ถัดไปอีกครั้งส่งผ่านในฟังก์ชันเสร็จสิ้นดัชนีปัจจุบันและผลลัพธ์ก่อนหน้า เมื่อการวนซ้ำทั้งหมดเสร็จสิ้นระบบ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 awaitแนะนำใน ES7:

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เพราะในสถานที่ส่วนใหญ่ที่ฉันต้องการอย่างแท้จริงรออยู่ในวง
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;
}

ฟังก์ชั่นนี้ช่วยให้คุณสร้างตัวแบ่งเปอร์เซ็นต์ใน for loop โดยใช้ settings.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.