Skip to content

Commit 3927348

Browse files
authoredAug 30, 2022
Don't call beforeError hooks with HTTPError if the throwHttpErrors option is false (#2104)
1 parent 9f06060 commit 3927348

File tree

3 files changed

+41
-3
lines changed

3 files changed

+41
-3
lines changed
 

‎documentation/3-streams.md

+5
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ To enable retrying when using streams, a retry handler must be attached.
239239

240240
When this event is emitted, you should reset the stream you were writing to and prepare the body again.
241241

242+
**Note:**
243+
> - [`HTTPError`s](./8-errors.md#httperror) cannot be retried if [`options.throwHttpErrors`](./2-options.md#throwhttperrors) is `false`.
244+
> This is because stream data is saved to `error.response.body` and streams can be read only once.
245+
> - For the Promise API, there is no such limitation.
246+
242247
#### `retryCount`
243248

244249
**Type: `number`**

‎source/core/index.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,10 @@ export default class Request extends Duplex implements RequestEvents<Request> {
797797
return;
798798
}
799799

800+
// `HTTPError`s always have `error.response.body` defined.
801+
// Therefore we cannot retry if `options.throwHttpErrors` is false.
802+
// On the last retry, if `options.throwHttpErrors` is false, we would need to return the body,
803+
// but that wouldn't be possible since the body would be already read in `error.response.body`.
800804
if (options.isStream && options.throwHttpErrors && !isResponseOk(typedResponse)) {
801805
this._beforeError(new HTTPError(typedResponse));
802806
return;
@@ -1148,9 +1152,15 @@ export default class Request extends Duplex implements RequestEvents<Request> {
11481152

11491153
private async _error(error: RequestError): Promise<void> {
11501154
try {
1151-
for (const hook of this.options.hooks.beforeError) {
1152-
// eslint-disable-next-line no-await-in-loop
1153-
error = await hook(error);
1155+
if (error instanceof HTTPError && !this.options.throwHttpErrors) {
1156+
// This branch can be reached only when using the Promise API
1157+
// Skip calling the hooks on purpose.
1158+
// See https://github.com/sindresorhus/got/issues/2103
1159+
} else {
1160+
for (const hook of this.options.hooks.beforeError) {
1161+
// eslint-disable-next-line no-await-in-loop
1162+
error = await hook(error);
1163+
}
11541164
}
11551165
} catch (error_: any) {
11561166
error = new RequestError(error_.message, error_, this);

‎test/hooks.ts

+23
Original file line numberDiff line numberDiff line change
@@ -1368,3 +1368,26 @@ test('does not throw on empty body when running afterResponse hooks', withServer
13681368
},
13691369
}));
13701370
});
1371+
1372+
test('does not call beforeError hooks on falsy throwHttpErrors', withServer, async (t, server, got) => {
1373+
server.get('/', (_request, response) => {
1374+
response.statusCode = 404;
1375+
response.end();
1376+
});
1377+
1378+
let called = false;
1379+
1380+
await got('', {
1381+
throwHttpErrors: false,
1382+
hooks: {
1383+
beforeError: [
1384+
error => {
1385+
called = true;
1386+
return error;
1387+
},
1388+
],
1389+
},
1390+
});
1391+
1392+
t.false(called);
1393+
});

0 commit comments

Comments
 (0)
Please sign in to comment.