I went to a meetup yesterday and there was a bit of confusion about how Javascript asynchronous code work and how to deal with it. When people read about asynchronous code, they tend to think of threads being dispatched, and code being interrupted with callbacks. This leads to the well-known concurrency management issues, and one has to create locks, and all that sort of things.
With Javascript (and any single threaded event-looped system), things are a bit different. Code runs in uninterrupted “bursts”. There is an event queue, and each burst corresponds to one event on the queue being processed. And the “event loop” is an infinite loop that keeps popping events from the queue as they arrive and running their associated bursts, or callbacks. On the browser things get a little bit more complicated because of the rendering loop, but this mental model is still valid.
What is an event? We are talking about low-level events here. It’s not those nice .on()
events, although the library implementing them can force a new low-level event to be created instead of just calling the callback. Some examples of low-level events are timers, network connections, disk I/O or user input.
The important thing here is that in order to keep the app responsive, the bursts should be as small as possible, because nothing will be processed until the previous burst has finished. In nodeJS, I/O, for instance, is natively split in separate bursts, which is great. One burst calls I/O, and then we can carry on processing others while we wait for the response, which will trigger another burst. We can even have separate bursts for chunks of data being read or written.
What happens if the main script has a chunk of code which is very slow (let’s say, because it runs an iterative algorithm that takes a while to complete)? The whole app is going to slow down because of this, and there is no way to put it in a separate thread. The answer is easy: run each iteration of the algorithm in a separate burst, or at least the whole chunk in its own burst. The first thing that a code needs in order to become a burst is to be a separate function. Then, instead of calling the function, call a low-level event dispatcher; for instance, set a timer that will fire in 0 milliseconds. This will defer the execution of the function until another burst.
The tricky thing here is that there might be another events on the queue, so you can’t guarantee when the code will actually run. That’s why you should supply a callback, and call it at the end of the slow function.
Notice that the slow function is not a low-level event by itself; the event is the timer, which has got a callback (the same way as we provide one to the slow function) that will run within the same burst.