DEV Community

Cover image for Javascript fetch, retry upon failure.

Javascript fetch, retry upon failure.

YCM Jason on March 27, 2018

This post is republished since the previous one was not showing up on my profile for some reason. So recently, I bumped into a situation where t...
Collapse
 
kevinfilteau profile image
Kevin Filteau • Edited

Liquid syntax error: 'raw' tag was never closed

Collapse
 
johnnyshrewd profile image
Johnny Shrewd

Perhaps add a check for a value smaller than 1 to avoid the infinite cycle:

try {
 ....
} catch (err) {
     if (retry === 1 || retry < 1) throw err;
          return await this.johnny(arg, retry - 1);
     }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lonniebiz profile image
Lonnie Best

Thanks! Your fetch_retry function saved the day on a bug I was encountering in deno:
github.com/denoland/deno/issues/13...

Collapse
 
webarter profile image
webarter

If someone in 2021 trying the Primitive version or ES6 version, for some reason the execution never gets to catch unless you add the processing of the success before it: so instead of

return fetch(url, options).catch(function(error)

you absolutely necessarily need to do
return fetch(url, options).then(res => res.json()).catch(function(error)

or whatever processing of response you want to do in that .then()
you can still do next .then down the line from what fetch_retry returns you afterwards

But without this then fetch_retry doesn't actually attempt any retries as it never gets to catch, even though it got 500 server error in my case.

Collapse
 
geekykidstuff profile image
Christian Pasquel

This is a very useful and perfectly written post. It not only provides an end result but also teaches you by showing the steps one would follow to write this function and improve it after some iterations, so you learn a way of thinking that can be applied to other functions you may want to write. Thank you for the post!

Collapse
 
norfeldtabtion profile image
Lasse Norfeldt • Edited

I gave it a swing with deno and came up with this:

fetchWithRetry.ts

type Parameters = {
  url: string
  numberOfRetries?: number
  print?: boolean
  dev?: boolean
}

export async function fetchWithRetry({
  url,
  numberOfRetries = 3,
  print,
  dev,
}: Parameters): Promise<Response> {
  const retriesLeft = numberOfRetries - 1
  print && console.log(`Retries left: ${retriesLeft}, url: ${url}`)

  if (retriesLeft == 0) return Promise.reject('Too many attemps without luck')

  return await fetch(url).catch(async (error) => {
    if (numberOfRetries !== 0) {
      await fetchWithRetry({
        url: !dev ? url : url.slice(0, -1),
        numberOfRetries: retriesLeft,
        print,
        dev,
      })
    }
    return error
  })
}

if (import.meta.main) {
  console.log('main')

  const successUrl = 'https://www.google.com'
  const failureUrl = 'https://www.googsdsdsdsdle.com'
  const partialFailingUrl = 'https://www.google.com1'

  console.log(successUrl)
  await fetchWithRetry({ url: successUrl, print: true })

  console.log(failureUrl)
  await fetchWithRetry({ url: failureUrl, print: true }).catch(() => {})

  console.log(partialFailingUrl)
  await fetchWithRetry({ url: partialFailingUrl, print: true, dev: true })
}

Enter fullscreen mode Exit fullscreen mode
deno run --allow-net fetchWithRetry.ts
Enter fullscreen mode Exit fullscreen mode

The short version of it

type Parameters = {
  url: string
  numberOfRetries?: number
}

export async function fetchWithRetry({
  url,
  numberOfRetries = 3
}: Parameters): Promise<Response> {
  const retriesLeft = numberOfRetries - 1
  if (retriesLeft == 0) return Promise.reject('Too many attemps without luck')

  return await fetch(url).catch(async (error) => {
    if (numberOfRetries !== 0) {
      await fetchWithRetry({
        url,
        numberOfRetries: retriesLeft,
      })
    }
    return error
  })
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
benarambide profile image
Benjamin Arambide

So usefull, thanks to share this

Collapse
 
gabrielmmsestrem profile image
Gabriel Mauricio Melgarejo Sestrem

Thanks Jason. That's my version based on your example:

  const fetchWithRetry = (url) => {
    return new Promise((resolve, reject) => {
      let attempts = 1;
      const fetch_retry = (url, n) => {
        return fetch(url).then(resolve).catch(function (error) {
        if (n === 1) reject(error)
        else
        setTimeout(() => {
          attempts ++
          fetch_retry(url, n - 1);
        }, attempts * 3000)
      });
    }
      return fetch_retry(url, 5);
    });
  }

Then I can call:

fetchWithRetry(api/data/${guid}).then(requestProcess)

Collapse
 
gabrielmmsestrem profile image
Gabriel Mauricio Melgarejo Sestrem

Hi, can I add the retries to the options?

fetch(url, {
retries: 3,
retryDelay: 1000
})
.then(function(response) {
return response.json();
})
.then(function(json) {
// do something with the result
console.log(json);
});

Collapse
 
enoch1017 profile image
enoch1017

Hello, I am a beginner of javascript . After watching your blog about promise ,especially this part-->
.....
.catch(function(error) {
fetch_retry(url, options, n - 1)
.then(resolve) // <--- simply resolve
.catch(reject); // <--- simply reject
})

I don't understand why resolve & reject can be included in then & catch functions directly... Is anything omitted?
Could you tell me why or where I can reference about this issue?
Sorry for my poor English.
I truely appreciate for your time.

Collapse
 
ycmjason profile image
YCM Jason

Thanks for your comments! I am not sure if I understand your question correctly.

But it sounds like you are confused with the idea of "functions as first-class citizens", which is a fancy way of saying you can pass functions as a values around.

Are you familiar with setTimeout? Have a read about it here.

Consider

setTimeout(function() {
  print('hello')
}, 3000);

We are passing a function function () { print('hello'); } to setTimeout as the first argument. So if we call that function f, then we can equivilantly pass f to setTimeout.

const f = function () {
  print('hello');
}
setTimeout(f, 3000);

So in another words, my code:

.catch(function(error) {
  fetch_retry(url, options, n - 1)
    .then(resolve)
    .catch(reject);
})

Is basically equivalent to:

.catch(function(error) {
  fetch_retry(url, options, n - 1)
    .then(() => resolve())
    .catch(() => reject());
})

It is also important to note that the following is incorrect (or not the same):

.catch(function(error) {
  fetch_retry(url, options, n - 1)
    .then(resolve())
    .catch(reject());
})

Because Javascript will first evaluate resolve() and use the value it returns to pass on to then.

I hope I did help a little.

Collapse
 
raneshu profile image
Ranesh • Edited

In the last line, do we really need to return await ...? i.e.

return await fetch_retry(url, options, n - 1);

?

Can we simply

return fetch_retry(url, options, n - 1);

?

Collapse
 
ycmjason profile image
YCM Jason

You can omit the await. I like putting await because it reminds me that I am dealing with async functions and direct values instead of Promises. Although async/await are just playing with promises at the end of the day.

Collapse
 
ben profile image
Ben Halpern

Sorry about the issue. We know all about what the problem was. Slighly less about what the solution is yet. We'll have it fixed soon, promise.

Collapse
 
ycmjason profile image
YCM Jason

It's alright, copy and pasting isn't very hard. Only thing is that I lost my comments and likes. I have set my previous post to unpublished in case you guys fixed it and two articles show on my profile. haha