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