Skip to content

Edge Cases and Errors

m4bwav edited this page Sep 29, 2026 · 1 revision

Odd inputs, odd responses, and the exact error each one gives in the published 2.0.0. The inputs were run in one Node 24.18.0 process against the local stand-in for the API; where Node 20.20.2 printed something else, the difference is named. Responses such as "HTML error page" were routes of the stand-in, not the real API.

Ids

entityId Result
missing, 0, '', NaN, false Error: Need an entity id to read, no request
'0' sent as /questions/0 (a non-empty string passes the first check)
'0127968', 2 ** 53 sent as written (digits)
new Number(1), new String('1') unwrapped and sent as 1
-1, 1.5, 1e21, Infinity, true, {}, [1, 2] TypeError, no request
'abc', '../x', ' 1 ', '1 2', '1;;2', '1/answers', '1?site=evil' TypeError, no request

The TypeError names the value. For ' 1 ':

rejects TypeError: entityId must be a post id (digits, or ids joined by ";"), not " 1 "; requests 0

and for 1e21, whose string form is 1e+21:

rejects TypeError: entityId must be a post id (digits, or ids joined by ";"), not 1e+21; requests 0

In the callback form these throw from retrieveMarkdown itself, before it returns; in the Promise form they reject. 1.x sent every one of the refused values into the request path, which could reach other API methods ('../../users/1').

Responses

Response Callback Promise
API error body (error_message), any status (null, StackExchangeError) with the API's message rejects with it
HTML error page, status 500 (null, StackExchangeError: The response is not JSON) rejects
gzip cut off part way (null, StackExchangeError: The response is not JSON) rejects
204 No Content (empty body) (null, StackExchangeError: The response is not JSON) rejects
a body the package inflates to more than 64 MiB (null, StackExchangeError: The decompressed response is larger than 67108864 bytes) rejects
connection dropped by the server (null, TypeError: fetch failed) rejects

For the HTML page the rejection, with its cause and status, was:

promise: rejects StackExchangeError: The response is not JSON (cause: SyntaxError: Unexpected token '<', "<html><bod"... is not valid JSON) status 500

The throttling error keeps the API's error_name, which is the thing to test for (the message is the stand-in's copy):

callback: (null, StackExchangeError: too many requests from this IP, more requests available in 3600 seconds)
promise: rejects StackExchangeError: too many requests from this IP, more requests available in 3600 seconds status 502

A dropped connection is the platform's own error, not a StackExchangeError. On Node 24.18.0:

promise: rejects TypeError: fetch failed (cause: SocketError: other side closed)

Answers that are not errors (no such post, no body_markdown, a 404 without error_message) are listed on How markdown is retrieved.

Timeouts and aborts

The default timeout is 30 seconds. With timeout: 300 and a server that never answers:

TimeoutError: The operation timed out. (instanceof DOMException: true; after 300 ms or more)

A timeout above 2147483647 (the most setTimeout accepts) is cut to that, so timeout: 1e12 and timeout: Infinity work as "about 24.8 days" rather than firing at once. timeout: 0 turns the timer off.

An AbortSignal that fires during the request rejects with the signal's reason. With controller.abort():

AbortError: This operation was aborted

With controller.abort(new Error('stopped by the caller')):

Error: stopped by the caller

A signal that has already fired rejects at once and sends nothing:

AbortError: This operation was aborted; requests: 0

AbortSignal.timeout(300) gives a different message from the timeout option, though the same name:

TimeoutError: The operation was aborted due to timeout

Behind a proxy that drops the tunnel

When a proxy hangs up on each CONNECT, Node's fetch tries again straight away, over and over, so the call fails only when its timeout fires. A throwaway proxy on 127.0.0.1 that closed every tunnel saw this with timeout: 2000:

TimeoutError: The operation timed out.
CONNECTs to api.stackexchange.com:443: more than 100

Keep a timeout when a proxy is involved; with timeout: 0 the call has nothing else to end it. 1.x failed at once with tunneling socket could not be established, cause=socket hang up.

A callback that throws

import {retrieveMarkdown} from 'stack-exchange-markdown-retriever';

let calls = 0;
retrieveMarkdown({entityId: 1}, markdown => {
  calls += 1;
  console.log('call', calls, markdown);
  throw new Error('thrown by the callback');
});

The callback runs once, and the exception ends the process as an uncaught exception (sample content):

call 1 Sample question one.
stderr: Error: thrown by the callback
exit 1

1.x caught such an exception and called the callback a second time, with the exception as its error. 2.0.0 calls it once.

Two builds, two classes

The ES module and CommonJS builds each have their own StackExchangeError. An error from the CommonJS build, checked against both classes:

error from the CommonJS build: instanceof cjs.StackExchangeError true, instanceof esm.StackExchangeError false, name StackExchangeError

Check error.name when an application may load both builds (for example a CommonJS dependency and ES module code in one process).

Clone this wiki locally