Custom fetch-retry in legacy projects

fetchRetry.js

function wait(delay) {
  return new Promise((resolve) => setTimeout(resolve, delay))
}

function fetchRetry({ url, delay = 1000, tries = 0, fetchOptions = {} }) {
  let __triesLeft = tries
  function onError(err) {
    __triesLeft = !!__triesLeft ? __triesLeft - 1 : 0

    if (!__triesLeft) throw err

    return wait(delay).then(() => fetchRetry({ url, delay, tries: __triesLeft, fetchOptions }))
  }
  return fetch(url, fetchOptions).catch(onError)
}

Usage

// --- NOTE: Target action
// 1. Try to use different IPs
const ips = ['178.248.232.203', '2.58.70.214', '159.65.25.117', getAPIBaseUrl()]
const getBaseUrl = ({ ip, url }) => (!validateAnything.hasProtocol(ip) ? `https://${ip}${url}` : `${ip}${url}`)
const getEndpoints = ({ url }) =>
  ips
    .map((ip) => getBaseUrl({ url, ip }))
    .filter((url) => validateAnything.isValidURL(url))
const endpoints = getEndpoints({ url: '/partner_api/tradein/accept' })
const abortController = new AbortController()
const tries = 20

Promise.first(
  endpoints.map((url) =>
    fetchRetry({
      url,
      delay: 30000,
      tries,
      fetchOptions: {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
        signal: abortController.signal,
      },
    })
  )
)
  .then(async (response) => {
    // QuicklyDone fulfils first

    const res = await response.json()
    if (res.ok) {
      abortController.abort()
      targetAction(res)
    } else throw new Error('/accept response is not ok!')
  })
  .catch((errs) => {
    // Arrary of rejections (if no one resolved)
    // Провалено (ips.length * tries) попыток

    // Or one main Error?
  })
// ---

validateAnything.js

const validateAnything = {
  hasProtocol(str) {
    const pattern = new RegExp('https?:\\/\\/')
    return !!pattern.test(str)
  },
  hasWWW(str) {
    const pattern = new RegExp(/www./)
    return !!pattern.test(str)
  },
  isValidURL(str) {
    const pattern = new RegExp(/(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/)
    return !!pattern.test(str)
  },
}

Polyfills

if (!Promise.first)
  // https://github.com/huruji/p-first
  Promise.first = function (e) {
    return Promise.all(
      e.map(function (e) {
        return e.then(
          function (e) {
            return Promise.reject(e)
          },
          function (e) {
            return Promise.resolve(e)
          }
        )
      })
    ).then(
      function (e) {
        return Promise.reject(e)
      },
      function (e) {
        return Promise.resolve(e)
      }
    )
  }

if (!Promise.any)
  Promise.any = (o) =>
    new Promise((i, l) => {
      var n, t, v, d, e
      let u = !1,
        c = [],
        h = 0,
        f = []
      function a(o) {
        u || ((u = !0), i(o))
      }
      function r(o) {
        f.push(o), f.length >= h && l(f)
      }
      for (let i of o) h++, c.push(i)
      for (let o of c)
        void 0 !== (null === (n = o) || void 0 === n ? void 0 : n.then) ||
        void 0 !== (null === (t = o) || void 0 === t ? void 0 : t.catch)
          ? (null === (d = null === (v = o) || void 0 === v ? void 0 : v.then((o) => a(o))) ||
              void 0 === d ||
              d.catch((o) => {}),
            null === (e = o) || void 0 === e || e.catch((o) => r(o)))
          : a(o)
    })

See also https://gitlab.com/smartprice/ringeo/-/merge_requests/1818

SmartPrice

#TCP