1

(this is not a duplicate of JavaScript closure inside loops – simple practical example because you can't choose which parameters the catch function takes)

I'm new to the asynchronous callback nature of Node.js. I'm trying to find out which element in a for loop threw an exception.

The current code always returns the last element in the array, regardless of which element threw the exception.

for (i = 0; i < output.length; i++) {
    var entity = Structure.model(entity_type)[1].forge();

    /* do some stuff here which I've taken out to simplify */

    entity.save()
        .then(function(entity) {
            console.log('We have saved the entity');
            console.log(entity);
            returnObj.import_count++;
        })
        .catch(function(error) {
            console.log('There was an error: ' + error);
            console.log('value of entity: ', entity); /* THIS entity variable is wrong */
            returnObj.error = true;
            returnObj.error_count++;
            returnObj.error_items.push(error);
        })
        .finally(function() {
            returnObj.total_count++;
            if (returnObj.total_count >= output.length) {
                console.log('We have reached the end. Signing out');
                console.log(returnObj);
                return returnObj;
            } else {
                console.log('Finished processing ' + returnObj.total_count + ' of ' + output.length);
            }
        })
}

How do I write promises in a way that gives me access to the element that threw the exception so I can store it in a list of problematic elements?

Community
  • 1
  • 1
TheBarnacle
  • 157
  • 6
  • What do you mean by returns last element? – thefourtheye Jul 03 '15 at 10:45
  • I mean that, after I iterate through 30 elements, it always returns element 30, instead of the element (e.g. 15) that caused the exception. – TheBarnacle Jul 03 '15 at 11:39
  • If you don't think it's a duplicate, please refer to [infamous loop issue](http://stackoverflow.com/q/1451009/1048572) instead. It's exactly the same problem - you don't have to choose which parameters the `catch` or `onclick` or whatever callback takes. – Bergi Jul 07 '15 at 14:17
  • Thanks very much for pointing me to that. I'm afraid I wasn't familiar with the vocabulary of "closure," nor did I realise this problem wasn't restricted to async try-catch statements. Hence when I was searching for the issue, I did not find the answers you have linked to. Is it possible someone else will do the same thing? – TheBarnacle Jul 07 '15 at 15:47

1 Answers1

1

This happens because the anonymous function passed to catch has access to entity only via a closure.

If entity is a primitive type, you can get around this easily by constructing a new function which captures this value via a param (where the value of entity will be copied construction time).

.catch(function(entity){ 
    return function(error) {
        console.log('There was an error: ' + error);
        console.log('value of entity: ', entity); /* THIS entity variable is wrong */
        returnObj.error = true;
        returnObj.error_count++;
        returnObj.error_items.push(error);
    };
}(entity))

(note that I'm invoking the function immediately with (), so catch receives only the returned function as a parameter)

If entity is an object (which are only passed by reference), you can use the same basic principle, but you have to create a copy of this entity, which will be slightly more complicated. In this case, it's probably easier if you write your error handler using the primitive i (as a primitive type it's easy to capture with the above method) or if you don't reuse the entity variable in your loop.

Btw, are you sure var entity = Structure.model(entity_type)[1].forge();-> here the 1 is not supposed to be an i?

TheBarnacle
  • 157
  • 6
doldt
  • 4,466
  • 3
  • 21
  • 36
  • Thanks for this! Do you mean that I should write .catch(function(i) { }()) ? I didn't know the parameter names could refer to existing variables. I thought function(variableName) just instantiated a new variable in a new scope. – TheBarnacle Jul 03 '15 at 11:37
  • Oh - the [1] is deliberate -I should have perhaps left that out too as it's referring to something elsewhere in the program. And "entity" is an object which is why I need to think about your second solution. – TheBarnacle Jul 03 '15 at 11:38
  • Yes, I mean that. If a parameter name refers to an existing variable, it will *shadow* it, meaning that inside that function by that name you can only reference that parameter. (This is a pretty ubiquitous concept, not javascript-specific, although it does come to play often with closures) – doldt Jul 03 '15 at 11:40
  • I'm afraid I can't seem to make that work for me. The variable i is coming out as undefined if I replace it with the entity variable. – TheBarnacle Jul 03 '15 at 13:22
  • What do you mean replace it? Edit your new code into the OP or show a plunkr please. – doldt Jul 03 '15 at 21:02
  • (sorry, I meant replace entity with i, in view of what you said about primitive types). HOWEVER I have now managed to get it to work after making the above edit. Thank you very much for your help! – TheBarnacle Jul 07 '15 at 08:57
  • Thanks for the edit! I'm glad it helped. – doldt Jul 07 '15 at 11:42