2016-08-15 JavaScript callbacks

Note: this is an introductory article on JavaScript callbacks. There will be a bit more advanced follow-up – an introduction to promises.

When programming a computer, you often want your program to do something first and something else next. The player shoots, and after some time the bullet hits or misses. You enter a formula in a spreadsheet, and after a while the whole spreadsheet is refreshed. You get the idea. The usual way to express such a sequence in time is to write some statement and then write another one after that.

There is at least one situation, though, when this way of coding is a Very Bad Idea™. Assume you have an environment which can only execute one program (thread) at the time (like, say, Emacs, or a web browser). Assume that you have a task for your program which can take a significant amount of time to finish (like, say, fetching some data over the network). What’s more, the lengthy part is not really done by your program – some external service does it, and the only thing your program has to do is waiting for it to complete its task. Instead of blocking the whole application for a few seconds needed to finish the data retrieval, it would be better to “launch” it and get to other task, which can be done before the data arrive. (Many modern websites do things like that. For instance, if yoy ask Facebook to “show more comments”, you can still do things on the page while the comments are being loaded.) In other words, you want your program to do other things while waiting for the results of the long operation.

The usual solution to this problem is via callbacks. The idea is that instead of waiting doing nothing while the long operation is being performed, our program can do other things. Only after the job is done, we “get back” to the point where we started it and do whatever we wanted with e.g. the data retrieved.

There’s one catch here, though. Since our program is executed by one thread, it can’t really do two things at a time. So how it’s possible that we continue doing what we were doing after telling the system to so something that takes a long time to complete, and then get back to process the results of that long operation? Shouldn’t we somehow interrupt the execution of the former code?

Indeed, twenty years ago the right mechanism for this mode of operation would probably be so-called interrupts, a low-level trick that enabled the processor to suspend whatever it worked on, deal with the “interrupt”, or signal, telling it that something needs its attention now, and get back to its work when the interrupt processing was finished.

In case of web applications in JavaScript, which are usually interactive, things are a bit different. An important characteristic of interactive applications (JS or not) is that they don’t do anything most of the time. Usually they wait for user input (mouse clicks, key presses etc.), then do what the user told them to do, and get back to the “waiting” state again. And that’s the moment when they can easily finish whatever they had started earlier.

OK, so enough theory. Let’s try it practically. Since this post is about JavaScript, we’ll use node, which can be easily run from the command line. We will simulate the long-running operation using a while (true) loop, breaking out of it when the time comes.

var wait_busily = function(time, log) {
	var end = Date.now() + time;
	while (true) {
		if (Date.now() > end)
			break;
	}
	console.log(log);
};

(I found it somewhere on StackOverflow, with the caveat that you should never use something like that in production code, since it blocks the thread completely for the given amount of time. This is exactly what I need for this experiment, though – to simulate a program which is really busy. In a moment we’ll see how a function which waits a designated amount of time and then does something should be implemented.)

You may now prepare a file wait.js with that function and then two lines saying e.g.

wait_busily(1000, 'a');
wait_busily(1000, 'b');

and launch node wait from the console. Good: we wait a second, get an “a”, wait another second and get a “b”.

Now imagine that our first function, which takes a second to complete, is really retrieving something from a database or the Internet. It still takes a second to complete, but someone else physically does it. In other words, our thread is free during that second. Instead of just idly waiting for the long operation to complete, it can do a whole lot of more useful things, like launch other similar requests, or just react to user input (if we are in a browser instead of node.js and the user clicks something, it is rather lame to make him wait a second for any reaction!).

Let us simulate the “wait for the result, but do something different” scheme. One question immediately springs to mind: if we just launch our long operation and proceed to other things right after that, how do we (a) know when the long operation completed and (b) get its result?

Enter callbacks. Many functions which may take a long time to finish and do not need the local processor to actually do the job accept a special kind of parameter, which is a function to be invoked later when the long operation is complete (and the thread has nothing else to do). This function often accepts an argument, which is the result of our long operation. (In fact, usually it accepts two arguments: while this is not enforced by the langauge, a common practice is that the former argument is the error (or null if the operation succeeded), and the latter the result.)

Coming back to our example, we are going now to write a function wait_lazily, which (like wait_busily) will accept time and a character for logging purposes, but also a callback launched when the desgnated time passes. (JavaScript already has a very similar function, called setTimeout, but we want also the “visual logging” capability, i.e., printing a character after we’re done.) Since we won’t have any meaningful “result” of the waiting, the callback will not accept any arguments (or more precisely, will ignore them).

var wait_lazily = function(time, log, callback) {
	setTimeout(function() {
		console.log(log);
		if (callback)
			callback();
	}, time);
};

Try now this code:

wait_lazily(1000, 'a');
wait_lazily(2000, 'b');

Of course, in practice it might happen that we wanted to do something after, say, “a” finished its job:

wait_lazily(1000, 'a', function() {
	wait_lazily(2000, 'c');
});
wait_lazily(2000, 'b');

This way “c” could use the result of “a” (but not “b”). Of course, “b” does not have access to the results of any of “a” and “c”.

It is not difficult to imagine that this can, well, escalate quickly: if we have several functions, each needing the result of the previous one(s), things may get complex. Also, it is not immediately obvious how to handle a situation when “c” needs the results of both “a” and “b” (and we want “a” and “b” to run in parallel, but we don’t even know which is going to finish first). In such situations, when using plain vanilla callbacks is cumbersome, one can use promises – but that will have to wait for another post.

CategoryEnglish, CategoryBlog, CategoryJavaScript