How to debounce and throttle async calls in JavaScript effectively?

Content verified by Anycode AI
July 21, 2024

Debouncing and Throttling

Debouncing and throttling, huh? They're two techniques to control the rate at which a function gets executed in JavaScript. Think of them as the bouncers and traffic lights of your code—making sure things don't get too wild or congested, especially with asynchronous calls that can sometimes mess up your performance if left unchecked.

Understanding Debouncing and Throttling

Debouncing? This fun little trick ensures that a function only gets called after a certain amount of time has passed since its last call. Picture a search box that waits for you to finish typing before fetching suggestions. That's your debounce at work.

Throttling, on the other hand, limits the function to running once in a given period, no matter how many times an event fires. Imagine how a window resize event shouldn't trigger a function a thousand times per second. That's throttling—keeping things calm and collected.

Getting into Debouncing

Let's dive right into how you can get debouncing set up for those asynchronous calls.

Basic Debouncing Function

Here's the starter pack for debouncing. A function fn combined with delay. It gives back a function that delays fn's execution until delay milliseconds have ticked by since the last call.

function debounce(fn, delay) {
    let timeoutID;
    return function(...args) {
        clearTimeout(timeoutID);
        timeoutID = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

Debouncing with an Async Function

The same principles apply to asynchronous functions too. Here’s a slight tweak to handle async:

function debounceAsync(fn, delay) {
    let timeoutID;
    return function(...args) {
        clearTimeout(timeoutID);
        timeoutID = setTimeout(async () => {
            await fn.apply(this, args);
        }, delay);
    };
}

Example Usage of Debounce

Say you've got an async function fetching data from an API:

async function fetchData(query) {
    const response = await fetch(`https://api.example.com/data?query=${query}`);
    const data = await response.json();
    console.log(data);
}

Debounce it like this:

const debouncedFetchData = debounceAsync(fetchData, 300);

// Hook it to an input event:
document.getElementById('searchBox').addEventListener('input', (event) => {
    debouncedFetchData(event.target.value);
});

Now for Throttling

Throttling asynchronous functions? We’ve got that covered too.

Basic Throttling Function

This ensures that a function only fires once per interval. Here’s how you do it:

function throttle(fn, delay) {
    let lastCall = 0;
    return function(...args) {
        const now = new Date().getTime();
        if (now - lastCall < delay) {
            return;
        }
        lastCall = now;
        fn.apply(this, args);
    };
}

Throttling with an Async Function

Handling async is quite similar. Just remember to consider the asynchronous nature within your throttle implementation:

function throttleAsync(fn, delay) {
    let lastCall = 0;
    return async function(...args) {
        const now = new Date().getTime();
        if (now - lastCall < delay) {
            return;
        }
        lastCall = now;
        await fn.apply(this, args);
    };
}

Example Usage of Throttle

Using that same fetchData function:

const throttledFetchData = throttleAsync(fetchData, 1000);

// Hook it to a scroll event:
window.addEventListener('scroll', () => {
    throttledFetchData('exampleQuery');
});

Combining Debounce and Throttle

Now, do you need the best of both worlds? Combine them!

Combined Implementation

Let's wrap a function with both mechanisms. This can be a bit tricky, but here goes:

function debounceAndThrottle(fn, debounceDelay, throttleDelay) {
    let debounceTimeout;
    let lastInvocationTime = 0;
    
    return function(...args) {
        const now = new Date().getTime();

        if (now - lastInvocationTime >= throttleDelay) {
            lastInvocationTime = now;
            clearTimeout(debounceTimeout);
            fn.apply(this, args);
        } else {
            clearTimeout(debounceTimeout);
            debounceTimeout = setTimeout(() => {
                lastInvocationTime = now;
                fn.apply(this, args);
            }, debounceDelay);
        }
    };
}

Example Combined Usage

Combining for our fetchData:

const combinedFetchData = debounceAndThrottle(fetchData, 300, 1000);

// Hook it to an input event:
document.getElementById('searchBox').addEventListener('input', (event) => {
    combinedFetchData(event.target.value);
});

A Few More Things to Think About

  1. Cancellation: In real-world cases, you might want to cancel those pending ops if they’re not needed anymore. AbortController can be really handy for this.

  2. Error Handling: Always try to handle errors gracefully in your async functions. Nobody likes crashes!

  3. Memory Leaks: Watch out for those sneaky memory leaks due to lingering timers. Clean up if debounced/throttled functions aren’t needed anymore.

Debouncing and throttling really can transform how responsive and efficient your applications are. With these in your toolkit, you’re set to handle asynchronous calls like a pro!

Have any questions?
Our CEO and CTO are happy to
answer them personally.
Get Beta Access
Anubis Watal
CTO at Anycode
Alex Hudym
CEO at Anycode