Skip to content

Commit

Permalink
Fix merging options and beforeError hook logic
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak committed Apr 20, 2020
1 parent 1f6ac45 commit d914a7e
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 17 deletions.
11 changes: 1 addition & 10 deletions source/as-promise/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,8 @@ export default class PromisableRequest extends Request {
error = new RequestError(error.message, error, this);
}

try {
for (const hook of this.options.hooks.beforeError) {
// eslint-disable-next-line no-await-in-loop
error = await hook(error as RequestError);
}
} catch (error_) {
this.destroy(new RequestError(error_.message, error_, this));
return;
}

// Let the promise decide whether to abort or not
// It is also responsible for the `beforeError` hook
this.emit('error', error);
}
}
17 changes: 16 additions & 1 deletion source/as-promise/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function asPromise<T>(options: NormalizedOptions): CancelableRequ
let globalResponse: Response;
const emitter = new EventEmitter();

const promise = new PCancelable<T>((resolve, reject, onCancel) => {
const promise = new PCancelable<T>((resolve, _reject, onCancel) => {
const makeRequest = (): void => {
// Support retries
// `options.throwHttpErrors` needs to be always true,
Expand All @@ -38,10 +38,25 @@ export default function asPromise<T>(options: NormalizedOptions): CancelableRequ
options.throwHttpErrors = true;
}

// Note from @szmarczak: I think we should use `request.options` instead of the local options
const request = new PromisableRequest(options.url, options);
request._noPipe = true;
onCancel(() => request.destroy());

const reject = async (error: RequestError) => {
try {
for (const hook of options.hooks.beforeError) {
// eslint-disable-next-line no-await-in-loop
error = await hook(error);
}
} catch (error_) {
_reject(new RequestError(error_.message, error_, request));
return;
}

_reject(error);
};

globalRequest = request;

request.once('response', async (response: Response) => {
Expand Down
24 changes: 20 additions & 4 deletions source/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ export class RequestError extends Error {

export class MaxRedirectsError extends RequestError {
declare readonly response: Response;
declare readonly request: Request;
declare readonly timings: Timings;

constructor(request: Request) {
super(`Redirected ${request.options.maxRedirects} times. Aborting.`, {}, request);
Expand All @@ -373,6 +375,8 @@ export class MaxRedirectsError extends RequestError {

export class HTTPError extends RequestError {
declare readonly response: Response;
declare readonly request: Request;
declare readonly timings: Timings;

constructor(response: Response) {
super(`Response code ${response.statusCode} (${response.statusMessage!})`, {}, response.request);
Expand All @@ -381,20 +385,25 @@ export class HTTPError extends RequestError {
}

export class CacheError extends RequestError {
declare readonly request: Request;

constructor(error: Error, request: Request) {
super(error.message, error, request);
this.name = 'CacheError';
}
}

export class UploadError extends RequestError {
declare readonly request: Request;

constructor(error: Error, request: Request) {
super(error.message, error, request);
this.name = 'UploadError';
}
}

export class TimeoutError extends RequestError {
declare readonly request: Request;
readonly timings: Timings;
readonly event: string;

Expand All @@ -407,6 +416,10 @@ export class TimeoutError extends RequestError {
}

export class ReadError extends RequestError {
declare readonly request: Request;
declare readonly response: Response;
declare readonly timings: Timings;

constructor(error: Error, request: Request) {
super(error.message, error, request);
this.name = 'ReadError';
Expand Down Expand Up @@ -606,6 +619,8 @@ export default class Request extends Duplex implements RequestEvents<Request> {
// `options.headers`
if (is.undefined(options.headers)) {
options.headers = {};
} else if (options.headers === defaults?.headers) {
options.headers = {...options.headers};
} else {
options.headers = lowercaseKeys({...(defaults?.headers), ...options.headers});
}
Expand Down Expand Up @@ -702,8 +717,8 @@ export default class Request extends Duplex implements RequestEvents<Request> {
getCookieString = promisify(getCookieString.bind(options.cookieJar));

options.cookieJar = {
setCookie: setCookie.bind(cookieJar),
getCookieString: getCookieString.bind(getCookieString)
setCookie,
getCookieString
};
}
}
Expand Down Expand Up @@ -750,7 +765,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
// `options.timeout`
if (is.number(options.timeout)) {
options.timeout = {request: options.timeout};
} else if (defaults) {
} else if (defaults && options.timeout !== defaults.timeout) {
options.timeout = {
...defaults.timeout,
...options.timeout
Expand All @@ -765,6 +780,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
}

// `options.hooks`
const areHooksUserDefined = options.hooks !== defaults?.hooks;
options.hooks = {...options.hooks};

for (const event of knownHookEvents) {
Expand All @@ -780,7 +796,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
}
}

if (defaults) {
if (defaults && areHooksUserDefined) {
for (const event of knownHookEvents) {
const defaultHooks = defaults.hooks[event];

Expand Down
2 changes: 1 addition & 1 deletion test/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ test('errors can have request property', withServer, async (t, server, got) => {
const error = await t.throwsAsync<HTTPError>(got(''));

t.truthy(error.response);
t.truthy(error.request!.downloadProgress);
t.truthy(error.request.downloadProgress);
});

// Fails randomly on Node 10:
Expand Down
44 changes: 43 additions & 1 deletion test/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {URL} from 'url';
import test from 'ava';
import nock = require('nock');
import getStream from 'get-stream';
import delay = require('delay');
import {Handler} from 'express';
import Responselike = require('responselike');
import got, {RequestError} from '../source';
import got, {RequestError, HTTPError} from '../source';
import withServer from './helpers/with-server';

const errorString = 'oops';
Expand Down Expand Up @@ -829,3 +830,44 @@ test('beforeRequest hook is called before each request', withServer, async (t, s

t.is(counts, 2);
});

test('beforeError emits valid promise `HTTPError`s', async t => {
t.plan(3);

nock('https://ValidHTTPErrors.com').get('/').reply(() => [422, 'no']);

const instance = got.extend({
hooks: {
beforeError: [
error => {
t.true(error instanceof HTTPError);
t.truthy(error.response!.body);

return error;
}
]
},
retry: 0
});

await t.throwsAsync(instance('https://ValidHTTPErrors.com'));
});

test('hooks are not duplicated', withServer, async (t, _server, got) => {
let calls = 0;

await t.throwsAsync(got({
hooks: {
beforeError: [
error => {
calls++;

return error;
}
]
},
retry: 0
}), {message: 'Response code 404 (Not Found)'});

t.is(calls, 1);
});

0 comments on commit d914a7e

Please sign in to comment.