Introduction
JavaScript has made considerable strides in the development world, with the addition of new and exciting features and frameworks. It’s rapid growth and popularity have made it an up-and-coming contender for any application stack.
What is Asynchronous development?
Recently, I have been writing code for small NodeJS tools and wanted to share a very popular topic: asynchronous development / non-blocking code. Because JavaScript is a single-threaded programming language, it relies on its call stack to perform operations. Much like a queue at a supermarket, only imagine one till open, chaos! JavaScript, however, has a great way of managing these operations.
Keeping with our supermarket analogy, when an operation is called in JavaScript, it’s added to the call stack, then executed and returned, much like a person going to a till, paying for their groceries, then leaving the store (synchronous).
Quickly, this begins to break down as the call stack builds up, and enter asynchronous development. Asynchronous code takes statements outside of the main program flow, allowing the code after the asynchronous call to be executed immediately without waiting.
A good example is the setTimeout function which is asynchronous, notice how the log is ONE, THREE, TWO. (give it a try in the console!)
console.log('ONE!');
setTimeout(() => {
console.log('TWO!');
}, 2000);
console.log('THREE!');
// logs out: ONE! THREE! TWO!
What exactly is happening here?
Although Asynchronous implementations vary across browsers and NodeJS the concept is fairly similar I will be focusing on the NodeJS event driven implementation.
The first line console.log('ONE!');
is added to the call stack, then executes logging ‘ONE!’ the setTimeout() does not have any functions but rather has a function argument known as a callback, it gets added to the call stack and then popped out immediately (not logging anything). console.log('THREE!');
then gets executed and logs out ‘THREE!’.
Now the call stack is empty, which means that the callback we have been waiting two seconds for gets added to the call stack and executed, logging ‘TWO!’.
To better understand how the callback methodology works, we need to take a look at the event/message/callback queue. This is different from the call stack! It’s simply a list of events to be processed. When we store an event on the queue, we sometimes store a function with it. This function is what we know as the callback. A queue data structure is a first-in-first-out structure.
When an event is added to the queue it’s waiting for the call stack to be empty so it can process these events, the part that decides when the call stack is ok to receive an event is known as the event loop.
Handling Asynchronous Functions
There are many different api’s JavaScript has for developers to consume asynchronous behaviour.
Callbacks
Functions are first-class objects, which means functions are the type of “object” (give it a try typeof function () {}With this knowledge, we can treat function decorations as any other first-class object (String, Array, Number). This includes assignment to variables and passing as arguments to functions.
Callbacks are function decorations passed as an argument to another function. Since the argument passed is a declaration, the function it was passed to can decide when it is executed with the parenthesis ()
.
Callback functions are often used to declare some code to be executed after a lengthy operation or event has been fulfilled. This makes them perfect for use with asynchronous development.
This is particularly common in the jQuery library and a vital part of the way we develop in node.
$.each([1,2,3,4,5], function (value) {
console.log(value);
});
Callbacks do, however, have a fundamental problem when it comes to making several asynchronous calls one after the other. In the programming world, this is known as callback hell
Promises
The Promise object is essentially a wrapper around an asynchronous function, by wrapping our function in this object we have access to the Promise API.
A Promise is usually invoked with a callback function which has two arguments: resolve and reject. These arguments are functions that, when called, resolve or reject the promise.
It’s up to the developer to decide precisely when to call the resolve or reject within the body of the callback function.
function readAsync() {
return new Promise((response, request) => {
fs.readdir('./', (error, filenames) => {
if (error) {
reject(error);
}
resolve(filenames);
})
})
}
The promise api is “chainable,” allowing developers to connect asynchronous functions together one after the other. This makes for better and more readable code.
When the reject function is called within the chain .catch
handles that call
getFileContents('./hello.txt')
.then((text) => doSomethingWithTheText(text))
.then((modifiedText) => writeBackToFile(modifiedText))
.catch((error) => console.log(error));
There are many third party promise libraries available for JavaScript such as Bluebird, When and Q I would advise trying them out!
ASYNC and AWAIT
ES6/ES2015 offers a whole new suite of syntax for developers to work with, two of these new features are Async and await, these two new features hope to ease the use of asynchronous programming. The purpose of async/await is to simplify the behaviour of using promises in a more synchronous manner, much like promises use structured callbacks, async/await uses a combination of generators and promises.
async function write() {
var text = await read();
console.log(text);
}