Asynchronous JavaScript

For this activity, create a file named asynchronous-javascript.html to run the following code samples.

JavaScript can run code asynchronously, which means that it doesn't always run in the order that it appears (from top to bottom).

This comes into play when you run code that could take a long time to execute (such as opening a large file, or communicating across the network)

We can simulate code that takes a long time to execute by using the setTimeout() function. This will demonstrate the asynchronous nature of JavaScript.

The setTimeout() function allows you to execute a function after waiting a specified number of milliseconds. Check out this article for more info on the setTimeout() function in JavaScript

Try out this sample code, which demonstrates the setTimeout() function:

const doSomething = () => console.log("Hello")
setTimeout(doSomething, 3000);

Notice that the first parameter passed to setTimeout() is the function to be executed (a callback function), and the second parameter is the number of milliseconds to wait until the callback will be executed.

If you run this code, and look in the console log, you'll see that it waits 3 seconds before the callback is triggered.

You could also pass an anonymous function as the first param (instead of a 'named' function), like so:

setTimeout(() => console.log("Hello Again"), 3000);

Before moving on, comment out the previous code samples so that we don't clutter up the console log with messages.

Now let's use setTimeout() to simulate something that takes a long time for a program to execute, like reading a large file. Add this function to your sample file:

const readLargeFile = () => {
	console.log("Starting to read large file...");
	setTimeout(() => console.log("Finished reading large file"), 3000)
}

This function will take 3 seconds (3000 milliseconds) to simulate reading a large file.

Now add this code, and notice the order in which it executes:

readLargeFile();
console.log("Blah!");

When you load the page in the browser, and look at the console log, you'll see that 'Blah!' appears before the program finishes reading the large file.

JavaScript can do operations that take a long time in the background (asynchronously), which allows your program to continue running. A synchronous language, such as Java, would force the program to wait until the
readLargeFile() finishes before it runs the code that logs 'Blah!' (side note: you can make Java code run asynchronously by using 'threads', which is a topic that we don't need to get into).

Asynchronous code execution prevents your program from being 'blocked' and unresponsive, while it executes long running operations. When your program's UI is blocked, then it becomes unresponsive, which means that a user won't be able to do things like clicking on a button, or scrolling the browser window. This is a great feature, becuase users hate it when the UI stops working, even for a short time.

But the down side of working with asynchronous code is that it can be difficult to deal with. For example, if you wanted to do something after readLargeFile() is done executing (such as work with the contents of the large file), then you'd have to design it to take a callback as a parameter, like so:

const readLargeFileWithCallback = (callback) => {
	console.log("Starting to read large file...");
	setTimeout(() => {
		console.log("Finished reading large file")
		const fileContent = "This is the large file......";
		callback(fileContent);
	}, 3000)
}

readLargeFileWithCallback((content) => console.log("FILE CONTENT:" + content));

As you can see, working with asynchronous code and callbacks can be a little messy, but it's important not to block/freeze the app.

It's important to note that asynchronous functions normally do not return a value. Instead, the result of the function would be passed into the callback (notice callback(fileContent) in the above code sample.

The code for a synchronized language would like this:

// don't run this code, it won't work properly!
const fileContent = readLargeFile(); // this would 'block' the program until it returns the file content
console.log("FILE CONTENT:" + content);
console.log("Blah");

This is much easier to read, because the code is synchronized, meaning that it runs in the order that it appears (line by line, from top to bottom). But, as mentioned, the readLargeFile() function would 'block' the program until it completes.

Callback Hell

If you have a lot a asynchronous code in your program, it can lead to what developers have called 'callback hell'.

As as example, assume that you are working with 3 asynchronous functions like these:

function doHomework(callback){
	console.log("Doing homework...");
	setTimeout(callback, 3000);
}

function brushTeeth(callback){
	console.log("Brushing teeth...");
	setTimeout(callback, 3000);
}

function sleep(callback){
	console.log("Sleeping...");
	setTimeout(callback, 3000);
}

As you can see, each of these functions will take 3 seconds before the callback is triggered.

Now assume that you need to wait for one function to complete before executing the next one. You could these functions like so:

doHomework(() => {
	console.log("done with homework!")
	brushTeeth(() => {
		console.log("done brushing teeth!")
		sleep(() => {
			console.log("done sleeping!")
		});
	})
})

A program with a lot of asynchronous code can be difficult to read, understand, and work with.

Because of this, JavaScript introduced promises to make it easier to work with asynchronous code. For more on promises, checkout this link