node-fetch icon indicating copy to clipboard operation
node-fetch copied to clipboard

Unhandled 'error' event when using AbortSignal to cancel requests

Open AleCaste opened this issue 1 year ago • 5 comments

Reproduction

Steps to reproduce the behavior:

const controller = new AbortController();

const fetchTimeout = setTimeout(() => {
    controller.abort();
}, 500);   // Try different values from 0 - 3000

const requestOptions = {
    method: 'POST',
    headers: {
      'content-type': 'application/json'
    },
    body: JSON.stringify({test: 'test'}),
    signal: controller.signal
};

try {
  const response = await fetch(url, requestOptions)
} catch (err) {
  // handle error
}

The problem is somehow intermittent. Try different timeout values until you can reproduce it. I get the following error:

node:events:368
      throw er; // Unhandled 'error' event
      ^

AbortError: The operation was aborted.
    at abort (file:///D:/xampp/htdocs/fetch_retry_test/node_modules/node-fetch/src/index.js:70:18)
    at file:///D:/xampp/htdocs/fetch_retry_test/node_modules/node-fetch/src/index.js:87:4
    at new Promise (<anonymous>)
    at fetch (file:///D:/xampp/htdocs/fetch_retry_test/node_modules/node-fetch/src/index.js:49:9)
    at wrappedFetch (D:\xampp\htdocs\fetch_retry_test\node_modules\fetch-retry\index.js:71:9)
    at Timeout._onTimeout (D:\xampp\htdocs\fetch_retry_test\node_modules\fetch-retry\index.js:127:11)
    at listOnTimeout (node:internal/timers:557:17)
    at processTimers (node:internal/timers:500:7)
Emitted 'error' event on Readable instance at:
    at emitErrorNT (node:internal/streams/destroy:157:8)
    at emitErrorCloseNT (node:internal/streams/destroy:122:3)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {
  type: 'aborted'
}

Expected behavior The error happens in index.js in the following code block:

const abort = () => {
  const error = new AbortError('The operation was aborted.');
  reject(error);
  if (request.body && request.body instanceof Stream.Readable) {
    request.body.destroy(error);   // <==== HERE IS WHERE THE UNHANDLED ERROR OCCURS!!!!
  }

  if (!response || !response.body) {
    return;
  }

  response.body.emit('error', error);
};

To fix it, you need a listener for the "error" event of the Readable stream, like this below:

const abort = () => {
  const error = new AbortError('The operation was aborted.');
  reject(error);
  if (request.body && request.body instanceof Stream.Readable) {
    request.body.on('error', ()=>{});  // You must define a listener so that potential errors when destroying the stream ar catched
    request.body.destroy(error);
  }

  if (!response || !response.body) {
    return;
  }

  response.body.emit('error', error);
};

Screenshots

Your Environment

software version
node-fetch 3.3.2
node 16.13.0
npm 8.1.0
Operating System Windows 10

Additional context I believe this issue was reported in the past, but it was closed: https://github.com/node-fetch/node-fetch/issues/1420

AleCaste avatar Jan 23 '24 00:01 AleCaste

I had the same issue. In the end, I had to stop using node-fetch and instead used the native fetch API. The drawback is that it returns a (web) ReadableStream and not a (Node) stream.Readable.

You'll have to use it like this

const reader = stream.getReader();
while (true) {
    try {
        const { value, done } = await reader.read();
        if (done) {
            break;
        }
        // Do something
    } catch(err) {
        console.log('Error reading stream', err);
        break;
    }
}

willemmulder avatar Mar 15 '24 07:03 willemmulder

Happened to me as well, any resolution for this?

axonedge avatar Apr 10 '24 08:04 axonedge