coresmart / persistencejs

persistence.js is an asynchronous Javascript database mapper library. You can use it in the browser, as well on the server (and you can share data models between them).
http://persistencejs.org
1.73k stars 240 forks source link

How to properly execute callback in a nested "for loop" with an async call in it? #152

Closed AleksMeshkov closed 10 years ago

AleksMeshkov commented 10 years ago

Hi! I have a Specialist model that has many Tags (tags are describing specialist skills). I want to fetch all specialists and pre-populate them with appropriate tags while fetching them from db. The issue I ran into is about executing callback when data retrieve is finished (including tags relationships). Could you please look through my code below? Is there any possibility to use Deferred with promises in this case?

// fetch all
var get = function (callback) {
        // fetch all
        Specialist.all().list(function (items) {
            var specialists = [];
            items.forEach(function (item) {
                item._tags = []; // tags - is a one-to-many relationship
                // I want to fetch and pre-populate every specialist with it's tags
                item.tags.list(null, function (tags) { // oops, it was an async call that breaks " items.forEach" sync loop
                    tags.forEach(function (tag) {
                        item._tags.push({
                            tag : tag.tag
                        });
                    });
                });
                specialists.push(item);
            });
            callback(specialists); // SO HOW SHOULD METHOD EXECUTE CALLBACK WHEN TAGS POPULATING IS FINISHED?
        });
    }
};
zefhemel commented 10 years ago

You'll need to use an asynchronous for-loop. There are other libraries that offer utilities for this, such as async.js. I also wrote up a blog post a while back showing how to asynchronously do for loops in JavaScript.

AleksMeshkov commented 10 years ago

@zefhemel thank you! I'll dive into this.

AleksMeshkov commented 10 years ago

Well here is my solution. I applied example of a managed control flow written here http://book.mixu.net/node/ch7.html

// fetch all
var get = function (callback) {
        Specialist.all().list(function (items) {
            var specialists = [];
            // that's an async function
            function populateWithTags(item, callback) {
                item._tags = [];
                item.tags.list(null, function (tags) {
                    tags.forEach(function (tag) {
                        item._tags.push({
                            tag : tag.tag
                        });
                    });
                    callback(item);
                });
            }

            function series(item) {
                if (item) {
                    populateWithTags(item, function(itemWithTags){
                       specialists.push(itemWithTags);
                        return series(items.shift());
                    });
                } else {
                    return callback(specialists);
                }
            }

            series(items.shift());
        });
    }
};