How do I make this async foreach loop work with promises?

28,673

Solution 1

In synchronous code, continuation is performed when the line ends ;

With promises, continuation is performed via .then. You were using a promise constructor and resolved it immediately, you did not wait for any task at all. I'd map my work into tasks and then either chain them with then or await them serially.

//I'm assuming
zippyarray; // array of Zippy objects

var tasks = zippyarray.map(function(zippy,i){
    return function(){ // return a task on that zippy;
       // basic logic here
       return $.get({
            // ajax request
       }).then(function(data){
            // process data like in your code
            // possibly store later for later use too
            return process(data); // return the processed data;
       });
    }
});

Now we can execute them all sequentially:

 var p = tasks[0](); // start the first one
 for(var i = 1; i < tasks.length; i++) p = p.then(tasks[i]);
 p.then(function(result){
       // all available here
 });

Or better, serially:

$.when.apply(tasks.forEach(function(t){ return t(); })).then(function(results){
     // all done
})

Solution 2

I know this is an old question but things have changed a bit recently.

If you're fine with using external libraries, the Bluebird promise library has a pretty good implementation for this: Promise.each.

E.g.

function helperFunc(zippyarray) {
  return Promise.each(zippyarray, zippy => {
    return someOperationThatReturnAPromise(zippy)
      .then((singleResult) => {
        // do something with the operation result if needed
      })
  }).then((originalArray) => {
    // this happens only after the whole array is processed
    // (result is the original array here)
    return Promise.resolve(originalArray)
  })
}
Share:
28,673
Fabis
Author by

Fabis

Updated on November 12, 2020

Comments

  • Fabis
    Fabis over 3 years

    I've already messed around with Promises in it, but I'm new to them and I just can't figure out how to do it properly. At the moment, there's no point to the Promise, because it doesn't wait till the async $.get completes.

    Basically, each foreach iteration has its own $.get function, and I need to have them all complete and then continue to the part that has the "...gets albumart" console.log.

    $.get(id,function(data) {
        //(there's some code here)
        var getZippyUrls = new Promise(function(resolve) {
                zippyarray.forEach(function(zippy) {
                //(more code)
                $.get(zippy.full, function(data) {
                    //^This is the foreach of $.gets
                   //(code's here)
                });  
               resolve(zippyarray);
            });
        });
    
        //This is my failed Promise ->
        getZippyUrls.then(function(response) {
            console.log("WE'RE OUT " + response.length);
            response.foreach(function(d) {
                console.log("Promise"+d.media);
            });
            console.log('eyyyyyy');
        });
    
        console.log("...gets albumart");
        //Now after the previous stuff is done, move on
    
  • Benjamin Gruenbaum
    Benjamin Gruenbaum about 10 years
    This is manually implementing logic that already exists with promises. It's called an asynchronous semaphore btw.
  • Fuzzyma
    Fuzzyma about 10 years
    Nice to know the name. Its never wrong to know the code behind the function. Especially if you are laging the jquery-lib on your page
  • Benjamin Gruenbaum
    Benjamin Gruenbaum about 10 years
    You don't need the jQuery library, you can use either a modern browser (promises are a part of the new ES6 specification), or a good promise library (like Bluebird). Having to manually do this like your code disregards a lot of things (error handling, etc). It could be written as $.when.apply(requests.map(function(el){ return $.get("something.html",{data:el}); })).then(function(results){ /* results all available here */ });
  • Fuzzyma
    Fuzzyma about 10 years
    You already wrote that in your answer. Note that my answer still isnt wrong. Maybe there are mroe modern ways but this way will work definitevely no matter which browser or lib you are using.
  • Benjamin Gruenbaum
    Benjamin Gruenbaum about 10 years
    Did I claim it was wrong anywhere? All I was saying is that I think it's not the best solution given the circumstance (quite a poor one imo) and I explained why (no error handling, manual wiring, repeating code, etc). I didn't mean to imply otherwise.
  • Fabis
    Fabis about 10 years
    I don't understand why are there 3 returns within the map() function. Could you clear that up for me? What do each of them do?
  • Benjamin Gruenbaum
    Benjamin Gruenbaum about 10 years
    @Fabis the outer return returns the task. We map each zippy to a function. The second return is from the task function and returns the result of $.get (a promise). The third return is for returning a value from a .then handler. In general, when you return a value from a then handler it will resolve the promise with that value, if that value is a promise itself it will resolve the outer promise with its unwrapped value.
  • Jamie Hutber
    Jamie Hutber almost 9 years
    If I used return process() then I'll get the error: Unhandled rejection TypeError: Property 'process' of object #<Object> is not a function Makes me think I shouldn't be using process function when return an array of objects
  • Benjamin Gruenbaum
    Benjamin Gruenbaum almost 9 years
    @JamieHutber that error indicates you didn't write the code you say you wrote. You wrote something.process(...) and that something didn't have a process method.
  • Rohman HM
    Rohman HM over 7 years
    Hi, can you give another example without using bluebird? I just need Promise.each. Using bluebird I got big compiled file. It's not good
  • MJV
    MJV over 7 years
    If you don't want to use libraries (possibly apart from jQuery which is used in the question itself), I think Benjamin's answer could be the way to go. Or if you're using ES6, you could add all the desired function calls in a collection and then use Promise.all with that.