AbortController is the standard way to abort any ongoing operations. AbortController and AbortSignal are now part of Nodejs LTS (originally introduced in v15.0.0).

Modern apps usually don’t work in isolation. They interact with entities like other APIs, file system, network, databases, etc. Each of these interactions adds an unknown delay to the handling of the incoming request. Sometimes, these interactions can take long time than expected resulting in overall delay in response. In such cases, the apps need a way to abort any operations that have taken longer than expected.

Cancel Async Operations

Let us take a use case where we want to abort an operation if it takes more than say 10 sec.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const fooTask = () => {
  // some operation which can run longer than expected
  // e.g. 
  // file read/write operation
  // external api call
  // database operation
}

const timeOut = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('operation timed out')), 10000);
});

await Promise.race([
  fooTask(),
  timeOut
]);

In the above scenario; if fooTask() operation takes longer time than expected say 10 sec, trigger a timeout. There are however two problems with the above code:

  • Even though timeout is triggered; the fooTask() operation is not stopped
  • Even if fooTask() operation completes within expected time, setTimeout timer will still be running and will eventually reject the promise with unhandled promise rejection error. This can cause performance/memory issues.

A more graceful way of handling above scenario is

  • when timeout is triggered, it sends a signal to abort fooTask() operation
  • when fooTask() operation is complete, it sends a signal to clear setTimeout timer

This is where AbortController and AbortSignal, now part of Nodejs core API comes into picture.

AbortController & AbortSignal

AbortController is a utility class used to signal cancelation in selected Promise-based APIs. The API is based on the Web API AbortController.

abortController.abort(): Triggers the abort signal, causing the abortController.signal to emit the ‘abort’ event.

abortController.signal: Type: AbortSignal

AbortSignal extends EventTarget with a single type of event that it emits — the abort event. It also has an additional boolean property, aborted which is true if the AbortSignal has already been triggered.

Let us modify our code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const { setTimeout: setTimeoutPromise } = require('timers/promises');

const abortTimeout = new AbortController();
const abortFooTask = new AbortController();

const fooTask = async ({signal}) => {
  if (signal?.aborted === true) {
  	 throw new Error('operation aborted');
  }
  
  try {
    await setTimeoutPromise(100, undefined, {signal: abortFooTask.signal });
    
    console.log('fooTask done');
    console.log('signal ---> abort timeout');
    abortTimeout.abort();
	  // some operation which can run longer than expected
	  // e.g. 
	  // file read/write operation
	  // external api call
	  // database operation
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('fooTask was aborted');
    }
  }
}

const timeOut = async () => {
  try {
    await setTimeoutPromise(10, undefined, { signal: abortTimeout.signal });
    console.log('operation taking more time than expected');
    console.log('signal ---> abort fooTask');
    abortFooTask.abort();
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('The timeout was aborted');
    }
  }
};

const start = async () =>  {
  await Promise.race([
    fooTask({signal: abortFooTask.signal}),
    timeOut()
  ]);
}

start();

I have used setTimeout to simulate the operations.

Output with:

  • timeout value of 10 for timeout operation and 100 for fooTask operation
1
2
3
operation taking more time than expected
signal ---> abort fooTask
fooTask was aborted
  • timeout value of 100 for timeout operation and 10 for fooTask operation
1
2
3
fooTask done
signal ---> abort timeout
The timeout was aborted

Conclusion

AbortController and AbortSignal are now part of Nodejs core APIs. They provide a more graceful way of cancelling async operations.

✨ Thank you for reading and I hope you find it helpful. I sincerely request for your feedback in the comment’s section. You can follow me on twitter @_manish25.