Skip to content

Retry ​

If you want to retry a task that had failed a couple of times more, you can use the retry property in the Task.

v3.4.0 #303

Example

You can find the related examples here.

Usage ​

ts
import { delay, Listr } from 'listr2'

interface Ctx {
  output: string
}

const tasks = new Listr<Ctx>(
  [
    {
      title: 'Some type errors',
      task: async(_, task): Promise<void> => {
        await delay(1000)
        task.output = 'test'

        await delay(1000)
        throw new Error('This type can not be assigned to type with, oh noes')
      },
      retry: 3
    }
  ],
  {
    concurrent: false,
    exitOnError: true
  }
)

const context = await tasks.run()

Retry Delay ​

v6.0.0 #668

Retry action can have a delay between the tries. For enabling this behavior, you can pass the retry to the given task as an object.

ts
import { delay, Listr } from 'listr2'

interface Ctx {
  output: string
}

const tasks = new Listr<Ctx>(
  [
    {
      title: 'Some type errors',
      task: async(_, task): Promise<void> => {
        await delay(1000)
        task.output = 'test'

        await delay(1000)
        throw new Error('This type can not be assigned to type with, oh noes')
      },
      retry: {
        delay: 1000,
        tries: 3
      }
    }
  ],
  {
    concurrent: false,
    exitOnError: true
  }
)

const context = await tasks.run()

While waiting between attempts the task enters a paused state, and the DefaultRenderer shows a live countdown until the next try through its pausedTimer option. You can check for this state inside a task with task.isPaused().

Dynamic Retry Delay ​

#791

The delay can also be a callback that returns the time to wait in milliseconds, which makes it possible to implement backoff strategies like an exponential backoff. The callback will be evaluated before every retry with the current retry attempt, where the count is the number of the failed attempt and error is the error it has thrown, and the context. It can also return a Promise.

ts
import { delay, Listr } from 'listr2'

const tasks = new Listr(
  [
    {
      title: 'Some type errors',
      task: async(): Promise<void> => {
        await delay(1000)
        throw new Error('This type can not be assigned to type with, oh noes')
      },
      retry: {
        tries: 3,
        delay: ({ count }): number => Math.min(1000 * 2 ** (count - 1), 30000)
      }
    }
  ],
  {
    concurrent: false,
    exitOnError: true
  }
)

await tasks.run()
  • If the callback throws, the task fails with that error without retrying further.
  • If the run is interrupted while the callback is pending, the task does not wait for it and stops with the error of the failed attempt.

Fail Without Retrying ​

#791

Whenever the task knows that retrying will never succeed, it can fail immediately by returning task.fail(error). The task fails with the given error through the regular error handling, which means rollback, exitOnError and error collection behave the same as an error that has been thrown in the last attempt, but the remaining retries are not used.

ts
import type { ListrTaskFailure } from 'listr2'
import { delay, Listr } from 'listr2'

const tasks = new Listr(
  [
    {
      title: 'Clean storage',
      task: async(_, task): Promise<ListrTaskFailure> => {
        await delay(1000)

        if (task.isRetrying().count > 0) {
          return task.fail(new Error('Storage is locked by another process.'))
        }

        throw new Error('Storage is not ready yet.')
      },
      retry: {
        tries: 10,
        delay: 1000
      }
    },
    {
      title: 'This task will still run',
      task: async(): Promise<void> => {
        await delay(3000)
      }
    }
  ],
  {
    concurrent: true,
    exitOnError: false
  }
)

await tasks.run()
  • The value of task.fail() has to be returned from the task, calling it without returning it will not fail the task. Throwing it, i.e. throw task.fail(error), behaves the same as returning it.
  • The given error is passed through as it is, without any validation or wrapping.
  • It only applies to the task that calls it. When a subtask fails itself and its error fails the parent task, the parent task still uses its own retries as usual.
  • Unlike task.skip(), the task ends up in the failed state, and unlike task.cancel(), it does not interrupt the other tasks.

Retry Event ​

Retrying is self-aware, and you can access the task if it is retrying via task.isRetrying(). It will either return an object with the given interface where the count will be 0 for not repeating tasks, and error is the last encountered error if retrying.

Retry Count ​

ts
import { delay, Listr } from 'listr2'

const tasks = new Listr(
  [
    {
      title: 'Some thing with errors',
      task: async(_, task): Promise<void> => {
        const retry = task.isRetrying()

        if (retry.count > 0) {
          task.title = 'This means I am retrying.'
          task.output = ['I am self aware that I am retrying for the %dth time.', retry.count]
        }

        await delay(1000)
        throw new Error('This type can not be assigned to type with, oh noes')
      },
      retry: 3
    }
  ],
  { exitOnError: false }
)

await tasks.run()

Last Error ​

ts
import { delay, Listr } from 'listr2'

const tasks = new Listr(
  [
    {
      title: 'Some thing with errors',
      task: async(_, task): Promise<void> => {
        const retry = task.isRetrying()

        if (retry.count > 0) {
          // eslint-disable-next-line no-constant-binary-expression
          if (retry.error === new Error('Something')) {
            task.title = 'I will process the task further.'
          }
        }

        await delay(1000)
        throw new Error('This type can not be assigned to type with, oh noes')
      },
      retry: 3
    }
  ],
  { exitOnError: false }
)

await tasks.run()

Renderer ​

  • When retrying, the task title will be reset to the original task title.

DefaultRenderer ​

Details

suffixRetries? ​

optional suffixRetries?: boolean

Defined in: packages/listr2/src/renderer/default/renderer.interface.ts:139

Suffix retry messages to clearly indicate the task is currently retrying.

  • true will add [RETRY:COUNT] as a suffix.
  • false will not add a suffix.

Default Value ​

false


pausedTimer? ​

optional pausedTimer?: PresetTimer

Defined in: packages/listr2/src/renderer/default/renderer.interface.ts:148

Show duration for the pauses.

Default Value ​

SimpleRenderer & VerboseRenderer ​

The non-TTY renderers log a RETRY entry with the current attempt count on each retry, rather than resetting an in-place title.