Each JavaScript environment comes with its own set of async functions. Some, like setTimeout and setInterval, are ubiquitous. Others are unique to certain browsers or server-side frameworks. The async functions provided by the JavaScript environment generally fall into two categories: I/O and timing. These are the basic building blocks that you’ll use to define complex async behaviors in your applications.
Node.js wasn’t created so that people could run JavaScript on the server. It was created because Ryan Dahl wanted an event-driven server framework built on a high-level language. JavaScript just happened to be the right language for the job. Why? Because the language is a perfect fit for nonblocking I/O.
In other languages, it’s tempting to “block” your application (typically by running a loop) until an I/O request completes. In JavaScript, that approach isn’t even possible. A loop like this will run forever:
| var ajaxRequest = new XMLHttpRequest; |
| ajaxRequest.open('GET', url); |
| ajaxRequest.send(null); |
| while (ajaxRequest.readyState === XMLHttpRequest.UNSENT) { |
| // readyState can't change until the loop returns |
| }; |
Instead, you need to attach a handler and return to the event queue.
| var ajaxRequest = new XMLHttpRequest; |
| ajaxRequest.open('GET', url); |
| ajaxRequest.send(null); |
| ajaxRequest.onreadystatechange = function() { |
| // ... |
| }; |
That’s how it goes. Whether you’re waiting for a keypress from the user or a batch of data from a remote server, you need to define a callback—unless your JavaScript environment gives you a synchronous I/O function that does the blocking for you.
In the browser, Ajax methods have an async option that can (but should never, ever) be set to false, bringing the entire browser pane to a halt until a response is received. In Node.js, synchronous API methods are clearly indicated with names like fs.readFileSync. These are convenient when writing short scripts but should be avoided when writing applications that need to handle multiple requests or operations in parallel. And these days, which applications don’t?
Some I/O functions have both synchronous and async effects. For instance, when you manipulate the DOM in a modern browser, the changes are immediate from your script’s perspective but aren’t rendered until you return to the event queue. That prevents the DOM from being rendered in an inconsistent state. You can see a simple demonstration of this at http://jsfiddle.net/TrevorBurnham/SNBYV/.
Adapting to nonblocking I/O is one of the biggest hurdles that newcomers to JavaScript face, but it’s also one of the language’s key strengths. It makes writing efficient, event-based code feel natural.
We’ve seen how async functions are a natural fit for I/O operations, but sometimes we want asynchronicity for its own sake. That is, we want to make a function run at some point in the future, perhaps for an animation or a simulation. The well-known functions for time-based events are setTimeout and its repeating cousin, setInterval.
Unfortunately, these well-known timer functions have their flaws. As we saw in Blocking the Thread, one of those flaws is insurmountable: no JavaScript timing function can cause code to run while other code is running in the same JavaScript process. But even with that limitation in mind, setTimeout and setInterval are alarmingly imprecise. Here’s a demonstration:
EventModel/fireCount.js | |
| var fireCount = 0; |
| var start = new Date; |
| var timer = setInterval(function() { |
| if (new Date - start > 1000) { |
| clearInterval(timer); |
| console.log(fireCount); |
| return; |
| } |
| fireCount++; |
| }, 0); |
When we schedule an event with setInterval and a 0ms delay, it should run as often as possible, right? So, in a modern browser powered by a speedy Intel i7 processor, at what rate does the event fire?
About 200/sec. That’s across Chrome, Safari, and Firefox. Under Node, the event fired at a rate of about 1000/sec. (Using setTimeout to schedule each iteration yields similar results.) By comparison, replacing setInterval with a simple while loop brings that rate to 4,000,000/sec in Chrome and 5,000,000/sec in Node!
What’s going on? It turns out that setTimeout and setInterval are slow by design. In fact, the HTML spec (which all major browsers respect) mandates a minimum timeout/interval of 4ms![16]
So, what do you do when you need finer-grained timing? Some runtimes offer alternatives.
In Node, process.nextTick lets you schedule an event to fire ASAP. On my system, process.nextTick events fire at a rate of over 100,000/sec.
Modern browsers (including IE9+) have a requestAnimationFrame function, which serves a dual purpose: it allows JavaScript animations to run at 60+ frames/sec, and it conserves CPU cycles by preventing those animations from running in background tabs. In the latest Chrome, you can even get submillisecond precision.[17]
Though they’re the bread and butter of async JavaScript, never forget that setTimeout and setInterval are imprecise tools. When you just want to produce a short delay in Node, use process.nextTick. In the browser, try to use a shim[18] that defers to requestAnimationFrame in browsers that support it and falls back on setTimeout in those that don’t.
That concludes our brief overview of basic async functions in JavaScript. But how do we tell when a function is async anyway? In the next section, we’ll ponder that question as we write our own async functions.
3.12.152.194