2

My question is very similar to this, but I can't get it to work :(

PROBLEM:

  1. I have a Javascript program that needs to run in IE11 and Chrome.

  2. It has a list of functions that I need to execute in order.

  3. Each function returns a Promise. Promise.all(promisesArray) works to the extent that I can "wait" until all the functions finish before proceeding. But it DOESN'T guarantee that the functions each run in order. This is essential.

  4. I've tried using Array.prototype.reduce(), but I haven't been able to figure out how to use it correctly.

  5. Because I need to run in IE11, I can't use ES6 features like "arrow functions".

Here is my code:

<script>
var fn = function(arg) {
  return new Promise (function (resolve, reject) {
    console.log("fn(" + arg + ")...");
    resolve("OK");
  });
}

var p = fn("A").then(function(r) {
  console.log("promise resolved: " + r + ".");
})

var chain = [];
chain.push(fn("3"));
chain.push(fn("1"));
chain.push(fn("2"));
console.log("Built chain:", chain);
Promise.all(chain);

chain.length = 0;
chain[2] = fn("30");
chain[1] = fn("20");
chain[0] = fn("10");
chain.reduce(function(promise, item) {
  return promise.then(function() {
    console.log("then:", item);
  }), Promise.resolve();
});
console.log("Done.");
</script>;

I need the functions to execute in order array[0], array[1], array[2].

pushkin
  • 9,575
  • 15
  • 51
  • 95
paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • If the functions should run in order, or rather the promises should resolve in order, don't use `Promise.all`. – Felix Kling May 09 '18 at 23:07
  • IE11 does not have support for Promises. Are you using a Promise library? Why not use a transpiler so you can use both Promises and for...of loop? – Nathan Power May 09 '18 at 23:07
  • Just on point `5. "Because I need to run in IE11, I can't use ES6 features like "arrow functions"` - arrow functions are irrelevant, they're just a new syntax, that can be easily transpiled using babljs for example – Jaromanda X May 09 '18 at 23:44
  • @NathanPower - a transpiler wont give you Promises - I know that's not what you meant, but it kinda reads that way – Jaromanda X May 09 '18 at 23:54

1 Answers1

5

You're really close with your reducer!

The initial value for the reducer is Promise.resolve(), so when the function is called the first time:

chain.reduce(function(promise, item) {
//                    ^^ promise is a resolved promise,
//                             ^^ item is chain[0]
  return promise.then(function() {
    console.log("then:", item);
    //           ^^ when the promise resolves (immediately) this is called
    //              BUT no value is returned.
  }), Promise.resolve();

});

Compare this with manually chaining the promises. You'd return the next promise to wait for:

Promise.resolve()
  .then(item => { console.log("then: ", item); return fn("10"); })
  .then(item => { console.log("then: ", item); return fn("20"); })
  .then(item => { console.log("then: ", item); return fn("30"); })

See how the reducer is so close? We'll just want to return a promise:

var chain = [fn("10"), fn("20"), fn("30")];

chain.reduce(function(promise, item) {
  return promise.then(function() {
    console.log("then:", item);
    return item;
  }), Promise.resolve();
});

Edit:
If the fn call starts the work, each of these assignments will start the calls out of order:

chain.length = 0;
chain[2] = fn("30");
chain[1] = fn("20");
chain[0] = fn("10");

To run each fn in the order you want, you'll have to defer the call to fn until the previous call resolves. We did that in our manual example above. Depending on how complex your data is, you could reduce over the array of arguments for fn or wrap each call in a function that won't run:

[10,20,30]
  .reduce(function(promise, arg) {
    return promise.then(function() {
      return fn(arg);
    }), Promise.resolve();
  });

[function() { return fn(10); }, function() { return fn(20) }, function() { return fn(30) }]
  .reduce(function(promise, callFn) {
    return promise.then(function() {
      return fn(callFn);
    }), Promise.resolve();
  });
Sean
  • 1,279
  • 9
  • 17
  • Beautiful: that was it! Thank you much! EXTRA CREDIT BONUS QUESTION: You'll notice above, I explicitly set `array[2]=fn(3)`,`array[1]=fn(2)`, `array[0]=fn(1)`. I would have expected it to execute "1, 2, 3" (index order). Instead, it executes "3, 2, 1" (the order entered into the array, vs. the order *OF* the elements in the array. Q: Any guesses "why"? Q: Any solutions? Besides (re)create the array in exactly the desired order? – paulsm4 May 10 '18 at 03:52
  • Since calling `fn(arg)` is starting the work right away they're actually going to be called in that order, 3, 2, 1. If you need to guarantee the order, you shouldn't start the work. That means calling `fn` much later. If you started with your input values: `var values = [1,2,3];`, how could you change the reducer to return the Promise? – Sean May 10 '18 at 04:03
  • var values = [1,2,3]; values.reduce(function(promise, arg) { return promise.then(function() { return fn(arg); }), Promise.resolve(); }) – Sean May 10 '18 at 04:11
  • I'm not clear. `fn(arg)` is a unit of work. Your solution helped me a) store an arbitrary list of "work items" in an array, then b) execute each item serially. That's great - thank you! But what do you mean by "call fn much later?" What exactly does that mean? How could I do that? – paulsm4 May 10 '18 at 04:20
  • Well, we didn’t quite make it execute the list of work items serially. We’re chaining the results of them, but the act of assigning array[2]=fn(3) calls fn and starts the work. That’s why the order is backwards. To actually be lazy with when the functions are executed you’ll want to only call fn in the reducer callback after the previous call finished. – Sean May 10 '18 at 04:52
  • Check it out after "**Edit**:" @paulsm4 – Sean May 10 '18 at 11:53