Categories
Etc

Pumping Messages (or A Blast From The Past)

Here it is, the second in a continuing series on browser-based JS development.

Last time I lightly covered the basics of the Page event life cycle, with onload and onunload being the two major players. However, I didn’t go into what events are and how they impact how you develop a web app. To get where we’re going, we really have to go back to the early days of Windows((The following statements may be equally applicable to X, NEXT, Cocoa, Carbon, GTK, whatever. I don’t really know. I’ve never done serious development on those toolkits. Further enlightenment would be appreciated if anyone does have experience.)).

If you’ve ever done Windows GUI programming, you’re probably familiar with the MessagePump (or DoEvents in VB). In classic Windows GUI programming, there is one thread, and only one thread, responsible for drawing and updating the UI. By default, all of your code runs on this same thread. At the heart of this thread lives a MessagePump, which is responsible for pulling a message off a queue, cracking it to determine what it’s all about, and then calling the parties interested in said message. To do things in the application, you tell the application that you’re interested in certain messages, like say WM_CLOSE, and the message pump happily calls your function when that message comes through the queue. JavaScript + DOM works in a very similar way.

After you write your first bit of DOM script, you probably notice that there’s no real main entry point for the page; no int main() {} or the like. Code that lives in a script block is run when the page loads, and you set up event listeners to be called when interesting things happen, like say a button is clicked. There are a number of ways to connect your function to an event; I’ll cover those in another one of these essays, but suffice to say for now, if you want to listen for an event, use dojo.connect(). For example, to listen for a click on a button you’d say dojo.connect(“myButton”, “click”, function(evt) { alert(‘myButton was clicked’); }); More on this later.

So far you have two ways to get your code called. 1) Code in script blocks is run while the page is being initialized and 2) in response to some DOM event. There’s a third: you can ask that some code be run at a set point in the future using setTimeout or setInterval. It looks something like

  setTimeout(function() { alert('5 seconds have elapsed'); }, 5000)

or

 setInterval(function() { alert('i run every 5 seconds'); }, 5000);

Both of these return a handle that can be later passed to clearTimeout or clearInterval to kill the pending timer. So something like

 var myHandle = setTimeout(function(){}, 5000);
 clearTimeout(myHandle);

would prevent the function from being called at all. It’s important to understand that the time given to these functions is more like a suggestion than a strict contract. Guess why…

All of your JavaScript runs on the same thread. Period. This thread is also responsible for updating the GUI, so it’s fairly important to limit what you’re doing at any given time. If you’re doing something crazy, like say bubble-sorting a million element array, you’re blocking everything else. The browsers do try to prevent you from going buck-wild; I would guess you’ve all seen the “Holy crap this script is going crazy!” dialog from IE or Firefox at some point. The single-threaded nature of the DOM also explains why the time passed to setTimeout and setInterval is merely a suggestion: the browser will run your code when it can, on the GUI thread.

Sometimes you just need to do something that takes a while. So how do you break it up? Here’s where closures come in really handy, along with the run-when-you-can nature of setTimeout ((here’s a handy little wrapper that better expresses the intent of setTimeout(func, 0):

function defer(func){
   return setTimeout(func, 0);
}
usage: defer(function() { /* work work work */ });

I don’t generally use it because it’s just one more function call to get stuff done and setTimeout(func, 0) is fairly easy to read anyway.)). Consider the following:

function countToAMillion(callback) {
   var currentState = 0;
   var iters = 1000;
   var goal = 1000000;

   function worker() {
     var i = iters;
     while(currentState < goal && i !== 0) {
       currentState++;
       i--;
     }
     if(currentState === goal) {
       callback();
     } else {
       setTimeout(arguments.callee, 0);
     }
   }
   setTimeout(worker, 0);
}

This function counts to a million, yielding to the browser every 1000 iterations ((see the Mozilla docs for more details on arguments.callee)). By yielding, you’re giving the browser a chance to catch up, dispatch other events that need to happen, and then resume processing your workload.

In general, it’s a good idea to keep your code short and sweet, especially when responding to events. A good pattern to follow looks something like this (though we don’t use it a lot today):

  1. inspect event to see if you can handle it
  2. propagate or cancel the event as needed
  3. defer work you need to do in response to the event

or in code:


Yes in this case countToAMillion instantly defers it’s own work, but I don’t necessarily know that when I’m wiring up the event handler. Also, sometimes you have to do a little work to figure out if you can actually handle the event; that’s fine, just try to keep it quick.

This one got a little long, but I hope it’s a good intro on how to construct an app and how to best respond to events. To sum up: everything runs on one thread so yield when you can to give your neighbors time to do their work too. Questions, comments, snide remarks welcome. Next time, events and topics.

Advertisements
Categories
Etc

The Page Event Life Cycle

This is the first in a series of posts on browser-based development with Dojo. These were originally intended for the Bloglines team, but I thought I’d throw them out to a larger audience. I’ll try to keep the entries short, but no promises. Some things just need lots of explanation… Anyway, here’s the first and it’s on the event life cycle for a page.

When an HTML page loads (and unloads) a number of events fire allowing the application to do different things. Browsers tend to fire the following events:

window.onload
fires when the page has finished loading. This includes loading any external resources referenced by the HTML, like stylesheets, script, and images, both tags and images referenced by background: rules in CSS.
window.onunload
fires when the user navigates away from the page. This one is provided to give your script the ability to do stuff that needs to happen when the user leaves, like save state or cleanup outstanding XHRs. This event also tends to like to terminate execution after a set amount of time, so anything you do in here has to happen fast. Your script may not run to completion, depending on how long you’re taking and which browser you’re on. I don’t recall the limits off-hand, but they do vary.

In addition, some browsers expose a method for determining when the DOM is done being constructed, but images and such have not finished loading. Firefox calls this onDOMContentLoaded and there are hacks to get it supported on other browsers. This is really handy; when you’re pulling content from edge cached locations you don’t want to wait for all those locations to answer DNS and respond before you can get on with setting up your page and responding to user events.

Now, why wait for onLoad at all before running script? Well, the DOM is in an indeterminate state beforehand. Sure, you can setup an interval and query for things, but it’s generally safer to just wait for the browser to tell you everything is ok and then go on your way.

Dojo wraps up some of the hacks referenced above and exposes them as dojo.addOnLoad(). If you want something to happen when the browser is all set, just add it like this:

Definitely do not do the following:

This will whack any event handlers stuck onto onload (including Dojo’s) and nothing based on onLoad will work. A similar warning applies to onUnload. Don’t go trying to window.onunload = myAwesomeCleanupFunction. Dojo does some cleanup work in unload that we really want to keep around to keep IE from leaking like a sieve.

So, you may be wondering, what kinds of things would I want to dojo.addOnLoad()? How do I sync up all these things if there are dependencies between them? For Bloglines, we try to funnel everything init related through the main class for the page, which for /b/view is an instantiation of bl.page.view. bl.page.view hooks onto Dojo’s onLoad and does a whole bunch of work in bl.page.view.init. Instead of trying to hook all the independent parts of the application up to Dojo’s onLoad, we let init take care of all the lovely synchronization issues. That.. mostly works.

So with all this work going on in init, what happens to the browser? More on that and the wonderful single-threaded, message-based world of DOM development in the next installment.