klonaway
klonaway

Reputation: 479

Fixing scope/context mistake in Node.js with Bluebird promises

I thought I had finally grasped some fundamental javascript notions thanks to this awesome video playlist : callbacks, scope and context were not a headcrack anymore.

And then, I decided to use promises (bluebird in Node.js)... Those problems arise again, and I guess I don't understand how scope and context work with promises. Here is a test I ran :

function testit(){
  for (var i=0 ; i<3 ; i++) {
    var test = i;
    console.log(test);
    
    Promise
    .delay(5000) // do some async operations...
    .then( () => {
        console.log(test);
    });
  }
  return("finished");
}
console.log(testit());

//// What I hoped to see :
// 0, 1, 2, finished, 0, 1, 2

//// What happens :
// 0, 1, 2, finished, 2, 2, 2

Here I am, back into scope/context trouble. To fix this, I found bluebird Promise.bind(), which gave me :

function testit(){
  for (var i=0 ; i<3 ; i++) {
    var test = i;
    console.log(test);
    
    Promise
    .bind(this, test)
    .delay(5000)
    .then( (test) => {
        console.log(test);
    });
  }
  return("finished");
}
console.log(testit());

//// What happens :
// 0, 1, 2, finished, 0, 1, 2

Yipee ! I have a workaround ! But it is clearly not handy when chaining .next() methods : I would have to pass the value of test from one promise to another... Can't do that in my code, in which I chain many different functions/methods.

So, is there a cleaner way to "bind"/"retain some values" coming from a for loop when working with promises in Node.js ?


EDIT : I'm thinking about a closure for the promise chain inside the loop :

function testit(){
  for (var i=0 ; i<3 ; i++) {
    var test = i;
    console.log(test);
    
    (function (test){
      Promise
      .delay(5000) // do some async operations...
      .then( () => {
          console.log(test);
      });
    })(test);
  }
  return("finished");
}
console.log(testit());

//// What happens :
// 0, 1, 2, finished, 0, 1, 2

It works too, and that's the best code I have so far. But my initial question remains : is there a better/cleaner way to achieve the initially expected result ?


CONCLUSION : The accepted answer is probably the cleanest way to manage async operations inside loops. Using a closure function declared outside the loop rocks. Thx Rob M. !

Upvotes: 0

Views: 95

Answers (1)

Rob M.
Rob M.

Reputation: 36511

A closure is your best bet here, otherwise test will be bound to the parent scope (not the Promise callback's scope) and you will continue seeing the run time value of test, rather than the call time value that you desire.

function getPromise(index) {
   return Promise
    .delay(5000)
    .resolve('index: ')
    .then((prefix) => {
        console.log(prefix + index);
    });
}

function testit(){
  for (var i=0 ; i<3 ; i++) {
    var test = i;
    console.log(test);
    getPromise(i);
  }
  console.log('finished');
}

.bind is a perfectly acceptable approach though if that works better for you

Upvotes: 1

Related Questions