Implement a retrier using Async Generator
Tags: javascript, async, generator
Published: 2020-11-30 20:45:40

## Implementing a retrier

Sometimes, you need to be able to retry an operation several time, until it succeeds (or give up after several tries). There are a lot of ways to implement this.

As a base, let's use a function called `pause` allowing you to wait for some time between your tries:

```js
function pause(delay = 100) {
  return new Promise(resolve => setTimeout(resolve, delay))
}
```

## A good old for loop

Now, a simple approach to implement a retrier would be to use a classic for loop:

```js
async function retrier(operation, { attempts = Infinity, delay = 100 })
  for (let i = 0 ; i < maxAttempts ; i++) {
    const result = await operation()
    if (result) return result
    await pause(delay)
  }
```

Then you can use the retrier this way:
```js
const result = await retrier(
  () => tryOperation(),
  { attempts: 5, delay: 500 }
)
```

As much as this works, there are a few things I don't like with this approach:
* You have little control on what's happening inside of the for loop (how many time did it take to succeed?)
* You have to pass the operation as a parameter which I think feel a bit weird
* Any custom logic you would need to get executed within the loop (for instance if you have several operations) will have to get into the `tryOperation` function

Of course you could avoid creating a `retrier` function and just duplicate this for loop everywhere. But with a more and more complicated code inside of the loop, or with `break` or `continue` statements, it would become really complex.

## Generator functions

Another way to implement this is to use an Async Generator. But first, let's have a look at what is a Generator.

A Generator function is a function (what a surprise) that returns a Generator (big brain time). A Generator `yields` values which you can iterate on, using a `for of` loop for instance.

The point of a Generator is that it can build values when you need them, instead of building an array, then iterating on it for instance. Consider the following example:

```js
// function* is the way to declare a Generator
function* count() {
  let count = 0
  // yield allows you to "generate" a value
  while(true) yield i++
}
```

If you use that Generator, you can iterate forever, with a count that increases up to Infinity. Without the need to generate all numbers beforehand!

```js
for (const index of count()) { console.log(index) }
```

### Async Generators

Now what's the difference with an Async Generator? Well... It's a Generator, but async! It's all you have to know about it, really.

You will declare it the same way, but with async before the `function` keyword, then use `await` in the for loop declaration.

Here is the retrier implemented using an Async Generator:
```js
async function* retrier({ attempts = Infinity, delay = 100 }) {
  for (let i = 0; i < attempts; i++) {
    yield i
    await pause(delay)
  }
}
```

Now, if you want to use this, all you have to do is to use a for await loop:
```js
for await (const _ of retrier({ attempts: 5, delay: 500 })) {
  // This gets executed every 500ms
  // And up to 5 times!
  const result = await tryOperation()
  if (result) break
}
```

As much as I agree that it does not change "much", I think this code is easier to approach and reason about as you keep the loop, which we are used to in JavaScript.

_______________

Photo by [Jayphen Simpson](https://unsplash.com/@jayphen?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)