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].

4
  • If the functions should run in order, or rather the promises should resolve in order, don't use Promise.all. Commented May 9, 2018 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? Commented May 9, 2018 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 Commented May 9, 2018 at 23:44
  • @NathanPower - a transpiler wont give you Promises - I know that's not what you meant, but it kinda reads that way Commented May 9, 2018 at 23:54

1 Answer 1

4

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();
  });
Sign up to request clarification or add additional context in comments.

6 Comments

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?
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?
var values = [1,2,3]; values.reduce(function(promise, arg) { return promise.then(function() { return fn(arg); }), Promise.resolve(); })
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?
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.