-5

Given this pattern

someArray.reduce(function(p, item) {
  return p.then(function() {
    return someFunction(item);
  });
}, $.Deferred().resolve()).then(function() {
  // all done here
  // access accumulated fulfilled , rejected `Promise` values
}, function err() {

});

what approaches are possible to return accumulated values of fulfilled , rejected Promise objects to .then(fulfilled) as an array following call to .reduce() ?

function someFunction(index) {
  console.log("someFunction called, index = " + index);
  var $deferred = $.Deferred();

  window.setTimeout(function() {
    $deferred.resolve();
  }, 2000);

  return $deferred.promise();
}
   
var someArray = [1,2,3,4,5];

someArray.reduce(function(p, item) {
  return p.then(function() {
    return someFunction(item);
  });
}, $.Deferred().resolve()).then(function(data) {
  // all done here
  console.log(data, arguments) // `undefined` , `[]`
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
guest271314
  • 1
  • 15
  • 104
  • 177
  • Can you clarify "*accumulated values of fulfilled , rejected Promise objects*", please? How should the result look exactly, and how do you want rejections to be treated? – Bergi Nov 13 '15 at 08:24
  • @Bergi A single array or object containing fulfilled `Promise` values at `.then(fulfilled)` , similarly at rejected `.then(rejected)` – guest271314 Nov 13 '15 at 08:40
  • What is "similarly"? Be specific, please. – Bergi Nov 13 '15 at 08:52
  • 1
    maybe you should take a look at [this answer](http://stackoverflow.com/a/33549935/2037556) – Anonymous0day Nov 13 '15 at 08:53
  • @Bergi _"What is "similarly"? Be specific, please."_ Results can be either array of results or object having values as results. Both results - fulfilled , rejected - should be same type of data structure. – guest271314 Nov 13 '15 at 08:58
  • @Anonymous0day _"maybe you should take a look at http://stackoverflow.com/a/33549935/"_ Could `.then()` be chained to `.map()` and return all `cur.name` to `.then(fulfilled)` as an array of fulfilled values from `Promise` objects iterated within `.map()` ? – guest271314 Nov 13 '15 at 09:03

3 Answers3

8

There are multiple possible strategies depending upon the specifics of what you're trying to do: Here's one option:

someArray.reduce(function(p, item) {
  return p.then(function(array) {
    return someFunction(item).then(function(val) {
        array.push(val);
        return array;
    });
  });
}, $.Deferred().resolve([])).then(function(array) {
  // all done here
  // accumulated results in array
}, function(err) {
  // err is the error from the rejected promise that stopped the chain of execution
});

Working demo: http://jsfiddle.net/jfriend00/d4q1aaa0/


FYI, the Bluebird Promise library (which is what I generally use) has .mapSeries() which is built for this pattern:

var someArray = [1,2,3,4];

Promise.mapSeries(someArray, function(item) {
    return someFunction(item);
}).then(function(results) {
    log(results);
});

Working demo: http://jsfiddle.net/jfriend00/7fm3wv7j/

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Returning `undefined` for each item in array at http://jsfiddle.net/uusjs3mt/2/ . Is it possible to continue loop if a `Promise` within loop is rejected ? – guest271314 Nov 13 '15 at 08:46
  • 1
    @guest271314 - `someFunction()` has to resolve with its value to contribute to the array. See http://jsfiddle.net/jfriend00/d4q1aaa0/. – jfriend00 Nov 13 '15 at 09:13
  • @guest271314 - I added another option which uses the Bluebird promise library since is has a `.mapSeries()` function which is built for iterating an array serially and asynchronously and returning an array of results. – jfriend00 Nov 13 '15 at 21:06
  • @guest271314 - I'm not sure why you would use `.concat()` for adding a single value. `.concat()` creates a whole new copy of the array each time you call it. `.push()` just adds a new element into the current array. Both work obviously, but when you don't need to copy the array, it's better not to. – jfriend00 Nov 13 '15 at 21:09
  • Bluebird version return results similar to `Promise.all()` ; if an item in array is rejected , chain does not continue – guest271314 Nov 14 '15 at 08:19
  • Both versions do not continue if item in array is rejected – guest271314 Nov 14 '15 at 22:01
  • 1
    @guest271314 - yes, that is true. That is the default handling of promises (in general). You can change that behavior to ignore errors if you want fairly easily by just handling the error yourself. Your question did not describe what you wanted the behavior to be if any single operation had a failure so my answer is coded to stop processing on the first error and return that error (same behavior as `Promise.all()`). – jfriend00 Nov 14 '15 at 22:02
  • The Question was hastily and poorly composed , and lacked clarity as to requirement followiing comment exchange at prior Question. Withdrawing "accepted" from Anonymous0day's Answer and awarding your Answer "accepted". This is to maintain consistently between actual Question at OP and solution provided; instead awarding "accepted" to solution provided in response to comment exchanges of attempted clarification of expected result of rejected promises that occurred after original Question was posted. – guest271314 Nov 16 '15 at 01:30
2

One solution possible :

var $j = function(val, space) {
  return JSON.stringify(val, null, space || '')
}
var log = function(val) {
  document.body.insertAdjacentHTML('beforeend', '<div><pre>' + val + '</div></pre>')
}

var async = function(cur){
  var pro = new Promise(function(resolve, reject) {

        log('loading : ' + cur.name);
        
        // we simualate the loading
        setTimeout(function() {
          if(cur.name === 'file_3.js'){
            reject(cur.name);
          }
          resolve(cur.name);
        }, 1 * 1000);

      });

      return pro;
}

var files = '12345'.split('').map(function(v) {
  return {
    name: 'file_' + v + '.js', 
  }
});


var listed = files.reduce(function(t,v){

  t.p = t.p.then( function(){
    return async( v )
      .then(function(rep){
      
               t.fulfilled.push(rep);
               log('fulfilled :' + rep); 
               return rep;
      
      } , function(rep){
               
               t.rejected.push(rep);
               log('-----| rejected :' + rep); 
               return rep;
      }
   ).then(function(val){ t.treated.push(val) })
  });
  
  return t;
  
} , {p : Promise.resolve() , treated : [] , fulfilled : [] , rejected : [] } )


listed.p.then( function(){ 
  log( 'listed : ' + $j( listed , '   ' )) 
});
Anonymous0day
  • 3,012
  • 1
  • 14
  • 16
2

Next to the approach demonstrated by @jfriend00, where you resolve each promise with an array where you append the current to all previous results, you can also use an array of promises as is known from the parallel execution pattern with Promise.all and .map.

For this, you have to put all the promises you create within the reduce steps in an array. After that, you can call Promise.all on this array to await all the results. The advantage of this approach is that your code only needs minimal adjustment, so that you can easily switch back and forth between a version that needs the results and one that does not.
To collect the results of each step in an array, we use a variant of reduce that is known as scan and does return an array (like map) instead of the latest result:

Array.prototype.scan = function scanArray(callback, accumulator) {
    "use strict";
    if (this == null) throw new TypeError('Array::scan called on null or undefined');
    if (typeof callback !== 'function') throw new TypeError(callback+' is not a function');

    var arr = Object(this),
        len = arr.length >>> 0,
        res = [];
    for (var k = 0; k < len; k++)
        if (k in arr)
            res[k] = accumulator = callback(accumulator, arr[k], k, arr);
    return res;
};

The pattern now looks like

Promise.all(someArray.scan(function(p, item) {
    return p.then(function() {
       return someFunction(item);
    });
}, Promise.resolve())).then(…)

(For jQuery, substitute Promise.resolve by $.Deferred().resolve() and Promise.all by $.when.apply($, …))

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • You define `.scan()`, but then your code example does not use it. I'm confused. Did you mean to use `.scan()` instead of `.reduce()`? – jfriend00 Nov 13 '15 at 20:59
  • @jfriend00: Thanks, yes, of course. A too minimalistic adjustment :-) – Bergi Nov 14 '15 at 12:42
  • @Bergi Missing `)` to close `Promise.all()` . `.scan()` appear to return `ReferenceError: t is not defined` ? – guest271314 Nov 14 '15 at 15:36
  • @Bergi Iteration does not continue if rejected `Promise` in array http://jsfiddle.net/nmjosw8b/1/ ? See OP at _"what approaches are possible to return accumulated values of fulfilled , rejected Promise objects to .then()"_ – guest271314 Nov 14 '15 at 16:12
  • @guest271314: Yes, failing fast on rejected promises is the default (and works just like the original code in the question or like jfriend's answer). If you want to keep rejections, [you should use `reflect`](http://stackoverflow.com/a/31424853/1048572) - just wrap it around `someFunction(item)`. – Bergi Nov 14 '15 at 16:18
  • @Bergi Yes, was not aware of that post .Used something similar at http://stackoverflow.com/a/33706461/ . Is Question not specific enough at _"what approaches are possible to return accumulated values of fulfilled , rejected Promise objects to `.then()`"_ that requirement is to return all fulfilled ,rejected promise items to `.then(fulfilled)` following iteration ? Not certain why solution posted accumulated "downvote" ? – guest271314 Nov 14 '15 at 16:25
  • @guest271314: Ah, so you think the OP meant that he wants to collect both fulfillment values and rejection values into the same array? I never understood that. – Bergi Nov 14 '15 at 16:28
  • See OP at _"what approaches are possible to return accumulated values of fulfilled , rejected Promise objects to `.then()`"_ . Updated Question. If too far removed from what appeared to initially be restrained to, please convey. – guest271314 Nov 14 '15 at 16:30
  • @Bergi _"Ah, so you think the OP meant that he wants to collect both fulfillment values and rejection values into the same array? I never understood that."_ Note , array items do not necessarily have to be promises. Any array item can be any value, or no value. Same result should be returned whether item at index 0 is promise and item at index 2 is not promise . – guest271314 Nov 14 '15 at 21:45
  • Either a) both fulfillment values, rejection values in same array at `.then(fulfilled)` , or b) array of fulfilled values returned to `.then(fulfilled)` and `rejection values, if any , being returned to `.then(rejected)` at same call . E.g., `someIterationFunction(someFunction(array)).then(fulfilled, rejected)` where if `console.log()` was at both `fulfilled` , `rejected` , both callbacks would be called in succession , with `fulfilled` called first. At http://stackoverflow.com/a/33706461/ used approach of returning all fulfilled , rejected to `.then(fulfilled)` – guest271314 Nov 14 '15 at 21:46
  • @guest271314: I'm not sure whether you really mean `.then(rejected)`, shouldn't that be `.then(null, onrejected)` (or even `.catch(onrejected)`)? And no, if you have `.then(fulfilled, rejected)` it is completely impossible that both callbacks are going to be called, a promise cannot fulfill and reject at the same time. – Bergi Nov 14 '15 at 21:51
  • @guest271314: Regarding the array items, that's not what the OP wants. He doesn't even say anything about what the input array contains. It's just `someArray`, and we are going to map over it with `someFunction`, which might return a promise (or a plain value, or throw). It's irrelevant to us what kind of values the input array contains, be it promises or something else. – Bergi Nov 14 '15 at 21:54
  • @Bergi _"it is completely impossible that both callbacks are going to be called, a promise cannot fulfill and reject at the same time."_ Conceptually, yes, that was portion of Question; if that was possible . Answer at http://stackoverflow.com/a/33706461/ not appear to wait for any promises; immediately return array of objects. Requirement of Question is to return both fulfilled, rejected promises . Not ceasing iteration of next item if current promise if rejected. If both `.then(fulfilled,rejected)` cannot be called in succession, return both fulfilled, rejected to `.then(fulfilled)` – guest271314 Nov 14 '15 at 22:06
  • @guest271314: Well the OP clearly wanted to wait during the loop, that's what his `reduce` solution already did. He only didn't know how to accumulate the results in an array, and that's what both me and jfriend answered. And even if he wanted to get rejection values back, the solution should look like the one by Anonymous0day not like yours. But let's wait for the OP to accept an answer, until then let the vote counts speak for themselves. – Bergi Nov 14 '15 at 22:12
  • @Bergi Yes, agreed. Question was perhaps poorly composed in relation to what expected results of requirement are. "Whether use `.reduce()` or other methods , loop or recursion , top function should behave like `Promise.all()` , without ceasing iteration when rejected promise encountered , bottom function should behave like jQuery `.deferred.always()`" . Would the above sentence better describe a Question ? Or would additional details be needed to avoid confusion at comments ? – guest271314 Nov 14 '15 at 22:32
  • @Bergi _"it is completely impossible that both callbacks are going to be called, a promise cannot fulfill and reject at the same time."_ Appear to be possible using `.catch()` , see http://stackoverflow.com/a/31524969/ , http://jsfiddle.net/eh6q1xkn/ – guest271314 Nov 14 '15 at 23:42
  • @guest271314: That's `.catch(…).then(…)` or `.then(…).catch(…)` (with two promises), but not `.then(…, …)` – Bergi Nov 14 '15 at 23:59