Repository: sindresorhus/got (ts)

Issue:
Cannot cancel when timeout option is set
When the options.timeout, or options.timeout.request, is set, the `pCancelable` promise is wrapped in a `pTimeout` promise. This prevents the possibility of calling cancel.

See [index.js#L130-L132](https://github.com/sindresorhus/got/blob/master/index.js#L130-L132).

Is this intentional?

Pull request description:
Fix cancel when timeout exists - fixes #344
Fixes https://github.com/sindresorhus/got/issues/344

Patch:
diff --git a/index.js b/index.js
index 0631aa2..e11bfd2 100644
--- a/index.js
+++ b/index.js
@@ -270,7 +270,7 @@ function asPromise(opts) {
 
 	const proxy = new EventEmitter();
 
-	const promise = timeoutFn(new PCancelable((onCancel, resolve, reject) => {
+	const cancelable = new PCancelable((onCancel, resolve, reject) => {
 		const ee = requestAsEventEmitter(opts);
 		let cancelOnRequest = false;
 
@@ -332,7 +332,11 @@ function asPromise(opts) {
 		ee.on('error', reject);
 		ee.on('uploadProgress', proxy.emit.bind(proxy, 'uploadProgress'));
 		ee.on('downloadProgress', proxy.emit.bind(proxy, 'downloadProgress'));
-	}));
+	});
+
+	const promise = timeoutFn(cancelable);
+
+	promise.cancel = cancelable.cancel.bind(cancelable);
 
 	promise.on = (name, fn) => {
 		proxy.on(name, fn);
@@ -567,6 +571,7 @@ for (const method of methods) {
 class StdError extends Error {
 	constructor(message, error, opts) {
 		super(message);
+		Error.captureStackTrace(this, this.constructor);
 		this.name = 'StdError';
 
 		if (error.code !== undefined) {


Test patch:
diff --git a/test/cancel.js b/test/cancel.js
index eada726..3ed0fdf 100644
--- a/test/cancel.js
+++ b/test/cancel.js
@@ -47,6 +47,25 @@ test('cancel in-progress request', async t => {
 	await t.notThrows(helper.aborted, 'Request finished instead of aborting.');
 });
 
+test('cancel in-progress request with timeout', async t => {
+	const helper = await createAbortServer();
+	const body = new Readable({
+		read() {}
+	});
+	body.push('1');
+
+	const p = got(helper.url, {body, timeout: 10000});
+
+	// Wait for the stream to be established before canceling
+	setTimeout(() => {
+		p.cancel();
+		body.push(null);
+	}, 100);
+
+	await t.throws(p, PCancelable.CancelError);
+	await t.notThrows(helper.aborted, 'Request finished instead of aborting.');
+});
+
 test('cancel immediately', async t => {
 	const s = await createServer();
 	const aborted = new Promise((resolve, reject) => {