Optimizing JavaScript async functions is key to boosting the performance of your web apps. Let me walk you through some tips and tricks, and make them sound less like a robot, more like a human!
Async functions let you write promise-based code that looks and feels like regular synchronous code. It's all thanks to the async
and await
syntax. This makes your code easier to read and handle errors compared to older promise chains or callbacks.
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
await
CallsSequential await
calls might be straightforward but they can slow things down by making operations wait for each other without reason.
// Bad Example (Sequential Execution, don't do this!)
async function getUserData() {
const userDetails = await fetchUserDetails();
const userPosts = await fetchUserPosts();
const userComments = await fetchUserComments();
return { userDetails, userPosts, userComments };
}
// Good Example (Parallel Execution, much better!)
async function getUserData() {
const [userDetails, userPosts, userComments] = await Promise.all([
fetchUserDetails(),
fetchUserPosts(),
fetchUserComments()
]);
return { userDetails, userPosts, userComments };
}
Using Promise.all
lets you run multiple async tasks side-by-side, which can shave off significant time.
Promise.allSettled
for Independent PromisesWhen tasks don’t rely on each other and their failures shouldn’t interfere, Promise.allSettled
is your friend.
async function fetchMultipleData() {
const results = await Promise.allSettled([
fetchUserDetails(),
fetchUserPosts(),
fetchUserComments()
]);
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log(result.value);
} else {
console.error(result.reason);
}
});
}
Promise.any
for Fastest Resolved PromiseNeed the quickest result from a bunch of promises? Promise.any
is the answer! It's handy when querying multiple sources for the same type of data.
async function fetchFastestData() {
const data = await Promise.any([
fetch('https://api.example.com/endpoint1'),
fetch('https://api.backup.com/endpoint1'),
fetch('https://api.cache.com/endpoint1')
]);
return data.json();
}
To cut down on network requests for frequently-accessed and rarely-changed data, the good old cache is your pal.
const cache = new Map();
async function fetchDataWithCache(url) {
if (cache.has(url)) {
return cache.get(url);
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, data);
return data;
}
For heavy computations that would otherwise block the main thread, Web Workers are amazing. They do the heavy lifting so your main thread can stay responsive.
// Main Thread Code
const worker = new Worker('worker.js');
worker.postMessage({ type: 'start', payload: largeDataSet });
worker.onmessage = function(event) {
if (event.data.type === 'result') {
console.log('Processed data:', event.data.payload);
}
};
// Inside worker.js
onmessage = function(event) {
if (event.data.type === 'start') {
const processedData = computeIntensiveTask(event.data.payload);
postMessage({ type: 'result', payload: processedData });
}
};
function computeIntensiveTask(data) {
// Perform heavy computations here
return processedResult;
}
With inputs triggering network requests, like search bars, debouncing can save you from sending too many requests too quickly.
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(async (event) => {
const query = event.target.value;
const response = await fetch(`/search?q=${query}`);
const results = await response.json();
displayResults(results);
}, 300));
Similar to debouncing, throttling makes sure a function only runs at certain intervals. It’s useful for managing regular actions like scroll events or button clicks.
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function(...args) {
const context = this;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
const button = document.getElementById('loadMoreButton');
button.addEventListener('click', throttle(async () => {
const response = await fetch('/loadMoreData');
const newData = await response.json();
displayData(newData);
}, 2000));