Error Handling ​
Exceptions that occur while running the Task will be handled internally through Listr. You can throw errors out of the tasks to show they are unsuccessful or stop execution. This can further be customized at Listr, Subtask or Task level.
Errors will yield a visual output on the terminal depending on the current renderer, and will also handle the Task that has failed depending on the configuration. If an application needs to quit prematurely and fail a specific task just throw out an instance of Error
.
The default behavior is if any of the tasks have failed, it will deem itself as unsuccessful and exit. This behavior can be changed with the exitOnError
option. If the exitOnError
is true
, the first error encountered will be thrown out, and it will propagate outwards starting from the Task.
WARNING
An Error
should be always a real Error
type extended from the JavaScript/Typescript Error
class.
Example
You can find the related examples here.
Throwing a Error ​
Throwing an error will stop any further action from the current Task and will propagate outwards of the Task to Listr and depending on the exitOnError
configuration, execution will be slowly halted for upcoming or concurrent tasks.
INFO
You don't have to catch and collect errors explicitly since they will always be collected by Listr.
WARNING
Be aware that the execution will only stop after the error is thrown out. This can kill any asynchronous action prematurely.
import { delay, Listr } from 'listr2'
const tasks = new Listr(
[
{
title: 'This task will fail.',
task: async (): Promise<void> => {
await delay(2000)
throw new Error('This task failed after 2 seconds.')
}
},
{
title: 'This task will never execute.',
task: (ctx, task): void => {
task.title = 'I will change my title if this executes.'
}
}
],
{ concurrent: false }
)
await tasks.run()
Changing the Behavior ​
Per Listr ​
import { delay, Listr } from 'listr2'
const tasks = new Listr(
[
{
title: 'This task will fail.',
task: async (): Promise<void> => {
await delay(2000)
throw new Error('This task failed after 2 seconds.')
}
},
{
title: 'This task will execute.',
task: (ctx, task): void => {
task.title = 'I will change my title if this executes.'
}
}
],
{ concurrent: false, exitOnError: false }
)
await tasks.run()
Per Subtask ​
import { delay, Listr } from 'listr2'
const tasks = new Listr(
[
{
title: 'This task will execute and not quit on errors.',
task: (ctx, task): Listr =>
task.newListr(
[
{
title: 'This is a subtask.',
task: async (): Promise<void> => {
throw new Error('I have failed [0]')
}
},
{
title: 'This is yet an another subtask and it will run.',
task: async (ctx, task): Promise<void> => {
task.title = 'I have succeeded.'
}
}
],
{ exitOnError: false }
)
},
{
title: 'This task will execute.',
task: (): void => {
throw new Error('I will exit on error since I am a direct child of parent task.')
}
}
],
{ concurrent: false, exitOnError: true }
)
await tasks.run()
Per Task ​
import { delay, Listr } from 'listr2'
const tasks = new Listr(
[
{
title: 'This task will fail.',
task: async (): Promise<void> => {
await delay(2000)
throw new Error('This task failed after 2 seconds.')
},
exitOnError: false
},
{
title: 'This task will execute.',
task: (_, task): void => {
task.title = 'I will change my title if this executes.'
}
}
],
{
concurrent: false,
exitOnError: true
}
)
await tasks.run()
Renderer ​
DefaultRenderer ​
Default renderer has options where you can change how the errors are displayed.
Interface
showErrorMessage? ​
optional
showErrorMessage:boolean
Show the error message or show the original title of the task.
true
will output the current error encountered with the task if there is any.false
will keep the current task title intact. This will also disablecollapseErrors
.
Default Value ​
true
Defined in ​
packages/listr2/src/renderer/default/renderer.interface.ts:118
collapseErrors? ​
optional
collapseErrors:boolean
Collapse error messages into a single message and overwrite the task title.
true
will collapse the error message.false
will show the error message as a data output under the current task title.
Default Value ​
true
Defined in ​
packages/listr2/src/renderer/default/renderer.interface.ts:127
Collected Errors ​
Errors from the Task are collected inside an array in the main Listr task list as tasks.error
where tasks
is the Listr class. This option is opt-in since v6.0.0.
Since there are options to ignore some errors on cases like exitOnError
, or the ability to retry the given task through task.retry
, encountered errors can be swallowed while the execution. To deal with those swallowed errors, all the errors that are encountered even though it does not stops the execution gets collected through this property.
Modes ​
#615Error collection has three modes to choose from which are, false
, minimal
and full
. This can be set through per Task in the Listr options with the key collectErrors
. The default mode is false
since I decided that this is the most-underused functionality, and it should be at least opt-in for saving some memory.
Due to potential memory leaks from cloning the context and task to the ListrError
, advised mode is minimal
, which will only collect where the error has occurred, when it has been encountered and what the error.message
is.
If you want to fetch the full information for debugging you can set the mode to full
. This will also clone the current context and task to the ListrError
.
You can disable the error collection completely by setting it to false
.
ListrError ​
ListrError
class extends the default Error
and has some additional information like the cause of the error and where it is coming from, and the frozen context at the given time to further debug the issue while execution.
ListrErrorTypes ​
A listr error can be caused by multiple reasons, for a better explanation of why that particular error occurred, a type property on the ListrError
exists in the form of enum ListrErrorTypes
.
Methodology ​
The order of the array tasks.error
where tasks
is the Listr class, represents the order of errors that are encountered.
To keep the error collection mechanism simple and predictable, it might also process the errors coming from the subtasks as well.
For example, the following example will clear some things up about the given mindset.
Code
import { Listr } from 'listr2'
const tasks = new Listr(
[
{
task: (): void => {
throw new Error('1')
}
},
{
task: (_, task): Listr => {
return task.newListr(
[
{
task: (): void => {
throw new Error('3')
}
},
{
task: (): void => {
throw new Error('4')
}
}
],
{ exitOnError: true }
)
}
},
{
task: (): void => {
throw new Error('2')
}
}
],
{ exitOnError: false, collectErrors: 'minimal' }
)
await tasks.run()
console.log(tasks.errors)
Output
[
Error: 1
at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:7:15)
at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35) {
error: Error: 1
at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:7:15)
at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35),
type: 'HAS_FAILED_WITHOUT_ERROR',
task: Task {
emitter: [EventEmitter],
listr: [Listr],
task: [Object],
options: [Object],
rendererOptions: {},
id: '5.1ffae60d8631f.efd766868b915.866e1eec8dcb1.6c49d60beb37d.a3a892931a5ef.e78a8650db4c1.74a7a66109265.bcc1d65fd77f-9.16ae5bbd949de.84a3445d953d9.b4ff3c4de3d24.6847e6b03009-48.845a4ab1c8fec.0406e1d03a734.306961606f12-3f.d63122b38bc71.2c6be88b853d.aa5a66a00
fa3-2.67a25dc32b275.05069591f2917.99ad61f61433b.62d2b3fcf014.6c3ab12e8638b.a012e95dfadb6.5ac0b32dde1f5.3783719aafae9.f7cf95351bc3.b468fee844f0.c9627515807f4.3c546e82beb7',
state: 'FAILED',
subtasks: undefined,
title: undefined,
initialTitle: undefined,
output: undefined,
retry: undefined,
message: [Object],
rendererTaskOptions: undefined,
prompt: undefined,
parent: undefined,
enabled: true,
taskFn: [Function: task]
},
path: [ undefined ],
ctx: undefined,
name: 'ListrError'
},
Error: 3
at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:16:23)
at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35) {
error: Error: 3
at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:16:23)
at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35),
type: 'HAS_FAILED',
task: Task {
emitter: [EventEmitter],
listr: [Listr],
task: [Object],
options: [Object],
rendererOptions: {},
id: 'e.b91e31c7a8633.a5c0d756a30ed.8653c1351752e.73b560a042c80.64e107220cbf3.fc2b7bd6f02f5.c9688ca346799.cb51fffc2226-a.6e976cd950398.b054762b266ad.464a3f3110918.e466da24f4d6-45.96ed406986ecb.11850cc6105df.560383a2d91c-36.0c1b392d14abe.9f89babe09822.45b315ac
4bb8-1.ee031a687fb11.86d5245ac9fc3.55e9067a7e183.53d94301c813.9591c72743037.4922af1ed491f.2bdb77eed4ee.ec002d1dfb8e1.7882a17eb2377.049051202a2e0.0c942ce10bc6b.0d33f80a6803',
state: 'FAILED',
subtasks: undefined,
title: undefined,
initialTitle: undefined,
output: undefined,
retry: undefined,
message: [Object],
rendererTaskOptions: undefined,
prompt: undefined,
parent: [Task],
enabled: true,
taskFn: [Function: task]
},
path: [ undefined, undefined ],
ctx: undefined,
name: 'ListrError'
},
Error: 2
at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:31:15)
at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35) {
error: Error: 2
at Task.taskFn (file:///home/cenk/development/listr2/examples/docs/task/error-handling/collection.ts:31:15)
at Task.run (file:///home/cenk/development/listr2/src/lib/task.ts:298:35),
type: 'HAS_FAILED_WITHOUT_ERROR',
task: Task {
emitter: [EventEmitter],
listr: [Listr],
task: [Object],
options: [Object],
rendererOptions: {},
id: '8.255d2f2e3fa9c.8870108a62aee.14a04d8597aa4.da8da95f37b79.575d8732ea0b5.cb0d308db7b40.f19fb8db928d.5ed830c8904b-e.b944062c0ab5f.d339986948bdf.5ad93c6148292.01e745100f4c-47.fe4e954aec826.032487f91f53b.df78f4442433-31.591906fa76151.dbe812f35d5a8.17d8b12a5
3c8-d.6919e94a5b5d7.e0f5853c6cc2e.c1020891d643a.1235a6678d71d.f39b92415cafb.0c07b9db54fc0.02522dfb9af63.c2fd91995b050.ef96265e70f54.d2ad4fff8edd0.ed4249a0f67f6.b78b6510ccf9',
state: 'FAILED',
subtasks: undefined,
title: undefined,
initialTitle: undefined,
output: undefined,
retry: undefined,
message: [Object],
rendererTaskOptions: undefined,
prompt: undefined,
parent: undefined,
enabled: true,
taskFn: [Function: task]
},
path: [ undefined ],
ctx: undefined,
name: 'ListrError'
}
]
Flow
- First error will be thrown from the first task. Since exitOnError is
false
on that context,ListrError
will get collected bytasks.errors
], and the value will be{ message: '1', type: ListrErrorTypes.HAS_FAILED_WITHOUT_ERROR }
. - Then it will recurse into the second task, which has two subtasks.
- The first task from the subtasks will fail and since the
exitOnError
is set totrue
in that context, that subtasks will fail and throw. TheListrError
appended to thetasks.errors
will be{ message: '3', type: ListrErrorTypes.HAS_FAILED }
- Since the subtask has crashed, it will not execute the upcoming tasks in the subtasks.
- It will return to the main task list and execute the 3rd task from that list. It will again show the same behavior with the first task, and the
ListrError
will be{ message: '2', type: ListrErrorTypes.HAS_FAILED_WITHOUT_ERROR }
.