// Packagesconstretry=require('async-retry');constfetch=require('node-fetch');awaitretry(async(bail)=>{// if anything throws, we retryconstres=awaitfetch('https://google.com');if(403===res.status){// don't retry upon 403bail(newError('Unauthorized'));return;}constdata=awaitres.text();returndata.substr(0,500);},{retries: 5,});
API
retry(retrier : Function,opts : Object)=>Promise
The supplied function can be async or not. In other words, it can be a function that returns a Promise or a value.
The supplied function receives two parameters
A Function you can invoke to abort the retrying (bail)
A Number identifying the attempt. The absolute first attempt (before any retries) is 1.
Right now, code like this will actually throw the error 'RetryOperation timeout occurred' which originates in node-retry. It should throw the error I made ("HI" + Math.random()), but doesn't because of a timeout error.
import * as retry from 'async-retry'
async function throwsErrors(){
await delay(1000)
throw new Error("HI" + Math.random())
}
retry(
async (bail, attempt) => {
await throwsErrors()
} , {
maxRetryTime: 100,
minTimeout: 2,
randomize: true,
retries: 100,
onRetry: console.log
}
).then(console.log)
async function delay(milliseconds: number) {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
I made an issue in node-retry that describes why this happens in detail, but the gist is that if you call retryOperation.retry(err) and there's a timeout error, node-retry drops err. I'm not sure if this is something that node-retry needs to support or if it's something that you can/should handle here.
I have this situation where I download the file, and want to delete this file (async operation) if the downloaded file is corrupted and needs retry.
returnretry(async(_,attempt)=>{awaitdownloadFileFromInternet();},{onRetry: err=>{// It happens that downloaded file has an error// I want to delete the downloaded corrupted fileawaitfs.unlink(pathToCorruptedFile);},retries: 3});
Is it possible to have async-retry retry the next attempt only once the onRetry resolves (if the return value is a Promise)?
is there a way to check a thrown error before deciding to retry? I tried wrapping my await line in a try/catch & bail()/throw depending on error.message, but that's making my unit tests go nuts for reasons that aren't clear to me
I faced this today, with my repo that uses firebase-admin.
Would like to hear your comments about it. Is the use of eslintConfig in package.json supposed to be dev/internal detail? Currently (async-retry 1.3.3), It's not.
// Packagesconstretry=require('async-retry')constfetch=require('node-fetch')awaitretry(asyncbail=>{// if anything throws, we retryconstres=awaitfetch('https://google.com')if(403===res.status){// don't retry upon 403bail(newError('Unauthorized'))// return <---- don't immediately return herethrownewError('Throw after bail');}constdata=awaitres.text()returndata.substr(0,500)},{retries: 5})
Calling bail will immediately reject the promise returned by retry, but if the promise returned by the retirer function is then itself rejected, attempts will continue running in the background until e.g. the max retry count is reached.
This can be surprising and result in obscure bugs.
Are you interested in types? We made some for using your library and I can share them. I can make a pull request, otherwise I can try and toss it up on DefinitelyTyped.
I recently added async-retry to the project I'm working on because I was getting a lot of ChunkLoadError Loading chunk 6 failed in Sentry. This is how I currently use it:
onRetry: an optional Function that is invoked after a new retry is performed. It's passed the Error that triggered it as a parameter.
But it should say this:
onRetry: an optional Function that is invoked before a new retry is performed. It's passed the Error that triggered it as a parameter.
It's a small but significant difference.
I thought onRetry was useless because the docs say it runs after the retry. Then I tested and found it actually runs before the retry, which is much more useful. The docs should reflect that.
This is most likely because I am not familiar enough with promises... but, how can I pass a function in a variable and then retry it if it fails the first time?:
// works ok:awaitretry(async(bail)=>{constres=awaitfetch('https://google.com')});// does not work constFn=fetch('https://google.com')awaitretry(async(bail)=>{constres=awaitFn;// if fails first time, it is not called again});
i want to retry the http request if the response data i am receiving is empty. The backend service takes some time to process the data, it return me an empty array if it has not processed yet. The array will hold data if it is successfully processed.
I haven't followed the details of whether the type of @types/retry has changed, but I got an error that option has no "retries", and I couldn't use this package.
constretry=require('async-retry');constfetch=require('node-fetch');awaitretry(async(bail)=>{// if anything throws, we retryconstres=awaitfetch('https://google.com');if(403===res.status){// don't retry upon 403bail(newError('Unauthorized'));return;}constdata=awaitres.text();returndata.substr(0,500);},{// Not Foundretries: 5,});
Throwing an error in onRetry seems like a weird behavior to me.
constretry=require("async-retry");(async()=>{try{awaitretry((bail,attempt)=>{thrownewError(`inside error at ${attempt}`);},{onRetry: (e,attempt)=>{console.error(e.message);thrownewError(`onRetry error at ${attempt}`);},},);}catch(e){console.error(e.message);}})();
Result:
inside error at 1
onRetry error at 1
inside error at 2
/home/xxxxx/playground.js:12
throw new Error(`onRetry error at ${attempt}`);
^
Error: onRetry error at 2
at Object.onRetry (/home/xxxxx/playground.js:12:17)
at onError (/home/xxxxx/node_modules/async-retry/lib/index.js:33:17)
at RetryOperation.runAttempt [as _fn] (/home/xxxxx/node_modules/async-retry/lib/index.js:43:9)
at Timeout._onTimeout (/home/xxxxx/node_modules/retry/lib/retry_operation.js:81:10)
at listOnTimeout (node:internal/timers:557:17)
at processTimers (node:internal/timers:500:7)
I expected that it caught once without retry like:
inside error at 1
onRetry error at 1
(finish)
otherwise, with retry like:
inside error at 1
inside error at 2
inside error at 3
inside error at 4
inside error at 5
inside error at 6
onRetry error at 6
(finish)
awaitretry(asyncbail=>{// if anything throws, we retryconstres=awaitfetch('https://google.com')// IF THIS CODE THROWS THEN THE CODE UNDERNEATH WON'T EVER RUN RIGHT?// SO WHY IS THERE LOGIC TO BAIL IN THERE, BECAUSE SURELY TO RUN THAT CODE// THE PROMISE WOULD HAVE RESOLVED CORRECTLY AND THEREFORE NOT NEED// TO BE RETRIED?if(403===res.status){// don't retry upon 403bail(newError('Unauthorized'))return}constdata=awaitres.text()returndata.substr(0,500)},{retries: 5})
It would be nice to allow the called function to override the amount of time to delay between invocations, for the next invocation only. The rationale for this request is that some REST APIs will return a retry-after header for some calls indicating how long one should wait before retrying the request. There is currently no way to communicate that directive to this module.
I find myself doing this a lot when using async-retry:
awaitPromise.race(retry(/* ... */),newPromise((_,reject)=>{setTimeout(()=>{reject(newError('FAILED to complete async retry in less than 1 minute'))},60000)}),)
It would be awesome if there were a clean way to provide an option (retryTimeout?) that allowed you to say ("time out after the retry went for this long, regardless of the attempt number").
I know there's feature to bail out conditionally, but I think it would be useful to have a function that is called as soon as the operation fails, and the return value of that function should decide whether or not the next retry should happen.
So if (!op.retry(err)) { would just become something like if (!options.shouldRetry(err) || !op.retry(err)) {.
Current alternative to this would be to have a try/catch in the retrier function, decide in catch, throw if need to retry, and swallow and bail if needed. But this is a lot of boilerplate and can not be extracted into a separate function.
While having a function in option would be much easier.
I am currently trying to retry requests made to Notion. If I am rate limited, their API returns the number of seconds I should wait before retrying. I would like to use this value in onRetry (or anywhere else, though here it makes sense) to dynamically alter how long to wait before the next retry. If I am not mistaken, this kind of behaviour is not supported by async-retry at the moment.