0

I have a web page which is chock full of javascript, and a few references to resources like images for the javascript to work with. I use a websocket to communicate with the server; the javascript parses the socket's data and does things with the page presentation accordingly. It all works fine, except when it doesn't.

The problem appears to be that that page contains images which I want to display parts of, under javascript control. No matter how I play with defer, there are apparently situations in which the images don't seem to be fully downloaded before the javascript tries to use them. The result is images are missing when the page is rendered, some small percentage of the time.

I'm not very used to languages and protocols where you don't have strict control over what happens when, so the server and browser shipping stuff and executing stuff in an uncontrolled and asynch order annoys me. So I'd like to stop depending on apparently unreliable tricks like defer. What I'd like to do is just download the whole page, and then open my websocket and send my images and other resources down through it. When that process is complete, I'll know it's safe to accept other commands from the websocket and get on with doing what the page does. In other words I want to subvert the browsers asynch handling of resources, and handle it all serially under javascript control.

Pouring an image file from the server down a socket is easy and I have no trouble coming up with protocols to do it. Capturing the data as byte arrays, also easy.

But how do I get them interpreted as images?

I know there are downsides to this approach. I won't get browser caching of my images and the initial page won't load as quickly. I'm ok with that. I'm just tired of 95% working solutions and having to wonder if what I did works in every browser imaginable. (Working on everything from IE 8 to next year's Chrome is a requirement for me.)

Is this approach viable? Are there better ways to get strict, portable control of resource loading?

5
  • Knowing when an async operation is complete in JS is very specific to the exact operation. Post some code for an async operation and we can help you understand exactly when it is done. Without a very specific situation, there is no generic answer for async. All async problems in JS are 100% solvable with reliability with the proper code. If you're going to be developing in JS, it is worth learning how to do this properly. We need your code in order to help. Commented Jan 25, 2015 at 21:34
  • 1
    On one of your many other topics in your question, I would wonder why you're putting an image down a webSocket when you can just serve up the image directly from your server and let the browser request it AS an image rather than stuff it down a socket as some array and then try to convert it to an image. You can research dataURLs for images if you really want to do it the hard way. If you want "send" the image from server to client, then send a URL in the webSocket and have the browser fetch the URL as an image. Commented Jan 25, 2015 at 21:36
  • The current webpage is much too long to post, over 230kb, but a summary: There's a single (and large) javascript, declared as <script language="javascript" type="text/javascript" defer> It is the last thing in the <body> section.The script starts with a large number of variable declarations, including a bunch of image placeholders like var cardsImage = new Image(); Commented Jan 27, 2015 at 2:00
  • Later in the script I have a websocket.onopen = function (ev) {...} and ... contains image source assignments: cardsImage.src="xxx"; This is intended to trigger the download of the image and in a perfect world I could have something somewhere after it that amounted to "now wait for all outstanding resource requests to be resolved", but I don't know of anything like that. The websocket is opened early in the execution, sometime thereafter I need to refer to cardsImage. Commented Jan 27, 2015 at 2:00
  • But, intermittently, when that happens, nothing is drawn, which leads me to think the image isn't all there yet. I've tried other ways to load the image (image tags in the html) but that also seemed to have timing problems. Commented Jan 27, 2015 at 2:00

1 Answer 1

1

You still haven't been very specific about what resources you are waiting for other than images, but if they are all images, then you can use this loadMonitor object to monitor when N images are done loading:

function loadMonitor(/* img1, img2, img3 */) {
    var cntr = 0, doneFn, self = this;

    function checkDone() {
        if (cntr === 0 && doneFn) {
            // clear out doneFn so nothing that is done in the doneFn callback
            // accidentally cause the callback to get called again
            var f = doneFn;
            doneFn = null;
            f.call(self);
        }
    }

    function handleEvents(obj, eventList) {
        var events = eventList.split(" "), i;

        function handler() {
            --cntr;
            for (i = 0; i < events.length; i++) {
                obj.removeEventListener(events[i], handler);
            }
            checkDone();
        }

        for (i = 0; i < events.length; i++) {
            obj.addEventListener(events[i], handler);
        }
    }

    this.add = function(/* img1, img2, img3 */) {
        if (doneFn) {
            throw new Error("Can't call loadMonitor.add() after calling loadMonitor.start(fn)");
        }
        var img;
        for (var i = 0; i < arguments.length; i++) {
            img = arguments[i];
            if (!img.src || !img.complete) {
                ++cntr;
                handleEvents(img, "load error abort");
            }
        }
    };

    this.start = function(fn) {
        if (!fn) {
            throw new Error("must pass completion function as loadMonitor.start(fn)");
        }
        doneFn = fn;
        checkDone();
    };

    // process constructor arguments
    this.add.apply(this, arguments);
}

// example usage code

var cardsImage = new Image();
cardsImage.src = ...

var playerImage = new Image();
playerImage.src = ...

var tableImage = new Image();

var watcher = new loadMonitor(cardsImage, playerImage, tableImage);
// .start() tells the monitor that all images are now in the monitor
// and passes it our callback so it can now tell us when things are done
watcher.start(function() {
    // put code here that wants to run when all the images are loaded
});

// the .src value can be set before or after the image has been 
// added to the loadMonitor
tableImage.src = ...

Note, you must make sure that all images you put in the loadMonitor do get a .src assigned or the loadMonitor will never call its callback because that image will never finish.

Working demo: http://jsfiddle.net/jfriend00/g9x74d2j/

Sign up to request clarification or add additional context in comments.

3 Comments

If I understand that, I'd have to put my entire processing - thousand of lines of code - in that .start() function. That's probably fine but it certainly seems obscure. Also, assume all three images happen to load before new loadMonitor() happens to execute. Will it still work? The other other resources I'm worried about is audio clips. Will it extend to that? Finally, is this portable to all known popular browsers?
@ScottM - Yes, all your processing that requires those resources would need to go inside a function that you pass to .start(). That is normal for code that wants to run when some event has occurred. Yes, it will work if the images are done loading before the loadMonitor is created or before .start() is called. It has explicit code to handle that condition.
@ScottM - This will work in all browsers except versions of IE earlier than IE9. If you want it to work in IE8 and earlier, it can easily be adapted to do so, but that is generally not a requirement any more so I avoid that additional complexity and testing (IE8 and earlier uses .attachEvent() instead of .addEventListener()). You'd have to show me what you're doing with audio files and how you're loading them. I'm sure there is a way to know when they are loaded, but it would depend upon what you're doing. Some audio is streamed, not loaded entirely in advance so it depends.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.