Search code examples
javascriptnode.jscallbackevent-loopasynccallback

Node for-loop callback required


I want to change the Tags format which I am fetching form one of the collections. Tags data contains some KC ids in an array which I am using to get KC data and insert in TagUnit to get final response format.

var newTags = Tags.map(function(TagUnit) {
    for (var i = 0; i < TagUnit.kcs.length; i++) {
        KCArray = [];
        KC.findById(TagUnit.kcs[i], function(error, data) {
            KCMap = {};
            KCMap['kc_id'] = data._id;
            KCMap['kc_title'] = data.title;
            KCArray.push(KCMap);
            if (KCArray.length == TagUnit.kcs.length) {
                TagUnit.kcs = KCArray;
            }
        });
     }
     return TagUnit;
});

response.send(JSON.stringify(newTags));

But I am not getting desired result. Response is giving out Tag data in initial for instead of formatted form. I guess it is due to event looping. I will be grateful if someone can help me with this.

**Edit: ** I am using MongoDB as database and mongoose as ORM.


Solution

  • I'd suggest using promises to manage your async operations which is now the standard in ES6. You don't say what database you're using (it may already have a promise-based interface). If it doesn't, then we manually promisify KC.findById():

    function findById(key) {
        return new Promise(function(resolve, reject) {
            KC.findById(key, function(err, data) {
                if (err) return reject(err);
                resolve(data);
            });
        });
    }
    

    Then, assuming you can do all these find operations in parallel, you can use Promise.all() to both keep track of when they are all done and to order them for you.

    var allPromises = Tags.map(function(TagUnit) {
        var promises = TagUnit.kcs.map(function(key) {
            return findById(key).then(function(data) {
                // make resolved value be this object
                return {kc_id: data._id, kc_title: data.title};
            });
        });
        // this returns a promise that resolves with an array of the kc_id and kc_title objects
        return Promise.all(promises).then(function(results) {
            return {
                 _id: TagUnit._id,
                 kcs: results
            };
        });
    });
    
    // now see when they are all done
    Promise.all(allPromises).then(function(results) {
        response.send(JSON.stringify(results));
    }).catch(function(err) {
        // send error response here
    });