JavaScript Goes Asynchronous (and It’s Awesome)
Key Takeaways
- JavaScript’s evolution has seen vast improvements in handling asynchronous code, particularly through ECMAScript 5, 6, and 7.
- ECMAScript 5 uses callbacks for asynchronous programming, which can lead to complexity due to the non-linear execution order inherent in asynchronous code.
- ECMAScript 6 introduces promises, which make asynchronous operations appear synchronous, improving the readability and maintainability of code. These promises are now provided by the browser, eliminating the need for an additional library.
- ECMAScript 7 offers async functions, which are syntax sugar to improve the language-level model for writing asynchronous code. These functions work directly with promises and can make asynchronous code look like regular synchronous code with a linear execution path.
First stop: ECMAScript 5 – Callbacks city
ECMAScript 5 (and previous versions as well) are all about callbacks. To better picture this, let’s have a simple example that you certainly use more than once a day: executing a XHR request.
var displayDiv = document.getElementById("displayDiv");
// Part 1 - Defining what do we want to do with the result
var processJSON = function (json) {
var result = JSON.parse(json);
result.collection.forEach(function(card) {
var div = document.createElement("div");
div.innerHTML = card.name + " cost is " + card.price;
displayDiv.appendChild(div);
});
}
// Part 2 - Providing a function to display errors
var displayError = function(error) {
displayDiv.innerHTML = error;
}
// Part 3 - Creating and setting up the XHR object
var xhr = new XMLHttpRequest();
xhr.open('GET', "cards.json");
// Part 4 - Defining callbacks that XHR object will call for us
xhr.onload = function(){
if (xhr.status === 200) {
processJSON(xhr.response);
}
}
xhr.onerror = function() {
displayError("Unable to load RSS");
}
// Part 5 - Starting the process
xhr.send();
Established JavaScript developers will note how familiar this looks since XHR callbacks are used all the time! It’s simple and fairly straight forward: the developer creates an XHR request and then provides the callback for the specified XHR object.
In contrast, callback complexity comes from the execution order which is not linear due to the inner nature of asynchronous code:

Second stop: ECMAScript 6 – Promises city
ECMAScript 6 is gaining momentum and Edge is has leading support with 88% coverage so far. Among a lot of great improvements, ECMAScript 6 standardizes the usage of promises (formerly known as futures). According to MDN, a promise is an object which is used for deferred and asynchronous computations. A promise represents an operation that hasn’t completed yet, but is expected in the future. Promises are a way of organizing asynchronous operations in such a way that they appear synchronous. Exactly what we need for our XHR example. Promises have been around for a while but the good news is that now you don’t need any library anymore as they are provided by the browser. Let’s update our example a bit to support promises and see how it could improve the readability and maintainability of our code:
var displayDiv = document.getElementById("displayDiv");
// Part 1 - Create a function that returns a promise
function getJsonAsync(url) {
// Promises require two functions: one for success, one for failure
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => {
if (xhr.status === 200) {
// We can resolve the promise
resolve(xhr.response);
} else {
// It's a failure, so let's reject the promise
reject("Unable to load RSS");
}
}
xhr.onerror = () => {
// It's a failure, so let's reject the promise
reject("Unable to load RSS");
};
xhr.send();
});
}
// Part 2 - The function returns a promise
// so we can chain with a .then and a .catch
getJsonAsync("cards.json").then(json => {
var result = JSON.parse(json);
result.collection.forEach(card => {
var div = document.createElement("div");
div.innerHTML = `${card.name} cost is ${card.price}`;
displayDiv.appendChild(div);
});
}).catch(error => {
displayDiv.innerHTML = error;
});
You may have noticed a lot of improvements here. Let’s have a closer look.
Creating the promise
In order to “promisify” (sorry but I’m French so I’m allowed to invent new words) the old XHR object, you need to create a Promise object:
Using the promise
Once created, the promise can be used to chain asynchronous calls in a more elegant way:
- Get the promise (1)
- Chain with the success code (2 and 3)
- Chain with the error code (4) like in a try/catch block
Terminus: ECMAScript 7 – Asynchronous city
Finally, we’ve reached our destination! We are almost in the future, but thanks to Edge’s rapid development cycle, the team is able to introduce a bit of ECMAScript 7 with async functions in the latest build! Async functions are a syntax sugar to improve the language-level model for writing asynchronous code. Async functions are built on top of ECMAScript 6 features like generators. Indeed, generators can be used jointly with promises to produce the same results but with much more user code. We do not need to change the function which generates the promise as async functions work directly with promise. We only need to change the calling function:
// Let's create an async anonymous function
(async function() {
try {
// Just have to await the promise!
var json = await getJsonAsync("cards.json");
var result = JSON.parse(json);
result.collection.forEach(card => {
var div = document.createElement("div");
div.innerHTML = `${card.name} cost is ${card.price}`;
displayDiv.appendChild(div);
});
} catch (e) {
displayDiv.innerHTML = e;
}
})();
This is where magic happens. This code looks like a regular synchronous code with a perfectly linear execution path:

Going further
If you want more detail on how we implemented it in Chakra, please check the official post on the Microsoft Edge blog.You can also track the progress of various browsers implementation of ECMAScript 6 and 7 using Kangax’s website. Feel free also to check our JavaScript roadmap as well! Please, do not hesitate to give us your feedback and support your favorite features by using the vote button:
More hands-on with Web Development
This article is part of the web development series from Microsoft tech evangelists on practical JavaScript learning, open source projects, and interoperability best practices including Microsoft Edge browser and the new EdgeHTML rendering engine. We encourage you to test across browsers and devices including Microsoft Edge – the default browser for Windows 10 – with free tools on dev.modern.IE:- Scan your site for out-of-date libraries, layout issues, and accessibility
- Use virtual machines for Mac, Linux, and Windows
- Remotely test for Microsoft Edge on your own device
- Coding Lab on GitHub: Cross-browser testing and best practices
- Microsoft Edge Web Summit 2015 (what to expect with the new browser, new supported web platform standards, and guest speakers from the JavaScript community)
- Woah, I can test Edge & IE on a Mac & Linux! (from Rey Bango)
- Advancing JavaScript without Breaking the Web (from Christian Heilmann)
- The Edge Rendering Engine that makes the Web just work (from Jacob Rossi)
- Unleash 3D rendering with WebGL (from David Catuhe including the vorlon.JS and babylonJS projects)
- Hosted web apps and web platform innovations (from Kevin Hill and Kiril Seksenov including the manifold.JS project)
Frequently Asked Questions about JavaScript Asynchronous Programming
What is the difference between synchronous and asynchronous programming in JavaScript?
In synchronous programming, tasks are executed one after another, meaning a task must be completed before the next one starts. This can lead to blocking or delay in execution if a task takes too long to complete. On the other hand, asynchronous programming allows tasks to be executed concurrently. This means that if a task is waiting for some operation, like data from an API, it doesn’t block the execution of other tasks. JavaScript uses callbacks, promises, and async/await to handle asynchronous operations.
How does the async function work in JavaScript?
The async function in JavaScript is used to define an asynchronous function that returns an implicit Promise as its result. When the async function is called, it runs the code within the function. If the function returns a value, the Promise will be resolved with the returned value, but if the async function throws an error, the Promise will be rejected with that error.
What is the role of the await operator in asynchronous JavaScript?
The await operator is used with an async function to pause the execution of the function and wait for the Promise’s resolution or rejection. It returns the resolved value of the Promise. If the value is not a Promise, it converts the value to a resolved Promise. If the Promise is rejected, the await expression throws the rejected value.
How can I handle errors in asynchronous JavaScript?
Error handling in asynchronous JavaScript can be done using try/catch blocks within async functions. If an error is thrown within the try block, execution is immediately shifted to the catch block. If a Promise is rejected within an async function, it can be caught in the catch block.
What are callbacks in JavaScript and how are they related to asynchronous programming?
Callbacks are functions that are passed as arguments to other functions and are invoked after some operation has been completed. In JavaScript, callbacks are often used for asynchronous operations. They allow the function to be executed once the asynchronous operation is complete and a response is ready.
What are Promises in JavaScript?
Promises in JavaScript represent a completion or failure of an asynchronous operation. They return a value which is either a resolved value or a reason for failure. Promises have three states: pending, fulfilled, and rejected. They help in managing asynchronous operations and provide better error handling than callbacks.
How can I use Promises to handle multiple asynchronous operations in JavaScript?
JavaScript provides Promise.all() and Promise.race() methods to handle multiple asynchronous operations. Promise.all() waits for all promises to be resolved or for any promise to be rejected. If the returned promise resolves, it is an array of the values from the resolved promises in the same order. Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
What is the event loop in JavaScript?
The event loop is a mechanism in JavaScript that constantly checks the call stack to see if there’s any function that needs to run. While doing so, it also checks the task queue where asynchronous callbacks are placed once they’re ready to be executed. If the call stack is empty, the event loop dequeues the tasks from the task queue to the call stack to be executed.
How does JavaScript handle asynchronous operations with the help of the event loop?
JavaScript uses the event loop in conjunction with the task queue to handle asynchronous operations. When an asynchronous operation is initiated, it is sent out of the current execution and waits for the operation to complete. Once the asynchronous operation is complete, its callback function is sent to a task queue. The event loop constantly checks the call stack and the task queue. If the call stack is empty, it takes the first task from the queue and pushes it to the call stack, which then starts executing.
Can I use async/await with forEach in JavaScript?
No, forEach is not promise-aware and won’t wait for your async function to finish before moving on to the next item in the array. Instead, you can use for…of or map() with Promise.all() to handle asynchronous operations within a loop.
David Catuhe is a Principal Program Manager at Microsoft focusing on web development. He is author of the babylon.js framework for building 3D games with HTML5 and WebGL. Read his blog on MSDN or follow him on Twitter.