0

I'm having a (seemingly fundamental) problem understanding promises. First the code:

'use strict';

var Q = require("q");

var mockPromise = function (statement) {
    var deferred = Q.defer();

    console.log("I'm running before I'm queued ...");

    setTimeout(function () {
        deferred.resolve(statement);
    }, 5000);

    return deferred.promise;
};

var promises = [
    mockPromise("1st statement"),
    mockPromise("2nd statement"),
    mockPromise("3rd statement")
];

Q.all(promises)
.then(function (results) {
    console.log(results);
});

Each promise function gets invoked upon adding it to the promise array, as opposed to when Q.all is called as I thought.

What am I not getting here?

How do I queue an array of promises without immediately invoking said promises?

Lindauson
  • 2,963
  • 1
  • 30
  • 33
  • Why do you want to queue the promises and then execute them? It's way faster if they are executed as soon as they are fired off – Derek Pollard Oct 11 '16 at 21:51
  • `Q.all()` is just a promise that finishes only once all the promises within its array is done – Derek Pollard Oct 11 '16 at 21:52
  • 1
    see these http://stackoverflow.com/questions/35543964/whats-the-best-approach-to-use-q-promise-in-a-loop-waiting-for-chain-to-complet/35544174#35544174 http://stackoverflow.com/questions/38694958/javascript-async-await-for-promises-inside-array-map/38695705#38695705 – Tamas Hegedus Oct 11 '16 at 22:04
  • 1
    A promise is a result value, nothing that can be executed or invoked. What you want is a function that returns a promise - those you can store and call whenever you want. – Bergi Oct 11 '16 at 22:14

4 Answers4

2

It seems the confusion is that you understand the promise API to be designed for lazy evaluation, which is not the case.

Promises are a way of handling long running requests, they were designed to start IMMEDIATELY to minimize waiting time, and to utilize chaining and joining to clarify how the results of these long running requests should be processed.

You might try to utilize the api Q-Lazy which allows you to delay invocation of promises until they have been subscribed to.

42shadow42
  • 405
  • 2
  • 9
  • It seems the Q API is more concerned with synchronizing promise completion vs. synchronizing invocation. Do you think that is a correct assessment? – Lindauson Oct 11 '16 at 22:20
  • @Lindauson Promises have nothing to do with invocations – Bergi Oct 11 '16 at 22:21
  • 2
    @Lindauson not really synchronizing, as the promise api has minimal if any impact on timing, but organizing would be accurate, the Q API helps to organize execution chains of callback based on success and failure conditions. – 42shadow42 Oct 11 '16 at 22:42
2
  1. Promises are objects. They are not 'executed'. They are 'resolved' or 'rejected'. When you create the array, you are executing the mockPromise() function three times. This function is immediately executed in that point of the code.

  2. The mockPromise() function creates a deferred and returns the associated promise. It also sets a timer to resolve the returned promise in the future.

  3. Q.all() just waits for the 3 promises to be 'resolved'. (technically it returns a new promise that will be resolved when the 3 previous promises are resolved)

If you want to execute the three async functions one after the other, I would recommend using the excellent async.js library. It provides many async flow control primitives. In your case you may be interested in series or waterfall methods.

tato
  • 5,439
  • 4
  • 31
  • 27
  • I really like your response @tato. In my (un-simplified) case, the functions that return the promises are added to the array based on a calculation. I noticed that the functions were being invoked even before the array was completely populated. It's not a major problem, but doesn't that create a race condition? – Lindauson Oct 11 '16 at 22:35
  • ... That is, in the event the promises array is still being populated, couldn't Q.all(promises) read completion prematurely? – Lindauson Oct 11 '16 at 22:38
  • In javascript you NEVER have race conditions. It is single threaded. So first call to mockPromise() will be completely executed before starting the second. However, notice that the *full execution* of mockPromise() only involves starting the timer. Timer completion will happen in the future and will **resolve** the promise. – tato Oct 11 '16 at 22:39
  • ... and Q.all() will not be executed until the three calls to mockPromise() have been completely executed (again, only starting the timer) – tato Oct 11 '16 at 22:40
  • @Lindauson No, Q.all(promises) will never read completion prematurely, the promise returned from Q.all(promises) will not be resolved until all promises in the array are COMPLETED (resolved or rejected) – 42shadow42 Oct 11 '16 at 22:45
  • 42shadow42, I understand that Q.all(promises) will not be resolved until all promises in the array are completed. What I was concerned with was, the possibility of Q.all(promises) incorrectly reading the end of the array because the array of promises was still being built/calculated. But, as @tato points-out, JavaScript is single threaded, the array is going to be built before Q.all(promises) is even run. – Lindauson Oct 11 '16 at 22:55
0

You'd normally defer asynchronous functionality, not just a value. For example:

'use strict';

var Q = require("q");

var mockPromise = function (statement) {
    var deferred = Q.defer();

    console.log("I'm running before I'm queued ...");

    setTimeout(function () {
        deferred.resolve(statement());
    }, 5000);

    return deferred.promise;
};

var promises = [
    mockPromise(function() { 
        console.log("running1"); 
        return "1st statement";
    }),
    mockPromise(function() { 
        console.log("running2"); 
        return "2nd statement";
    }),
    mockPromise(function() { 
        console.log("running3"); 
        return "3rd statement";
    }),
];

Q.all(promises)
.then(function (results) {
    console.log(results);
});

Note that the deferred functionality is going to run regardless of whether you ever call a .then on a promise.

Andrew Rueckert
  • 4,858
  • 1
  • 33
  • 44
  • Thanks for your response Andrew. What was more interesting to me was that the functions get executed, even without/before calling Q.all(promise). – Lindauson Oct 11 '16 at 22:23
0

Let me show a sample using standard promises. They work pretty much the same as Q promises:

function mockPromise(value) {
  return new Promise(resolve => {
    console.log("I'm not running before I'm queued ...");
    setTimeout(() => {
      resolve(value);
    }, 1000);
  });
}

mockPromise("1st promise").then(x => {
  console.log(x);
  return mockPromise("2nd promise");
}).then(x => {
  console.log(x);
  return mockPromise("3nd promise");
}).then(x => {
  console.log(x);
});
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97