1

I'm using Node.js and Q to write server-side asynchronous code. I'm new to promises (and I'm new to asynchronous programming in general), and I'm having a bit of trouble that I haven't been able to solve by staring at the Q documentation. Here's my code (it's coffeescript - let me know if you want to see the javascript instead):

templates = {}
promises = []
for type in ['html', 'text']
    promises.push Q.nfcall(fs.readFile
        , "./email_templates/#{type}.ejs"
        , 'utf8'
        ).then (data)->
            # the problem is right here - by the time
            # this function is called, we are done
            # iterating through the loop, and the value 
            # of type is incorrect
            templates[type] = data
Q.all(promises).then(()->
    console.log 'sending email...'
    # send an e-mail here...
).done ()->
    # etc

Hopefully my comments explained the problem. I want to iterate through a list of types, and then run a chain of promises for each type, but the problem is that the value of type is being changed outside of the scope of the promises. I realize that, for such a short list, I can just unroll the loop, but this is not a sustainable solution. How can I ensure that each promise sees a different yet locally correct value of type?

Matt Ball
  • 354,903
  • 100
  • 647
  • 710
jayhendren
  • 4,286
  • 2
  • 35
  • 59

2 Answers2

3

You have to encapsulate your data assignment closure in another closure, so that the value of type is preserved until the inner closure is executed.

For further details: http://www.mennovanslooten.nl/blog/post/62

peaceman
  • 2,549
  • 19
  • 14
  • Thank you. Though I didn't copy this solution exactly, the explanation in this blog post helped me create a fix. – jayhendren Nov 29 '13 at 23:56
1

I don't know CoffeeScript, but this should work in JS:

var promises = [];
var templates = {};
var ref = ['html', 'text'];

for (var i = 0, len = ref.length; i < len; i++) {
    var type = ref[i];

    promises.push(Q.nfcall(fs.readFile, "./email_templates/" + type + ".ejs", 'utf8').then((function (type) {
        return function (data) {
            return templates[type] = data;
        };
    }(type))));
}

Q.all(promises).then(function() {
    return console.log('sending email...');
    // ...
}).done(function() {
    // ...
});

Edit: CoffeeScript translation:

templates = {}
promises = []
for type in ['html', 'text']
    promises.push Q.nfcall(fs.readFile
        , "./email_templates/#{type}.ejs"
        , 'utf8'
        ).then do (type)->
            (data)->
                 templates[type] = data
Q.all(promises).then(()->
    console.log 'sending email...'
).done ()->
    console.log '...'

The part of importance being:

).then do (type)->
    (data)->
        templates[type] = data
John Kurlak
  • 6,594
  • 7
  • 43
  • 59
  • @MattBall I have an extra closure in there (see the immediately-invoked function expression). – John Kurlak Nov 29 '13 at 23:49
  • Thanks, John. I believe I understand what's going on here. However, I accepted the other answer because it provided a link to an explanation of the problem, which was more helpful than a quick-fix. – jayhendren Nov 30 '13 at 00:00
  • @jayhendren No problem. I don't care who you accept, so long as your original problem is solved! – John Kurlak Nov 30 '13 at 00:05