How to wait on http calls in Iron Router's onBeforeLoad?

I want to create a preloading script that performs a number of async functions to download external content. I’m pretty close here, but I haven’t quite figured out how to to defer calling this.next() in my onBeforeRun function. In the code below you can see I use a loop and setTimeout but I lose the context of my router somehow and this.next() is undefined.

if (Meteor.isClient) {
    IR_BeforeHooks = {
        preloadProject: function() {
            var itemsProcessed = 0;
            _.each(items.items, function(e) {
                    HTTP.get(e.S3URL, {
                            headers: {
                                'Accept': '*/*'
                            },
                            responseType: 'arraybuffer' //requires aldeed:http
                        }, function(error, result) {
                            if (error) {
                                Session.set('error', {
                                    'title': 'Could not download',
                                    'message': error
                                });
                            }
                            if (result) {
                                itemsProcessed = itemsProcessed + 1;
                            }
                        }) //http get
                }) //each
            function waitToRender(router) {
                console.log('waiting...')
                var progress = (itemsProcessed / items.items.length) * 100;
                if (progress < 100) {
                    $('.progress-bar').css('width', Math.floor(progress) + '%');
                    setTimeout((function() {
                        waitToRender(router);
                    }), 50);
                    //console.log('timeout for a few ms here')
                }
                else {
                    console.log('all done, render');
                    router.next(); // I get this is not a function error here
                }
            }
            waitToRender(this);
        }
    }
}

and my router

Router.before(
    IR_BeforeHooks.preloadProject, 
    {
        only:['editor', 'viewer', 'embedder']
    }
);

So my questions are:
How can I maintain the context of my router inside of my WaitToRender function so I can call this.next() inside of it?
or
Am I doing this completely wrong / is there a much better way?

Thanks anybody who has a moment to look at this :smile:

It looks like before you can render the route to a template, you need to download some files.

waitToRender() is using a timeout to poll if it’s ready yet.

There’s a couple things I could offer you.

  1. I’d render the template, then wait for the files. It’ll be easier to show the loading bar, loaded items, etc in a rendered template. Maybe you’re not doing this for a good reason, but I’d consider it.
  2. Use https://github.com/caolan/async in the browser.

Async let’s you pass an array of “todos” to an async function. Async then returns a promise to complete the preload, and you can measure progress. That way you don’t have to poll with setTimeout.

Just some suggestions :slight_smile:

Thank you @michaelcole. I have complicated reasons that require I preload this before rendering the views. I am hesitant to add another async package here because either way I do it I need to work on the prexisting callbacks from my http calls and I strongly prefer to use standard JS and meteor functions to do this.

I’ve setup a bounty on stack overflow for this question if anybody wants to review it there:

Is the loading bar in your code completely necessary? It seems like replacing this with a spinner would allow you to simplify the code as you won’t need that waitToRender function to calculate percentage.

Also, why are you hesitant to use another async library to help you here? Something like this would solve your problem quite easily.

Thanks for the reply.

I’ve just updated my SO post with an answer if not a work around that requires no new external libraries. To answer your questions, the loading bar is very useful for UX but it isn’t the reason I need to do the preloading http calls. To explain that would require I delve in to the guts of what it is I am making which is beyond the scope or future usefulness of this post as far as I know.

Regarding my hesitation on additional external libraries that is more a philosophical code choice to reduce future dependencies, performance, and compatibility issues along with my own understanding and ability to maintain the project’s codebase. More of a personal preference I’ve developed over time when working on complicated projects.