Executing Code in Parallel - JavaScript
Purpose?
This article is going to cover running code in parallel using JavaScript and how to measure execution time.
- How do you run code in parallel using JavaScript?
- How do you measure execution time?
Let's Jump In.
Here’s a quick example of what running code in parallel in JavaScript looks like.
const responses = await Promise.all([promise1, promise2, promise3])
for(let response of responses) {
// do something
}
This will execute promise1, promise2, and promise3 in parallel. Then, it will combine the response from each promise together into an array. Which I can then loop over.
If you're not familiar with async/await, promises, or Promise.all please check out the links to get a brief idea. Or circle back when you're ready to get some additional information on implementation details.
This is great for either writing NodeJS in the backend or regular JavaScript in the frontend. I primarily work with NodeJS and write backend API code in most projects. When writing backend NodeJS you're receiving a request then preforming some operation and then sending a response back to the client with that data. It's a 1:1 relationship between request and response. A common API would be facilitating the interaction between the client and the database.
In some cases, the backend has to handle making multiple requests to the database or external APIs. This is where performance is critical and where running requests in parallel using Promise.all really shines.
Scenario:
Imagine your client makes a request to your backend API. The request requires you to make five separate queries to your database to construct the proper response the client needs. Let's also imagine each query to your database takes 50 ms to finish.
Bad:
Make each query in sequence (synchronously). Resulting in each query waiting for the previous to finish before having a chance to execute.
let response1 = await executeQuery("SELECT * FROM table where id = 1")
let response2 = await executeQuery('SELECT * FROM table where id = 1')
...
// format responses
// respond to client
- Total estimated execution time: 250 ms (5 * 50)
Good:
Make all the queries in parallel (asynchronously). Resulting in each query firing at the same time.
let promise1 = new Promise((resolve) => resolve(executeQuery(...))
let responses = await Promise.all([promise1, promise2, promise3, ...])
for(let response of responses) {
// format responses
// respond to client
}
- Total estimated execution time: 50 ms
This is amazing. We just cut the total execution time by 5x. We also saved some lines of code, provided a cleaner implementation, and made it easier for developers in the future to maintain.
Okay so we have a general idea for what is happening. Now we can test this by using some code I wrote to measure execution time. The example will create an array of five API requests which take two seconds to finish (strictly for demonstration purposes). Using async/await and Promise.all we are able to execute all five API requests in right around two seconds. Versus what would normally take ten seconds if the API requests were made one after the other.
Test Code:
We have a function called makeRequest which will wait two seconds then respond. We have a function called process which will take an array of promises then execute them in parallel. We also time this processing using console.time and console.timeEnd which allows us to easily time the function.
Feel free to paste this code in google chrome developer tools (my preference) or JS Fiddle.
function makeRequest() {
return new Promise((resolve) => {
setTimeout(() => resolve({ 'status': 'done' }), 2000);
});
}
async function process(arrayOfPromises) {
console.time(`process`);
let responses = await Promise.all(arrayOfPromises);
for(let r of responses) {}
console.timeEnd(`process`);
return;
}
async function handler() {
let arrayOfPromises = [
makeRequest(),
makeRequest(),
makeRequest(),
makeRequest(),
makeRequest(),
];
await process(arrayOfPromises);
console.log(`processing is complete`);
}
handler();
Results:
Test #1:
process: 2002.60400390625ms
processing is complete
Test #2:
process: 2005.722900390625ms
processing is complete
Test #3:
process: 2001.069091796875ms
processing is complete
As you can see we are getting pretty close to the perfect time of two seconds on each run. This is a great way to speed up your JavaScript whether you are writing client side code or server side code.
Conclusion:
Running code in parallel can be made easy using Promise.all and testing code execution time can be easily done using console.time. If you have any questions please feel free to ping me or comment below. Feel free to take the code above and use it to test other things as well!
Ryan Jones
Founder - Serverless Guru
Medium - @serverlessguru
Twitter - @serverlessgurux
Thanks for reading :)