Use cases for efficient management of asynchronous operations


What is the article about?

In modern web applications, asynchronous operations play a key role. However, managing them can be tricky, especially when you need to cancel tasks that have already been submitted for execution. Before the advent of AbortController, developers resorted to various crutches, such as creating global variables that tracked the state of the request, or using wrappers over XMLHttpRequest.

AbortController is a class provided in JavaScript that allows you to manage asynchronous operations such as Fetch запросы, Promise, fs, setTimeout и setInterval. It can be used to interrupt the execution of asynchronous tasks and prevent unwanted side effects from executing tasks that are no longer relevant. AbortController provides a robust and standardized mechanism for managing asynchronous tasks. It allows developers to control the execution of asynchronous operations, prevent unnecessary requests from being executed, and avoid memory leaks. In addition, using AbortController improves the performance and resource consumption of web applications. You can read more about the AbortController and AbortSignal APIs at link.

Let’s move on to examples

To create an instance of AbortController, use the class constructor:

const controller = new AbortController();

// После создания экземпляра AbortController, можно получить экземпляр AbortSignal, используя свойство signal:

const signal = controller.signal;

// Имитация отмены запроса через 3 секунды

setTimeout(() => {
  controller.abort();
}, 3000);

fetch('https://api.example.com/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch request aborted');
    } else {
      console.error('Fetch request failed:', error);
    }
  });

In this example, 3 seconds after the start of the API request, the method will be called abort(), which will cancel the Fetch request. If the request is not completed within 3 seconds, the error handler will catch the cancel event and print an appropriate message to the console. If the request completes earlier, the result will be processed and printed to the console without being cancelled.

When using AbortController, it is important to properly handle possible errors. When an operation is cancelled, it usually throws an error AbortError. This allows you to determine if the operation was completed successfully or canceled or completed for some other reason.

catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch request aborted');
    } else {
      console.error('Fetch request failed:', error);
    }
)

Keep in mind that not all browsers and environments support AbortController. For backward compatibility, it is recommended check having support for AbortController before using it:

if ('AbortController' in window) {
  // Используем AbortController
} else {
  // Используем альтернативное решение или продолжаем без отмены операций
}

Cancellation Promise

function delay(duration, signal) {
  return new Promise((resolve, reject) => {
    if (signal.aborted) {
      return reject(new DOMException('Operation aborted', 'AbortError'));
    }

    const timeoutId = setTimeout(() => {
      resolve();
    }, duration);

    signal.addEventListener('abort', () => {
      clearTimeout(timeoutId);
      reject(new DOMException('Operation aborted', 'AbortError'));
    });
  });
}

const controller = new AbortController();
const signal = controller.signal;

delay(5000, signal)
  .then(() => {
    console.log('Promise resolved');
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Promise aborted');
    } else {
      console.error('Promise failed:', error);
    }
  });

// Отменяем промис через 3 секунды

setTimeout(() => {
  controller.abort();
}, 3000);

Cancellation of setTimeout and setInterval

function createInterval(callback, interval, signal) {
  if (signal.aborted) {
    return;
  }

  const intervalId = setInterval(() => {
    callback();
    if (signal.aborted) {
      clearInterval(intervalId);
    }
  }, interval);

  signal.addEventListener('abort', () => {
    clearInterval(intervalId);
  });
}

const controller = new AbortController();
const signal = controller.signal;

createInterval(() => {
  console.log('Interval callback executed');
}, 1000, signal);

// Отменяем интервал через 5 секунд

setTimeout(() => {
  controller.abort();
}, 5000);

Managing Parallel and Sequential Asynchronous Operations

const urls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'https://api.example.com/data3'
];

const controller = new AbortController();
const signal = controller.signal;

function fetchWithSignal(url, signal) {
  return fetch(url, { signal }).then(response => response.json());
}

Promise.all(urls.map(url => fetchWithSignal(url, signal)))
  .then(results => {
    console.log('All fetch requests completed:', results);
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('One or more fetch requests aborted');
    } else {
      console.error('One or more fetch requests failed:', error);
    }
  });

// Отменяем все запросы через 3 секунды

setTimeout(() => {
  controller.abort();
}, 3000);

Creating a custom AbortController with timeout

In some cases, it is useful to automatically cancel asynchronous operations if they do not complete within a given period of time. To do this, you can create a custom AbortController with a timeout:

class TimeoutAbortController extends AbortController {
  constructor(timeout) {
    super();
    setTimeout(() => {
      this.abort();
    }, timeout);
  }
}

const controller = new TimeoutAbortController(3000);
const signal = controller.signal;

fetch('https://api.example.com/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch request aborted');
    } else {
      console.error('Fetch request failed:', error);
    }
  });

Last example =)

As of Node.js 10.0.0, many module features fs support promises and can use AbortController. In this example we are using fs.promises.readFile() with AbortController to cancel reading a file:

const fs = require('fs').promises;
const { AbortController } = require('abort-controller');

const controller = new AbortController();
const signal = controller.signal;

async function readWithAbort(path, signal) {
  try {
    const data = await fs.readFile(path, { encoding: 'utf-8', signal });
    console.log('File contents:', data);
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('File read operation aborted');
    } else {
      console.error('File read operation failed:', error);
    }
  }
}

const filePath="./example.txt";

// Отменяем чтение файла через 3 секунды

setTimeout(() => {
  controller.abort();
}, 3000);

readWithAbort(filePath, signal);

Instead of a conclusion

  1. Use AbortController only when you really need to cancel asynchronous operations. In some cases, alternative approaches may be more appropriate (for example, ignoring the result if it is not relevant).

  2. Handle undo errors to provide the user with information about what happened and what are the possible next steps.

  3. Clean up resources after canceling an operation. For example, when using setTimeout or setIntervaldon’t forget to call clearTimeout or clearInterval upon cancellation.

  4. In the absence of support for AbortController, provide alternative solutions or inform the user of possible limitations.

By following these best practices, you can get the most out of AbortController to manage asynchronous operations and keep your application running smoothly.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *