We can implement our code for execution using two approaches that include: Synchronous code execution and asynchronous code execution.
Synchronous code execution means that the code is executed sequentially. This is attributed to the fact that the code is single-threaded, and a segment of the code uses the thread exclusively until it has finished processing while the other segments wait for the thread to be released.
The synchronous execution brings about issues to do with performance as an application can be very slow due to this approach to executing the code.
I know you might be wondering, how then do you enhance the performance of an application as JavaScript code executes synchronously by default. Asynchronous
code execution to the rescue!.
Asynchronous code execution means that other segments of the code do not have to wait for the currently executing code to release the thread before they can start executing. You can think of this as creating new threads in the application to execute other code segments in the background.
A thread is a lightweight process for a particular application. For example, A running word document is a process and lightweight process for the application can be listening for keyboard input. A collection of instructions that together form a task is another way we can use to describe a thread.
In this tutorial, we will learn how to use Promises
, Generators
, and Async
Await
to execute code asynchronously.
Using Promises
The JavaScript documentation defines a Promise as the eventual completion or failure of an asynchronous operation, and it also returns a value.
The promise has three states: the pending
state, the fulfilled
state, and the rejected
state. The pending state means that the asynchronous operation is busy processing a task behind the scenes.
The fulfilled state means that the asynchronous operation was successful, and the rejected state means that the operation failed to complete.
We will use WebStorm
development throughout this tutorial. Open the application and select File > New > Project
. On the window that opens, click Node.js
, on the Location
section, enter the project name as concurrency
and press the Create
button to create a node
application.
Create a JavaScript file named promises.js
under the root folder. Copy and paste the following code into the file.
const customers = [
{id: 1, name: 'john', email: 'john@mail.com'},
{id: 2, name: 'mary', email: 'mary@mail.com'},
{id: 3, name: 'doe', email: 'doe@gmail.com'}
]
function validEmailExists(){
let thePromise = new Promise((resolve,reject) =>{
setTimeout(() => {
let customerWithValidEmail = customers
.find((customer) => customer.email.match("@gmail.com"));
if (customerWithValidEmail !== undefined){
resolve(customerWithValidEmail);
}else {
reject("No customer with the email format")
}
}, 1000);
}).then((customer) => {
console.log(`The first user with a valid email is: ${customer.email}`);
}).catch((error) => {
console.log(error);
})
}
validEmailExists();
In this code we have defined an array of objects that are consumed by the validEmailExists()
method to find the first email that matches the email format provided.
To execute this task asynchronously, we have created a promise, and to denote that this task will execute for some time in the background, we have added a minimum time of 1
second using the setTimeout()
method.
The reason this is minimum time is that if there are other segments of the code to be executed the asynchronous operation will have to wait until the stack is empty before it is added to the stack by the event loop for processing.
The find()
method in the setTimeout()
method returns the first element that matches the provided condition and if no element is found it returns undefined
. If the condition to check whether an element was found returns valid, we invoke the resolve()
callback and pass the element as the argument of the callback.
The resolve()
callback means that the asynchronous operation was successful. If the asynchronous operation encountered an error we invoke the reject()
callback which means the asynchronous task failed.
To retrieve the value returned by the asynchronous operation, we chain the promise using a .then()
callback. This is a callback for the resolved case of the promise and since it has access to the object returned, we log the email property to the console.
The last .catch()
method is a callback for the rejected case of the promise. This callback will be invoked when the promise fails.
To run the code, open a new terminal window by pressing View > Tools Window > Terminal
. A terminal window will open at the bottom of your development environment. Use the following command to run the file.
node promises.js
The following is the output of the program:
The first user with a valid email is: doe@gmail.com
Using Async Await
The JavaScript documentation defines the async
function as a function that has the async keyword and contains zero or more await
keywords inside the function.
Note that the await
keyword can only be used within the context of the async
function, and if use it outside this context, you will get a SyntaxError
.
The async/await
is a cleaner way that we use to consume the promise-based APIs without explicitly chaining the promises.
In our project, create a new file named asyncawait.js
. Copy and paste the following code into the file.
const customers = [
{id: 1, name: 'john', email: 'john@mail.com'},
{id: 2, name: 'mary', email: 'mary@mail.com'},
{id: 3, name: 'doe', email: 'doe@gmail.com'}
]
function getCustomerNames(){
return new Promise((resolve, reject) => {
setTimeout(() => {
let customersNames = customers.map((customer) => customer.name);
resolve(customersNames);
},2000)
})
}
const logCustomerNames = async() => {
const theNames = await getCustomerNames();
console.log(theNames);
}
logCustomerNames();
This example uses the same array of objects we created in the previous example. The getCustomerNames()
method contains a task that executes asynchronously behind the scenes for 2
seconds.
The task involves using the map()
method to get all the names of the objects in the array. Once all the names are retrieved, we invoke the resolve()
callback and pass the names as the argument of the callback.
The resolved value of the promise returned by the getCustomerNames()
method is treated as the return value of the await
expression which is declared in the async
function named logCustomerNames()
.
The await expression suspends the execution until the promise returned by getCustomerNames()
method is fulfilled or rejected. This makes the promise returned by the getCustomerNames()
method behave as though it is synchronous.
To view, the values returned by the await expression, log the variable containing the values to the console. Note that this prevents us from using the .then()
chaining method as we did in the previous example.
Use the following command to run the asyncawait.js
file.
node asyncawait.js
The following is the output of the above code:
[ 'john', 'mary', 'doe' ]
Using Generators
The JavaScript documentation defines a Generator
as a function that can be exited and later re-entered, and their variable bindings are saved across re-entrances.
Generators can be used with promises and async functions as a tool for asynchronous programming to prevent problems brought about by callbacks such as Callback Hell
and Inversion of Control
.
Create a file named generators.js
under the root folder. Copy and paste the following code into the file.
const values = [100, 200, 500, 600, 300];
function getSum(lengthOfNumbersToSum){
let count = 0;
for (let i = 0; i < values.length; i++) {
if (i === lengthOfNumbersToSum){
break
}else {
count += values[i];
}
}
return count
}
function* getYield(){
yield getSum(3);
yield getSum(2);
yield getSum(4)
yield getSum(5);
}
let generator = getYield();
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
In this code, we have created an array of numbers that are consumed by the getSum()
method to find their sum. The getSum()
method computes the sum based on a specific size of the array. The size of the array to be computed is passed as defined as the parameter of this method.
Note that this argument will help us to yield different values from the generator function. The getYield()
method is a generator function denoted by the asterisk character *
.
When we invoke the getYield()
method it does not execute the body directly. The method returns an iterator()
and with this iterator, we can invoke the next()
method to execute the body.
Invoking the next()
method executes the body until the first yield
is encountered. The generator function gets suspended and an object containing the properties value
and done
is returned. The done
property is a boolean and indicates whether the generator function has yielded the last value.
The first call to our yield returns the sum of the array of size 3
, and the next time we call the next()
method, the generator function is resumed from the last point where it was suspended to return the next yield.
This process continues until the generator function encounters the return expression or an error is thrown. At this point, we say the generator function has finished and the done property of the object returned will evaluate to true.
Run the above code using the following command.
node generators.js
The following is the output of the above code.
{ value: 800, done: false }
{ value: 300, done: false }
{ value: 1400, done: false }
Conclusion
In this tutorial, we have learned how to enhance our applications with asynchronous programming. The approaches that we have covered to realize this include: Using promises
, using async/await
, and using generators
. Note that the sole purpose why we use asynchronous programming is because we do not want to wait for an instruction to complete to execute other instructions. Some tasks such as network communications and image processing are slow, and we can leverage asynchronous programming while executing these tasks in the background to enhance the performance of our application.