KLôN KLôN - 4 months ago 16
Javascript Question

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 ?

Answer

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