withRetry

this blogpost goes through the many versions of the same thing, with promises and without, it's quite illuminating to compare them to deepen your understanding of async, await and promises.

the best one (personally) so far, taken from the blogpost:

const fetch_retry = (url, options, n) =>
  fetch(url, options).catch(function(error) {
    if (n === 1) throw error
    return fetch_retry(url, options, n - 1)
  })

some amazing tidbits:

rewritten to accommodate all async functions

const withRetry = (func, n) =>
  func().catch(err => {
    if (n === 1) throw error
    return withRetry(func, n - 1)
  })

rewritten again to accommodate a delay timer / max retries

const withRetry = (func, retries) =>
  func().catch(async err => {
    if (retries && retries < 1) throw err
    retries && console.error('withRetry will throw in', retries)

    const delay = t => new Promise(resolve => setTimeout(resolve, t))
    
    // do something, like log stuff
    console.error('failed!', err)

    await delay(200)
    return withRetry(func, retries && retries - 1)
  })

here's the thought process into refactoring the withRetry with a timer:

// the slow async function that errors out a few times before resolving
let times = 10
const slowFunc = () => {
  return new Promise((resolve, reject) =>
    setTimeout(() => {
      if (times === 1) resolve('slowFunc completed')

      console.log('slowFunc will fail', times, 'more times before succeeding.')
      times -= 1
      reject('slowFunc rejecting')
    }, 100)
  )
}

// original ish implementation
const withRetry0 = (func, n) => {
  return new Promise((resolve, reject) => {
    func()
      .then(result => {
        console.log('resolved!', result)
        resolve(result) // resolve when suceeded
      })
      .catch(err => {
        // do something, like log stuff
        console.error('failed!', err)

        setTimeout(() => {
          console.error('retrying! in 200ms for', times, 'more time(s)')
          resolve(withRetry0(func, n - 1)) // returns resolved function when it ever suceeds
        }, 200)
      })
  })
}

//withRetry0(slowFunc).then(res => console.log("withRetry completed!", res));

// cleanup
const withRetry2 = (func, n) => {
  return new Promise((resolve, reject) =>
    func()
      .then(resolve)
      .catch(err => {
        // do something, like log stuff
        console.error('failed!', err)

        setTimeout(() => {
          console.error('retrying! in 200ms for', times, 'more time(s)')
          resolve(withRetry2(func, n - 1)) // returns resolved function when it ever suceeds
        }, 200)
      })
  )
}

//withRetry2(slowFunc).then(res => console.log("withRetry2 completed!", res));

// more cleanup
// - i actually don't need to return a new Promise, as the async function already returns a promise
// - i also don't need to call .then(), because if it succeeds, it resolves outside (also, since i've taken out the promise, i can't run a resolve callback anyway)
// - however this means setTimeout needs a defined resolver
const withRetry3 = (func, n) =>
  func().catch(async err => {
    // do something, like log stuff
    console.error('failed!', err)

    return new Promise((resolve, reject) =>
      setTimeout(resolve(withRetry3(func, n - 1)), 200)
    )
  })

//withRetry3(slowFunc).then(res => console.log("withRetry3 completed!", res)); // retry only 3 times and then throw error

const withRetry4 = (func, n) =>
  func().catch(async err => {
    // do something, like log stuff
    console.error('failed!', err, n)
    if (n < 1) throw err

    return new Promise((resolve, reject) =>
      setTimeout(resolve(withRetry4(func, n - 1)), 200)
    )
  })

// withRetry4(slowFunc, 3)
//   .then(res => console.log("withRetry4 completed!", res))
//   .catch(err => console.error("withRetry4 rejected as expected!", err)); // retry only 3 times and then throw error

// with help from: https://stackoverflow.com/questions/39538473/using-settimeout-on-promise-chain
const withRetry5 = (func, n) =>
  func().catch(async err => {
    const delay = t => new Promise(resolve => setTimeout(resolve, t))
    if (n < 1) throw err // throw when fail, pass when undefined
    // do something, like log stuff
    console.error('failed!', err)
    n && console.error('withRetry5 will throw in', n)

    await delay(200) // dunno why, if you .then() this it somehow succeeds
    return withRetry5(func, n - 1)
  })

// withRetry5(slowFunc)
//   .then(res => console.log("withRetry5 completed!", res))
//   .catch(err => console.error("withRetry5 rejected as expected!", err)); // retry only 3 times and then throw error

// withRetry5(slowFunc, 3)
//   .then(res => console.log("withRetry5 completed!", res))
//   .catch(err => console.error("withRetry5 rejected as expected!", err)); // retry only 3 times and then throw error

// final function
const withRetry = (func, n) =>
  func().catch(async err => {
    const delay = t => new Promise(resolve => setTimeout(resolve, t))
    if (n < 1) throw err

    // do something, like log stuff
    console.error('failed!', err)
    n && console.error('withRetry5 will throw in', n)

    await delay(200)
    return withRetry(func, n - 1)
  })