How to create a promise-based retry function in vanilla JavaScript?

I’m working on a web application where I need to make API requests. It needs to be robust and assume it can fail because of network or sever errors. I want to have a re-try function that continues making a certain number of calls before it stops.

I need to improve this function so it can handle 2 cases. 1) Cancellation. Suppose the user closes the browser or navigates to a different part of the page. The current function doesn’t have way to cancel ongoing retries. 2) Handle certain error codes. Suppose you get a HTTP 4xx error code. There is no point in retrying because it could be unauthorized (401) or forbidden (403). But, it should retry for 5xx error codes. My current function doesn’t provide a way to tell the difference between different error codes.

Here is the minimal reproducible example I have for you:

function retry(fn, retries = 3, delay = 1000) {
    return new Promise((resolve, reject) => {
        let attempt = (n) => {
            fn().then(resolve)
              .catch((error) => {
                if (n === 0) {
                    reject(error)
                } else {
                    setTimeout(() => {
                        
                    }, delay); // 1 second delay default
                }
              });
        };
        attempt(retries);
    })
}

// Test
let fetchWithRetry = () => retry(() => fetch("https://jsonplaceholder.typicode.com/todos/95"));

fetchWithRetry()
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Failed to fetch data:', error));

I found another Stack Overflow answer regarding the retry logic but it does not fully address the need for cancellation and specific HTTP error codes. Promise Retry Design Patterns