How do you keep code with continuations/callbacks readable?
- by Heinzi
Summary: Are there some well-established best-practice patterns that I can follow to keep my code readable in spite of using asynchronous code and callbacks?
I'm using a JavaScript library that does a lot of stuff asynchronously and heavily relies on callbacks. It seems that writing a simple "load A, load B, ..." method becomes quite complicated and hard to follow using this pattern.
Let me give a (contrived) example. Let's say I want to load a bunch of images (asynchronously) from a remote web server. In C#/async, I'd write something like this:
disableStartButton();
foreach (myData in myRepository) {
var result = await LoadImageAsync("http://my/server/GetImage?" + myData.Id);
if (result.Success) {
myData.Image = result.Data;
} else {
write("error loading Image " + myData.Id);
return;
}
}
write("success");
enableStartButton();
The code layout follows the "flow of events": First, the start button is disabled, then the images are loaded (await ensures that the UI stays responsive) and then the start button is enabled again.
In JavaScript, using callbacks, I came up with this:
disableStartButton();
var count = myRepository.length;
function loadImage(i) {
if (i >= count) {
write("success");
enableStartButton();
return;
}
myData = myRepository[i];
LoadImageAsync("http://my/server/GetImage?" + myData.Id,
function(success, data) {
if (success) {
myData.Image = data;
} else {
write("error loading image " + myData.Id);
return;
}
loadImage(i+1);
}
);
}
loadImage(0);
I think the drawbacks are obvious: I had to rework the loop into a recursive call, the code that's supposed to be executed in the end is somewhere in the middle of the function, the code starting the download (loadImage(0)) is at the very bottom, and it's generally much harder to read and follow. It's ugly and I don't like it.
I'm sure that I'm not the first one to encounter this problem, so my question is: Are there some well-established best-practice patterns that I can follow to keep my code readable in spite of using asynchronous code and callbacks?