Simple retrier of async function
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.