Simple retrier of async function I wrote a simple function for retrying the passed function in case it throws.
I did it for my app that needs to call an external API through the network, which may spuriously fail.
export async function retry(fn, retries, delayMs, onFailedAttempt = null) {
    let lastError = null;

    for (let attempt = 0; attempt &lt;= retries; attempt++) {
        if (attempt &gt; 0) {
            await delay(delayMs);
        }

        try {
            const res = await fn();
            return res;
        } catch (error) {
            if (onFailedAttempt) {
                onFailedAttempt(error, attempt, retries, delayMs);
            }

            if (window.verbose) {
                console.debug(`Trial ${attempt + 1}/${retries} failed. Retrying in ${delayMs}ms...`);
            }

            lastError = error;
        }
    }

    throw lastError;
}

This seems to work, do you folks think I have overlooked any corner case?
Your code looks good and will work well. I have made some minor improvements to improve readability and reduce redundancy.
export async function retry(fn, retries, delayMs, onFailedAttempt) {
  let attempt = 0;

  while (true) {
    try {
      return await fn();
    } catch (error) {
      if (onFailedAttempt) {
        onFailedAttempt(error, attempt, retries, delayMs);
      }

      if (window.verbose) {
        console.debug(`Trial ${attempt + 1}/${retries} failed. Retrying in ${delayMs}ms...`);
      }

      if (attempt++ &lt; retries) {
        await delay(delayMs);
      } else {
        throw error;
      }
    }
  }
}

Improvements:

onFailedAttempt = null in the function signature is redundant. If onFailedAttempt is not passed as an argument, its value will be undefined. Which will work equally well in your case when checking its value.

Moving await delay(delayMS) in catch block to keep related code together. As we only delay when an error occurs and if this was not the last attempt.

const res = await fn(), res is not needed here. You can simply do return await fn().

We could use the do...while here, but we'll have to check the attempt &lt; retries condition two times in that situation. By using while (true), we only have to check it once.

We can also remove the lastError variable. If we get an error and this was the last attempt, we simply throw error.