Here is the retrier implemented using an Async Generator:

async function* retrier({ attempts = Infinity, delay = 100 }) {
  for (let i = 0; i < attempts; i++) {
    yield i
    await pause(delay)
  }
}

HTTP request sample:

const tryHttp = ({ arg }) => {
  if (arg < 3) return Promise.reject({ ok: false, message: 'Fck u' })
  return Promise.resolve({ ok: true })
}

Retryable HTTP client sample (high level API for usage in code):

const yourHttpClient = async ({ onEarchIterator, validator }) => {

  // NOTE: Now, if you want to use this,
  // all you have to do is to use a for await loop:
  for await (const i of retrier({ attempts: 10, delay: 500 })) {
    const result = await tryHttp({ arg: i })
      .then((res) => res)
      .catch((err) => err)
    onEarchIterator(result)
    if (validator.result(result)) break
  }
}

Usage sample:

yourHttpClient({
  onEarchIterator: (res) => {
    // NOTE: We have event on each iterate
    console.log('- this is your event after request', res)
  },
  validator: {
    // NOTE: We have access to each response
    result: (res) => res.ok === true,
  },
})

See also retry-on-error-js