Asynchroniczny dla cyklu w JavaScript


87

Potrzebuję pętli, która czeka na wywołanie asynchroniczne przed kontynuowaniem. Coś jak:

for ( /* ... */ ) {

  someFunction(param1, praram2, function(result) {

    // Okay, for cycle could continue

  })

}

alert("For cycle ended");

Jak mogłem to zrobić? Czy masz jakies pomysły?


128
Wow ( /* ... */ )wygląda jak potwór i teraz się boję :(
Pointy

Odpowiedzi:


182

Nie możesz mieszać synchronicznych i asynchronicznych w JavaScript, jeśli zablokujesz skrypt, zablokujesz przeglądarkę.

Musisz iść tutaj w pełni napędzaną wydarzeniami, na szczęście możemy ukryć brzydkie rzeczy.

EDYCJA: zaktualizowano kod.

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;
}

Zapewni nam to asynchroniczność loop, możesz oczywiście zmodyfikować ją jeszcze bardziej, aby wziąć na przykład funkcję sprawdzającą stan pętli itp.

A teraz do testu:

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')}
);

A wynik:

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
może czegoś mi brakuje, ale nie rozumiem, dlaczego jest to właściwie asynchroniczne. Nie potrzebujesz setTimeout czy czegoś takiego? Wypróbowałem twój kod, wyjąłem console.log i podkręciłem licznik, a to po prostu zawiesiło przeglądarkę.
rocketsarefast

Nie rozumiem, co mam loop.break()robić? Po prostu sposób na wymuszenie, jeśli chcesz?
whitfin

2
jak powiedział rocketsarefast, ta odpowiedź nie jest asynchroniczna, a zatem całkowicie błędna!
kofifus

podobnie jak loop.next, loop.break powinien zostać ustawiony jako no-op, gdy parametr done jest true: `break: function () {if (! done) {done = true; oddzwonić(); }} `
db-inf

4
Przepraszamy, musiałem zagłosować przeciw, ponieważ to nie jest tak naprawdę asynchroniczne.
Rikaelus

44

Uprościłem to:

FUNKCJONOWAĆ:

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

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

STOSOWANIE:

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

PRZYKŁAD: http://jsfiddle.net/NXTv7/8/


+1 Zrobiłem coś podobnego do tego, ale wstawiłem część setTimeout do funkcji biblioteki.
rocketsarefast

Czy nie jest w zasadzie rekurencją?
Pavel

7

Bardziej przejrzystą alternatywą dla tego, co zasugerował @Ivo, byłaby kolejka metod asynchronicznych , przy założeniu, że wystarczy wykonać jedno wywołanie asynchroniczne dla kolekcji.

(Zobacz ten post Dustina Diaza, aby uzyskać bardziej szczegółowe wyjaśnienie)

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);

Wystarczy utworzyć nowe wystąpienie programu Queue, dodać potrzebne wywołania zwrotne, a następnie opróżnić kolejkę z odpowiedzią asynchroniczną.

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

Dodatkową zaletą tego wzorca jest to, że można dodać wiele funkcji do kolejki zamiast tylko jednej.

Jeśli masz obiekt, który zawiera funkcje iteratora, możesz dodać obsługę tej kolejki za kulisami i napisać kod, który wygląda na synchroniczny, ale taki nie jest:

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

po prostu napisz, eachaby umieścić anonimową funkcję w kolejce, zamiast wykonywać ją natychmiast, a następnie opróżnij kolejkę po zakończeniu wywołania asynchronicznego. To bardzo prosty i mocny wzorzec projektowy.

PS Jeśli używasz jQuery, masz już do dyspozycji kolejkę metod asynchronicznych o nazwie jQuery.Deferred .


1
Cóż, jeśli dobrze zrozumiesz pytanie, nie przyniesie to pożądanego zachowania, wydaje się, że chce wykonać pewne wywołania zwrotne, w someFunctionktórych opóźnia resztę pętli, twój wzorzec ustawia listę funkcji, które zostaną wykonane w kolejności i wszystkie otrzymają wyniki jednego innego wywołania funkcji. To dobry wzór, ale nie sądzę, aby pasował do omawianego pytania.
Ivo Wetzel

@Ivo Bez dodatkowych informacji nie będziemy wiedzieć na pewno, ale mówiąc ogólnie, myślę, że to zły projekt, aby kod synchroniczny czekał na operację asynchroniczną przed kontynuowaniem; w każdym przypadku, gdy go wypróbowałem, powodowało to zauważalne opóźnienie interfejsu użytkownika spowodowane jednowątkowym JS. Jeśli operacja trwa zbyt długo, istnieje ryzyko, że Twój skrypt zostanie na siłę zatrzymany przez przeglądarkę.
Adam Lassek

@Ivo też bardzo uważam na kod, na którym się opiera setTimeout. Ryzykujesz niezamierzone zachowanie, jeśli kod jest wykonywany szybciej, niż się spodziewasz.
Adam Lassek

@Adam W jaki sposób mógłbym zaryzykować niezamierzone zachowanie, setTimeoutjeśli wywołanie zwrotne zajmuje tylko połowę czasu, a kod jest ponownie wykonywany szybciej ... więc po co? „Kod” w „pętli” jest nadal w porządku, jeśli robisz coś poza nim przed pełnym wywołaniem zwrotnym, już dzwonisz z powodu kłopotów, ale z drugiej strony jest to pojedynczy wątek, trudno jest scenariusz, w którym setTimeoutcoś zepsuje, bez dalszego błędnego projektowania.
Ivo Wetzel

Poprosił również o taki moduł Node.js w innym pytaniu. Stwierdziłem, że generyczne rozwiązanie dla pętli „asynchronicznej synchronizacji” jest ogólnie złym pomysłem. Wolę wybrać coś, co dokładnie spełnia wymagania tego, co próbuję osiągnąć.
Ivo Wetzel

3

Spójrz także na tę wspaniałą bibliotekę caolan / async . Twoja forpętla może być łatwo wykonana przy użyciu mapSeries lub series .

Mógłbym opublikować przykładowy kod, gdyby Twój przykład zawierał więcej szczegółów.


2

Możemy też skorzystać z pomocy jquery.Deferred. w tym przypadku funkcja asyncLoop wyglądałaby tak:

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

funkcja zwrotna będzie wyglądać następująco:

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();
  }
};

przykładowa funkcja rozwiązująca odroczone:

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

Używałem metody „setTimeout (Func, 0);” trik przez około rok. Oto kilka ostatnich badań, które napisałem, aby wyjaśnić, jak to nieco przyspieszyć. Jeśli chcesz tylko odpowiedzi, przejdź do kroku 4. Krok 1 2 i 3 wyjaśnij rozumowanie i mechanikę;

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

Biorąc pod uwagę asynchroniczną funkcję roboczą, someFunctionktóra wywoła funkcję wynikową z resultargumentem mówiącym, czy pętla powinna być kontynuowana, czy nie:

// 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
  });
})

Aby sprawdzić, czy pętla ma zostać zakończona, funkcja someFunctionrobocza może przekazać funkcję wyniku do innych operacji asynchronicznych. Ponadto całe wyrażenie można zamknąć w funkcji asynchronicznej, przyjmując funkcję donejako wywołanie zwrotne.


1

Jeśli podoba Ci się odpowiedź wilsonpage, ale jesteś bardziej przyzwyczajony do używania składni async.js, oto jej odmiana:

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!');
});

Demo można znaleźć tutaj - http://jsfiddle.net/NXTv7/8/


1

Oto kolejny przykład, który moim zdaniem jest bardziej czytelny niż inne, w którym zawijasz swoją funkcję asynchroniczną wewnątrz funkcji, która przyjmuje donefunkcję, bieżący indeks pętli i wynik (jeśli istnieje) poprzedniego wywołania asynchronicznego:

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

Po done()wywołaniu wyzwala następne wywołanie asynchroniczne, ponownie przekazując wykonaną funkcję, bieżący indeks i poprzedni wynik. Po zakończeniu całej pętli callbackzostanie wywołana podana pętla .

Oto fragment, który możesz uruchomić:

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

Możesz użyć async awaitwprowadzonych w ES7:

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

Działa to tylko wtedy, gdy someFunctionzwraca się Obietnica!

Jeśli someFunctionnie zwraca Obietnicy, możesz sprawić, że sam zwróci Obietnicę w następujący sposób:

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

Następnie zamień tę linię await someFunction(param1, param2);naawait asynSomeFunction(param1, param2);

Proszę zrozumieć obietnice przed napisaniem async awaitkodu!


To powinno dać Unexpected await inside loop.
Reyraa

@Reyraa to nie javascriptproblem. To ostrzeżenie pochodzi z twojej eslintkonfiguracji. Zawsze wyłączam tę regułę, eslintponieważ w większości miejsc naprawdę potrzebuję czekać wewnątrz pętli
Praveena

0

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

EDYTOWAĆ:

link z 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;
}

Ta funkcja umożliwia utworzenie procentowej przerwy w pętli for przy użyciu settings.limit. Właściwość limit jest po prostu liczbą całkowitą, ale gdy zostanie ustawiona jako array.length * 0.1, spowoduje to, że settings.limit_callback będzie wywoływane co 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
 */

przykład:

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

Rozwiązanie oparte na bibliotece obietnic:

/*
    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

Musiałem wywołać niektóre Xczasy funkcji asynchronicznych , każda iteracja musiała nastąpić po wykonaniu poprzedniej, więc napisałem bibliotekę litte, której można używać w następujący sposób:

// 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");
});

Za każdym razem, gdy wywoływana jest funkcja pętli zdefiniowana przez użytkownika, ma ona dwa argumenty, indeks iteracji i wartość zwracaną przez poprzednie wywołanie.

Oto przykład wyniku:

"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"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.