By design, JavaScript is a synchronous scripting language. In fact, many of the widely used asynchronous functions in JavaScript are not part of the core language. Understanding how asynchronous features work in the JavaScript ecosystem, including the role played by external APIs, is an essential part of using the language effectively.
This post will show you how to use asynchronous functionality in JavaScript. It’s also going to explain an important difference between the way code execution works in JavaScript and other languages and frameworks. Understanding this difference is essential to understanding how to do asynchronous programming with JavaScript.
Understanding JavaScript asynchronous code
JavaScript programs rely on a JavaScript runtime environment for execution. Browsers, like Chrome and Firefox, and application servers, like Node.js, include a runtime environment as part of the application. This enables websites to run JavaScript in client browsers and standalone JavaScript applications to run in Node.js.
While Chrome and Node.js use the Google V8 JavaScript engine, Firefox uses its own engine, SpiderMonkey. As part of Microsoft’s re-engineering of the Edge browser, it will also use the V8 JavaScript engine. The two engines differ in how they work internally and produce different performance results in benchmarks, but they can be regarded as substantially the same from the developer’s perspective.
In many execution environments, such as Java application servers, .NET, and Ruby on Rails, speed, scalability, and throughput are handled by spawning new threads of execution. This is how many web servers respond dynamically to changing traffic volume, but it comes with overhead costs in thread scheduling and context switching.
JavaScript uses a single-threaded, nonblocking, event loop to provide concurrency. Rather than rely on thread management to manage multiple I/O tasks, JavaScript engines use events and callbacks to handle asynchronous requests. In the case study code below you'll see the JavaScript event loop in action and you'll get first-hand experience using the callback queue.
The event and callback structure is the fundamental mechanism by which JavaScript engines are able to efficiently handle overlapping tasks, like responding to I/O requests. This is why understanding JavaScript callbacks is essential to understanding asynchronous programming in JavaScript.
In the client browser callbacks enable JavaScript code to make a call that might take a long time to respond, like a web service API call, without “freezing” the web page while it waits for a response. (There’s plenty of bad JavaScript code that does that anyway.)
In server-side applications, like those running on Node.js, it’s the event and callback structure that enables JavaScript to be used effectively for applications that inherently require a lot of asynchronous multitasking, like web servers.
Because JavaScript is a synchronous language by design, and because the event loop is implemented in the JavaScript engines that are part of browsers and application servers, all of the asynchronous functionality in JavaScript is implemented in external libraries. In fact, even commonly used asynchronous functions like setTimeout()
are provided by libraries and interfaces.
JavaScript’s reliance on external libraries for I/O activities such as file system and network access originates in the language’s origins as a scripting language for websites. Access to the DOM and functions for manipulating it are built in to the language. As the use of JavaScript expanded into more areas, such as server-side functionality with Node.js, external libraries provided the functionality necessary to perform the I/O and asynchronous functions for those roles.
In this post you’ll learn about basic asynchronous functionality in JavaScript by building your own callbacks, and you'll examine the contents of the event loop. You’ll also see how callbacks can be chained together to execute a sequence of events that manipulate the results of the preceding call when they become available.
Prerequisites
To accomplish the tasks in this post you will need the following:
- Node.js and npm (The Node.js installation will also install npm.)
- Git (if you'd like to use source code control)
You should also have a basic understanding of JavaScript.
There is a companion repository for this post available on GitHub.
Initializing the project
Begin your exploration of concurrency in JavaScript by initializing a new Node.js project. You can initialize the project and its Git repository with a series of command line instructions.
Open a console window and execute the following instructions in the directory where you want to create the project directory: