Repository: sindresorhus/got (ts)

Issue:
Calling got.stream via options flag with got()
Looking through the source, I see `got.stream` calls `asStream`, with got calling `asPromise`, with the various methods added one level down.

I'd love to be able to do something like: 

```
got(url, {
  stream: true
})
``` 

which under the hood would call `asStream`. Something similar is fine too.

The use case is to have got wrapped so each request has the same configurable retries and timeout specs.

Pull request description:
Allow calling of `got.stream` via options
Fixes #362.

This commit allows the user to pass a `stream` flag to `opts`. When this flag is `true`, `got` returns a stream just as if it was called with `got.stream`.

Patch:
diff --git a/index.js b/index.js
index 6c82aa8..037fb7a 100644
--- a/index.js
+++ b/index.js
@@ -337,7 +337,7 @@ function asPromise(opts) {
 						}
 					}
 
-					if (statusCode !== 304 && (statusCode < 200 || statusCode > limitStatusCode)) {
+					if (opts.throwHttpErrors && statusCode !== 304 && (statusCode < 200 || statusCode > limitStatusCode)) {
 						throw new got.HTTPError(statusCode, res.statusMessage, res.headers, opts);
 					}
 
@@ -425,7 +425,7 @@ function asStream(opts) {
 
 		res.pipe(output);
 
-		if (statusCode !== 304 && (statusCode < 200 || statusCode > 299)) {
+		if (opts.throwHttpErrors && statusCode !== 304 && (statusCode < 200 || statusCode > 299)) {
 			proxy.emit('error', new got.HTTPError(statusCode, res.statusMessage, res.headers, opts), null, res);
 			return;
 		}
@@ -467,7 +467,8 @@ function normalizeArguments(url, opts) {
 			retries: 2,
 			cache: false,
 			decompress: true,
-			useElectronNet: false
+			useElectronNet: false,
+			throwHttpErrors: true
 		},
 		url,
 		{
@@ -585,7 +586,13 @@ function normalizeArguments(url, opts) {
 
 function got(url, opts) {
 	try {
-		return asPromise(normalizeArguments(url, opts));
+		const normalizedArgs = normalizeArguments(url, opts);
+
+		if (normalizedArgs.stream) {
+			return asStream(normalizedArgs);
+		}
+
+		return asPromise(normalizedArgs);
 	} catch (err) {
 		return Promise.reject(err);
 	}
diff --git a/readme.md b/readme.md
index 5daa9cc..db36afa 100644
--- a/readme.md
+++ b/readme.md
@@ -105,6 +105,13 @@ Type: `Object`
 
 Any of the [`http.request`](http://nodejs.org/api/http.html#http_http_request_options_callback) options.
 
+###### stream
+
+Type: `boolean`<br>
+Default: `false`
+
+Returns a `Stream` instead of a `Promise`. This is equivalent to calling `got.stream(url, [options])`.
+
 ###### body
 
 Type: `string` `Buffer` `stream.Readable`
@@ -206,6 +213,14 @@ Default: `false`
 
 When used in Electron, Got will use [`electron.net`](https://electronjs.org/docs/api/net/) instead of the Node.js `http` module. According to the Electron docs, it should be fully compatible, but it's not entirely. See [#315](https://github.com/sindresorhus/got/issues/315).
 
+###### throwHttpErrors
+
+Type: `boolean`<br>
+Default: `true`
+
+Determines if a `got.HTTPError` is thrown for error responses (non-2xx status codes).
+
+If this is disabled, requests that encounter an error status code will be resolved with the `response` instead of throwing. This may be useful if you are checking for resource availability and are expecting error responses.
 
 #### Streams
 


Test patch:
diff --git a/test/arguments.js b/test/arguments.js
index 6546888..b12510c 100644
--- a/test/arguments.js
+++ b/test/arguments.js
@@ -1,5 +1,6 @@
 import {URL} from 'universal-url';
 import test from 'ava';
+import pEvent from 'p-event';
 import got from '..';
 import {createServer} from './helpers/server';
 
@@ -21,6 +22,10 @@ test.before('setup', async () => {
 		res.end(req.url);
 	});
 
+	s.on('/stream', (req, res) => {
+		res.end('ok');
+	});
+
 	await s.listen(s.port);
 });
 
@@ -81,6 +86,16 @@ test('WHATWG URL support', async t => {
 	await t.notThrows(got(wURL));
 });
 
+test('should return streams when using stream option', async t => {
+	const data = await pEvent(got(`${s.url}/stream`, {stream: true}), 'data');
+	t.is(data.toString(), 'ok');
+});
+
+test('should not allow stream and JSON option at the same time', async t => {
+	const error = await t.throws(got(`${s.url}/stream`, {stream: true, json: true}));
+	t.is(error.message, 'Got can not be used as a stream when the `json` option is used');
+});
+
 test.after('cleanup', async () => {
 	await s.close();
 });
diff --git a/test/http.js b/test/http.js
index e9b35e8..e10b00c 100644
--- a/test/http.js
+++ b/test/http.js
@@ -60,6 +60,10 @@ test('status code 304 doesn\'t throw', async t => {
 	t.is(response.body, '');
 });
 
+test('doesn\'t throw on throwHttpErrors === false', async t => {
+	t.is((await got(`${s.url}/404`, {throwHttpErrors: false})).body, 'not');
+});
+
 test('invalid protocol throws', async t => {
 	const err = await t.throws(got('c:/nope.com', {json: true}));
 	t.is(err.constructor, got.UnsupportedProtocolError);
diff --git a/test/stream.js b/test/stream.js
index 4788143..9e6d723 100644
--- a/test/stream.js
+++ b/test/stream.js
@@ -84,6 +84,11 @@ test('have error event #2', async t => {
 	await t.throws(pEvent(stream, 'response'), /getaddrinfo ENOTFOUND/);
 });
 
+test('have response event on throwHttpErrors === false', async t => {
+	const response = await pEvent(got.stream(`${s.url}/error`, {throwHttpErrors: false}), 'response');
+	t.is(response.statusCode, 404);
+});
+
 test('accepts option.body as Stream', async t => {
 	const stream = got.stream(`${s.url}/post`, {body: intoStream(['wow'])});
 	const data = await pEvent(stream, 'data');