1

I don't understand the behavior of this javascript program. My intent was to sequentially fetch three numbered resources, but it seems like "i" has been captured in a closure and only its final value is used.

function fetch(x) {
    console.log('fetching resource ' + x);
}

var prom = Promise.resolve();
for(var i=1; i<=3; i++) {
    prom = prom.then(() => { fetch(i);} );
}

//prints
//fetching resource 4
//fetching resource 4
//fetching resource 4

I don't know enough about js to solve this -- how might I modify this program to produce 1, 2, and 3? Do I need to use resolve() somewhere?

UPDATE: it has been pointed out that this question is duplicative of JavaScript closure inside loops – simple practical example, which is technically true, but nonetheless I think this question has merit because of its new application in the world of Promises. Technically, the aforementioned question does correctly answer the question posed here, because the Promise element in this question turns out to be a red herring not relevant to the answer. But the recognition of the Promise as being irrelevant is an insight gained by reading this question and not the aforementioned duplicate.

Magnus
  • 10,736
  • 5
  • 44
  • 57
  • Just use `let` instead of `var`. `var` is function-scoped which means you only have one variable `i` that has the value 4 by the time your `then` callback is called. `let` is block-scoped so each iteration of the loop gets it's own variable `i`. – Paul Nov 07 '18 at 03:46
  • Possible duplicate of [JavaScript closure inside loops – simple practical example](https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – user2864740 Nov 07 '18 at 03:49

5 Answers5

2

Change var to let

function fetch(x) {
    console.log('fetching resource ' + x);
}

var prom = Promise.resolve();
for(let i=1; i<=3; i++) {
    prom = prom.then(() => { fetch(i);} );
}

Or create an IIFE for the same

function fetch(x) {
    console.log('fetching resource ' + x);
}

var prom = Promise.resolve();
for(var i=1; i<=3; i++) {
    prom = (function(i){ return prom.then(() => { fetch(i);} );})(i);
}
Nikhil Aggarwal
  • 28,197
  • 4
  • 43
  • 59
0

Create a const to store the value you want.

function fetch(x) {
    console.log('fetching resource ' + x);
}

var prom = Promise.resolve();
for(var i=1; i<=3; i++) {
    const j = i;
    prom = prom.then(() => { fetch(j);} );
}
Ricky Mo
  • 6,285
  • 1
  • 14
  • 30
0

Wrap your for each content into closure to maintain the value of i;

function fetch(x) {
    console.log('fetching resource ' + x);
}

for(var i=1; i<=3; i++) {
     (function(index){Promise.resolve().then(() => { fetch(index);} )})(i);
}
front_end_dev
  • 1,998
  • 1
  • 9
  • 14
0

I suggest you to put the promise inside the for loop, like this:

function fetch(x) {
    console.log('fetching resource ' + x);
}


for(var i=1; i<=3; i++) {
    var prom = Promise.resolve(i);
    prom.then(fetch);
}

Or if you want to define the promise outside the loop, you can create a temporary array to use Promise.all

function fetch(x) {
    console.log('fetching resource ' + x);
}

var arr = [];

for(var i=1; i<=3; i++) {
    arr.push(i);
}

Promise.all(arr).then(values =>
{
  values.forEach(fetch);
});
Tân
  • 1
  • 15
  • 56
  • 102
0

The issue here has almost nothing to do with Promises and everything to do with closures. Your code:

for(var i=1; i<=3; i++) {
    prom = prom.then(() => { fetch(i);} );
}

means "when the promise resolves, call that anonymous function, which calls fetch() with the current value of i." The current value. Since you have already exited the for loop at this point (since the anonymous function is guaranteed not be called until you return from this code), i is equal to 4.

Ricky Mo offered a practical way to do what you apparently want to do.

Michael Lorton
  • 43,060
  • 26
  • 103
  • 144