You can fix this by

elementObject[key].addEventListener(
  'mouseleave',
  (_evt: any) => onMouseOutHandler(_evt),
  { signal: controller.signal }   as AddEventListenerOptions
)

The options object is causing that error, even when it has properties that match the AddEventListenerOptions interface, it still needs to be told that it is an AddEventListenerOptions.

Answer from Frazer Kirkman on Stack Overflow
🌐
Medium
medium.com › @icjoseph › using-react-to-understand-abort-controllers-eb10654485df
Using React to understand Abort Controllers | by Joseph Chamochumbi | Medium
November 18, 2022 - This randomId function can now take an optional signal. If the signal is an instance of AbortSignal then we attach an event listener to it, which will clear the ongoing timeout, when an abort event happens.
🌐
Localcan
localcan.com › blog › abortcontroller-nodejs-react-complete-guide-examples
AbortController in Node.js and React - Complete Guide with 5 Examples - Blog - LocalCan™
May 23, 2025 - async function fetchWithTimeout(url, timeoutMs = 5000) { // Create signals for timeout and user cancellation const userController = new AbortController() const timeoutSignal = AbortSignal.timeout(timeoutMs) // Combine both signals const combinedSignal = AbortSignal.any([userController.signal, timeoutSignal]) try { const response = await fetch(url, { signal: combinedSignal, }) return await response.json() } catch (error) { if (error.name === 'AbortError') { if (timeoutSignal.aborted) { throw new Error('Request timed out') } else { throw new Error('Request cancelled by user') } } throw error } }
🌐
Medium
medium.com › @amitazadi › the-complete-guide-to-abortcontroller-and-abortsignal-from-basics-to-advanced-patterns-a3961753ef54
The Complete Guide to AbortController and AbortSignal | by Amit Kumar | Medium
September 3, 2025 - try { // Creates a signal that auto-aborts after 5 seconds const signal = AbortSignal.timeout(5000); const response = await fetch('/api/slow-endpoint', { signal }); return await response.json(); } catch (error) { if (error.name === 'TimeoutError') { console.log('Request timed out'); } throw error; }
🌐
DEV Community
dev.to › mohsenfallahnjd › abortcontroller-abortsignal-5ea4
AbortController & AbortSignal - DEV Community
September 23, 2025 - // create a controller that auto-aborts after ms export const withTimeout = (ms = 5000) => AbortSignal.timeout(ms) // combine multiple signals -> aborted if ANY aborts export function anySignal(...signals) { const c = new AbortController() const onAbort = (s) => c.abort(s.reason ??
🌐
LogRocket
blog.logrocket.com › home › the complete guide to the abortcontroller api
The complete guide to the AbortController API - LogRocket Blog
March 12, 2025 - This tutorial will offer a complete guide on how to use the AbortController and AbortSignal APIs in both your backend and frontend. In our case, we’ll focus on Node.js and React.
🌐
j-labs
j-labs.pl › home › tech blog › how to use the useeffect hook with the abortcontroller
AbortController in React. How to use the useEffect hook with the AbortControl? | j‑labs
December 9, 2025 - AbortSignal: This is an object that represents the signal used for cancellation. It has a single property: – aborted: This property is a Boolean that indicates whether the associated operation has been aborted (true) or not (false).
Find elsewhere
Top answer
1 of 3
22

Based off your code, there are a few corrections to make:

Don't return new Promise() inside an async function

You use new Promise if you're taking something event-based but naturally asynchronous, and wrap it into a Promise. Examples:

  • setTimeout
  • Web Worker messages
  • FileReader events

But in an async function, your return value will already be converted to a promise. Rejections will automatically be converted to exceptions you can catch with try/catch. Example:

async function MyAsyncFunction(): Promise<number> {
  try {
    const value1 = await functionThatReturnsPromise(); // unwraps promise 
    const value2 = await anotherPromiseReturner();     // unwraps promise
    if (problem)
      throw new Error('I throw, caller gets a promise that is eventually rejected')
    return value1 + value2; // I return a value, caller gets a promise that is eventually resolved
  } catch(e) {
    // rejected promise and other errors caught here
    console.error(e);
    throw e; // rethrow to caller
  }
}

The caller will get a promise right away, but it won't be resolved until the code hits the return statement or a throw.

What if you have work that needs to be wrapped with a Promise constructor, and you want to do it from an async function? Put the Promise constructor in a separate, non-async function. Then await the non-async function from the async function.

function wrapSomeApi() {
  return new Promise(...);
}

async function myAsyncFunction() {
  await wrapSomeApi();
}

When using new Promise(...), the promise must be returned before the work is done

Your code should roughly follow this pattern:

function MyAsyncWrapper() {
  return new Promise((resolve, reject) => {
    const workDoer = new WorkDoer();
    workDoer.on('done', result => resolve(result));
    workDoer.on('error', error => reject(error));
    // exits right away while work completes in background
  })
}

You almost never want to use Promise.resolve(value) or Promise.reject(error). Those are only for cases where you have an interface that needs a promise but you already have the value.

AbortController is for fetch only

The folks that run TC39 have been trying to figure out cancellation for a while, but right now there's no official cancellation API.

AbortController is accepted by fetch for cancelling HTTP requests, and that is useful. But it's not meant for cancelling regular old work.

Luckily, you can do it yourself. Everything with async/await is a co-routine, there's no pre-emptive multitasking where you can abort a thread or force a rejection. Instead, you can create a simple token object and pass it to your long running async function:

const token = { cancelled: false }; 
await doLongRunningTask(params, token); 

To do the cancellation, just change the value of cancelled.

someElement.on('click', () => token.cancelled = true); 

Long running work usually involves some kind of loop. Just check the token in the loop, and exit the loop if it's cancelled

async function doLongRunningTask(params: string, token: { cancelled: boolean }) {
  for (const task of workToDo()) {
    if (token.cancelled)
      throw new Error('task got cancelled');
    await task.doStep();
  }
}

Since you're using react, you need token to be the same reference between renders. So, you can use the useRef hook for this:

function useCancelToken() {
  const token = useRef({ cancelled: false });
  const cancel = () => token.current.cancelled = true;
  return [token.current, cancel];
}

const [token, cancel] = useCancelToken();

// ...

return <>
  <button onClick={ () => doLongRunningTask(token) }>Start work</button>
  <button onClick={ () => cancel() }>Cancel</button>
</>;

hash-wasm is only semi-async

You mentioned you were using hash-wasm. This library looks async, as all its APIs return promises. But in reality, it's only await-ing on the WASM loader. That gets cached after the first run, and after that all the calculations are synchronous.

Even if it is wrapped in an async function or in a function returning a Promise, code must yield the thread to act concurrently, which hash-wasm does not appear to do in its main computation loop.

So how can you let your code breath if you've got CPU intensive code like what hash-wasm uses? You can do your work in increments, and schedule those increments with setTimeout:

for (const step of stepsToDo) {
  if (token.cancelled)
    throw new Error('task got cancelled');

  // schedule the step to run ASAP, but let other events process first
  await new Promise(resolve => setTimeout(resolve, 0));

  const chunk = await loadChunk();
  updateHash(chunk);
}

(Note that I'm using a Promise constructor here, but awaiting immediately instead of returning it)

The technique above will run slower than just doing the task. But by yielding the thread, stuff like React updates can execute without an awkward hang.

If you really need performance, check out Web Workers, which let you do CPU-heavy work off-thread so it doesn't block the main thread. Libraries like workerize can help you convert async functions to run in a worker.


That's everything I have for now, I'm sorry for writing a novel

2 of 3
0

I can suggest my library (use-async-effect2) for managing the cancellation of asynchronous tasks/promises. Here is a simple demo with nested async function cancellation:

    import React, { useState } from "react";
    import { useAsyncCallback } from "use-async-effect2";
    import { CPromise } from "c-promise2";
    
    // just for testing
    const factorialAsync = CPromise.promisify(function* (n) {
      console.log(`factorialAsync::${n}`);
      yield CPromise.delay(500);
      return n != 1 ? n * (yield factorialAsync(n - 1)) : 1;
    });
    
    function TestComponent({ url, timeout }) {
      const [text, setText] = useState("");
    
      const myTask = useAsyncCallback(
        function* (n) {
          for (let i = 0; i <= 5; i++) {
            setText(`Working...${i}`);
            yield CPromise.delay(500);
          }
          setText(`Calculating Factorial of ${n}`);
          const factorial = yield factorialAsync(n);
          setText(`Done! Factorial=${factorial}`);
        },
        { cancelPrevious: true }
      );
    
      return (
        <div>
          <div>{text}</div>
          <button onClick={() => myTask(15)}>
            Run task
          </button>
          <button onClick={myTask.cancel}>
            Cancel task
          </button>
        </div>
      );
    }
🌐
GitHub
github.com › facebook › react › issues › 21043
Feature request: Consider supporting AbortSignal/AbortController for cleanup · Issue #21043 · facebook/react
March 20, 2021 - Hey, I've recently worked on adding support for AbortController/AbortSignal in a bunch of Node.js APIs and some DOM ones and I think it would be really cool if React added support to the web platform's cancellation standard primitive. Ba...
🌐
Medium
medium.com › @yoav.yh › using-abortcontroller-in-react-da73a6dd45ad
Using AbortController in React. Avoiding unnecessary HTTP requests in… | by Yoav Hirshberg | Medium
November 28, 2022 - In this article, I would explain how to implement the AbortController in general, but with a focus on React application. ... TL;DR; create a controller using the AbortController() constructor, then grab a reference to its associated AbortSignal object using its signal property.
🌐
Agoraio-community
agoraio-community.github.io › VideoUIKit-Web-React › interfaces › _internal_.AbortSignal.html
Interface AbortSignal - agora-react-uikit
The search index is not availableagora-react-uikit ... A signal object that allows you to communicate with a DOM request (such as a Fetch) and abort it if required via an AbortController object. ... Returns true if this AbortSignal's AbortController has signaled to abort, and false otherwise.
🌐
Kettanaito
kettanaito.com › blog › dont-sleep-on-abort-controller
Don't Sleep on AbortController - kettanaito.com
September 17, 2024 - Every abort event is accompanied with the reason for that abort. That yields even more customizability as you can react to different abort reasons differently. The abort reason is an optional argument to the controller.abort() method. You can access the abort reason in the reason property of any AbortSignal instance.
🌐
Westbrookdaniel
westbrookdaniel.com › blog › react-abort-controllers
Using AbortControllers to Cancel Fetch in React - Daniel Westbrook
Here we use the web api AbortController as the signal for fetch. By returning a function from useEffect we can trigger the abort controller on dismount (see the React docs). The AbortSignal (controller.signal) is then passed into the fetch as an argument and voilà!
🌐
GitHub
github.com › facebook › react-native › issues › 50015
AbortSignal/AbortController doesn't work · Issue #50015 · facebook/react-native
March 13, 2025 - Description AbortController / AbortSignal are used in javascript to cancel a request started with fetch, or any other async task that should be cancellable (docs here) In react native, there are several issues with these APIs: DOMExcepti...
Author   lufinkey
🌐
Run, Code & Learn
blog.delpuppo.net › react-query-abort-request
React Query - Abort Request - Run, Code & Learn
May 17, 2023 - Hey Folks, Today it's time to learn how you can abort an ongoing request with ReactQuery. Before moving to the example, I want to introduce the AbortSignal. AbortSignal is a javascript interface used to abort ongoing methods. Typically, you ...
🌐
React Native
reactnative.dev › docs › next › global-AbortSignal
AbortSignal · React Native
This is unreleased documentation for React Native Next version. For up-to-date documentation, see the latest version (0.82). ... The global AbortSignal class, as defined in Web specifications.
🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Web › API › AbortSignal
AbortSignal - Web APIs - MDN Web Docs - Mozilla
A Promise-based API should respond to the abort signal by rejecting any unsettled promise with the AbortSignal abort reason. For example, consider the following myCoolPromiseAPI, which takes a signal and returns a promise. The promise is rejected immediately if the signal is already aborted, ...
Top answer
1 of 7
32

According to the docs axios supports the AbortController of the fetch API.

Cancellation

Axios supports AbortController to abort requests in fetch API way:

const controller = new AbortController();

axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// cancel the request
controller.abort()

It's not clear exactly where testController is declared:

let testController: AbortController;

but I suspect it's in the body of the function component and redeclared on a subsequent render cycle.

I suggest using a React ref to store an AbortController, and reference this ref value around your app. This is so the component holds on to a stable reference of the controller from render cycle to render cycle, to be referenced in any useEffect hook cleanup function to cancel in-flight requests if/when the component unmounts.

const abortControllerRef = useRef<AbortController>(new AbortController());

function loadTest() {
  TestAPI.getTest(abortControllerRef.current.signal)
    .then((e) => {
      console.log(e.data);
    })
    .catch((e) => {
      console.error(e);
    });
}

useEffect(() => {
  const controller = abortControllerRef.current;
  return () => {
    controller.abort();
  };
}, []);
2 of 7
8

I would recommend to read this post.

In a nutshell you would like to use useEffect to create controller, and, what is more important, to use return statement to abort the controller.

useEffect(() => {
 const controller = new AbortController();
 const signal = controller.signal;
 getData(signal)

 //cleanup function
 return () => {controller.abort();};
}, [fetchClick]);

getData function can then be your axios call in the form:

const getData = async (signal) =>{
 const res = await axios.get(url, {signal: signal}).then(...)
}
🌐
Codedrivendevelopment
codedrivendevelopment.com › posts › everything-about-abort-signal-timeout
Everything about the AbortSignals (timeouts, combining signals, and how to use it with window.fetch) | Code Driven Development
April 20, 2024 - You can use AbortSignal.timeout(500) to automatically create the abort signal which will abort after that duration (Without the boilerplate of using AbortController).