2

I am running into an issue when I use promise inside a loop in node.js. Below is a simple example, I am using cheerio to scrape a web page and do the following:

$('.xyz').each(function(){
  fn1()
  .then(fn2)
  .then(fn3)    
});


function fn1() {
  var deferred = Q.defer();
  console.log("1");
  deferred.resolve();
  return deferred.promise;
}

function fn2() {
  var deferred = Q.defer();
  console.log("2");
  deferred.resolve();
  return deferred.promise;
}

function fn3() {
  var deferred = Q.defer();
  console.log("3");
  deferred.resolve();
  return deferred.promise;
}

I was expecting the final output to be 123123123, but instead I am getting 111222333. Could someone explain why this is happening. I am pretty new to node.js and could use some help.

user3587203
  • 66
  • 1
  • 6

3 Answers3

5

It's because Q schedules "then" callback to the next tick (after the promise was resolved). See source. "Next tick" in turn is realized through setImmediate or setTimeout or process.nextTick and so on, depending on the platform you are using (see source). All promises realization guarantees that then callback will be executed after current call stack execution. It can be realized through adding callback to the microtask queue (like v8 engine) or to the event loop task queue (using setTimeout for example). If you do not understand how event loop task queue works - see this video

alexpods
  • 47,475
  • 10
  • 100
  • 94
  • Thanks! That video helped. I resolved the issue by following suggestions from this link: http://stackoverflow.com/questions/17217736/while-loop-with-promises?rq=1 – user3587203 Jan 18 '15 at 20:30
2

There's a subtle difference between queueing up a promise handler and redeclaring the promise. Consider the following code, which produces the behaviour you desire, that is, 123123123:

var a = Q();

[1,2,3].forEach(function(){
  a = a.then(fn1)
    .then(fn2)
    .then(fn3);
});


function fn1() {
  var deferred = Q.defer();
  console.log("1");
  deferred.resolve();
  return deferred.promise;
}

function fn2() {
  var deferred = Q.defer();
  console.log("2");
  deferred.resolve();
  return deferred.promise;
}

function fn3() {
  var deferred = Q.defer();
  console.log("3");
  deferred.resolve();
  return deferred.promise;
}

(JSBin here: http://jsbin.com/hituzedare/2/edit?js,console)

Now consider the following, very similar example, which results in 111222333.

var a = Q();

[1,2,3].forEach(function(){
  a.then(fn1)
    .then(fn2)
    .then(fn3);
});


function fn1() {
  var deferred = Q.defer();
  console.log("1");
  deferred.resolve();
  return deferred.promise;
}

function fn2() {
  var deferred = Q.defer();
  console.log("2");
  deferred.resolve();
  return deferred.promise;
}

function fn3() {
  var deferred = Q.defer();
  console.log("3");
  deferred.resolve();
  return deferred.promise;
}

(JSBin here: http://jsbin.com/hituzedare/1/edit?js,console)

The catch is: the first example redefines the variable as a new promise - specifically, the promise returned by fn3.

Therefore, on the next forEach iteration, the promise is now chained to the end of fn3, not to the top promise.

I hope this helps!

Guilherme Rodrigues
  • 2,818
  • 1
  • 17
  • 22
0

Well, it's true that you're chaining your promises with groups of threes, but you're running each chain parallelly, so the flow goes something like:

  1. Run fn1(), schedule fn2 and fn3 for later.
  2. Run fn1() again, schedule the same
  3. Run fn1() yet again, schedule the same
  4. One of the fn2()s run
  5. etc...

If you want them to run sequentially, you need to schedule them all on the same promise object.

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308