README.md
# HTTP as Promised — Promisified HTTP client.
[![NPM Version][npm-image]][npm-url]
[![Build Status][travis-image]][travis-url]
[![Test Coverage][coveralls-image]][coveralls-url]
[![Code Climate][codeclimate-image]][codeclimate-url]
[![Dependencies][dependencies-image]][dependencies-url]
Using [bluebird][bluebird] and [create-error][create-error] to make [request][request] easier to use. The most notible difference between this library and simply "promisifying" the request module is that this library will automatically reject the promise with an `HTTPError` if the response idicates an HTTP error (e.g. `response.statusCode >= 400`). HTTP as Promised supports all the same [options you'd pass to request](https://github.com/mikeal/request/blob/master/README.md#requestoptions-callback) as well as all of [request's convenience methods](https://github.com/mikeal/request/blob/master/README.md#convenience-methods).
## Super simple to use. Promise.
HTTP as Promised is designed to be the simplest way possible to make http calls. It supports HTTPS and follows redirects by default.
```javascript
var $http = require('http-as-promised');
$http('https://www.github.com')
.spread(function (response, body){
console.log(body) // HTTP request was successful
})
.catch(function (error){
console.error(error) // HTTP request was unsuccessful
});
```
## Options
In addition to [all of the options](https://www.npmjs.org/package/request#request-options-callback-) that the [request module][request] accepts, there are two options specific to HTTP as Promised:
* **`error`** - If set to `false` HTTP as Promised will no longer reject the response with an [`HTTPError`](#http-errors) based on the its HTTP status code. Defaults to `true`. [See below](#http-errors).
* **`resolve`** - Indicates the fulfillment value with which you want the HTTP promise to be resolved. Accepts a string or array of strings. Possible values:
* **`['response', 'body']`** *(default)* - By default HTTP as Promised will resolve promises with an array containing the `response` ([`http.IncomingMessage`](http://nodejs.org/api/http.html#http_http_incomingmessage) object) followed by the response `body` (`String`, `Buffer`, or JSON object if the `json` option is supplied). This means that for simple access to the `body` you would probably want to use [`.spread()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#spreadfunction-fulfilledhandler--function-rejectedhandler----promise) instead of [`.then()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#thenfunction-fulfilledhandler--function-rejectedhandler----promise) as seen in the example above.
* **`['body', 'response']`** - This swaps the ordering of the resolved array so that the "body" comes before the "response" object in the resolved array.
* **`'response'`** - This will resolve the promise with just the `response` object
* **`'body'`** - This is probably the one that is going to be the most useful setting for developers looking for a simple interface. Using this means you can easily pass the promises around and know that the fulfillment value is just going to be the `body` object.
```javascript
var url = 'https://www.npm.org',
nock = require('nock')(url);
nock.post('/').reply(200, 'Hello World!');
$http.post(url, { resolve: 'body' }).then(console.log);
```
```
"Hello World"
```
## HTTP Errors
HTTP as Promised exposes a custom `HTTPError` constructor which is extended from the global `Error` constructor. The `HTTPError` constructor also exposes more specific types of `HTTPError` constructors both for ranges/types of HTTP Errors (4xx/client and 5xx/server) as well as status-code-specific HTTP errors (404, 418, 509, etc.). When instanciated, each of these constructors will be a fully-fledged `instanceof Error` with stack traces and everything. In addition to the `message` and `name` properties, instances of `HTTPError` will also include additional HTTP specific information:
```javascript
var $http = require('http-as-promised'),
err = new $http.error[505];
console.log('Error: ', err instanceof Error);
console.log('HTTP Error: ', err instanceof $http.error);
console.log('HTTP 5xx Error: ', err instanceof $http.error['5xx']);
console.log('HTTP Server Error:', err instanceof $http.error['server']);
throw err;
```
```
Error: true
HTTP Error: true
HTTP 5xx Error: true
HTTP Server Error: true
HTTPError: 505 HTTP Version Not Supported
at Object.<anonymous> (/test.js:2:7)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:906:3
{ [HTTPError: 505 HTTP Version Not Supported]
statusCode: 505,
title: 'HTTP Version Not Supported',
summary: 'server does not support the HTTP protocol version',
range: '5xx',
type: 'ServerError',
message: '505 HTTP Version Not Supported' }
```
When an `HTTPError` is the reason why a response was rejected by HTTP as Promised, it will include some additional properties:
```javascript
var $http = require('http-as-promised'),
url = 'https://www.npm.org',
nock = require('nock')(url);
nock.get('/bogus/url').reply(404, 'Cannot find /bogus/url');
$http(url+'/bogus/url').catch(console.log);
```
```
{ [HTTPError: 404 Not Found]
statusCode: 404,
title: 'Not Found',
summary: 'requested resource could not be found',
range: '4xx',
type: 'ClientError',
message: '404 Not Found',
body: 'Cannot find /bogus/url',
response: [Object: IncomingMessage]
options:
{ error: true,
method: 'GET',
uri: 'https://www.npm.org/bogus/url' } }
```
## Catching HTTP Errors
Since we're using Bluebird to construct our promises, handling specific HTTP Errors is a breeze using [`.catch()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#catchfunction-errorclassfunction-predicate-function-handler---promise):
```javascript
var $http = require('http-as-promised'),
url = 'https://stackoverflow.com',
nock = require('nock')(url);
nock.get('/teapot').reply(418);
$http(url+'/teapot')
.catch($http.error[418], function (e){
// Catch 418 I'm A Teapot HTTP Errors
})
.catch($http.error.client, function (e){
// Catch any remaining Client HTTP Errors
})
.catch($http.error['4xx'], function (e){
// An alias for $http.error.client
})
.catch($http.error, function (e){
// Catch any other HTTP Errors that weren't already caught
})
.catch(function (e){
// Catch any other type of Error
})
```
For better stack traces you can enable bluebird's [`longStackTraces`](https://github.com/petkaantonov/bluebird/blob/master/API.md#promiselongstacktraces---void), as well as bluebird's other [error management configuration](https://github.com/petkaantonov/bluebird/blob/master/API.md#error-management-configuration) methods by calling them on HTTP as Promised:
```javascript
$http.longStackTraces();
nock.get('/').reply(420);
denialOfService(url).catch(err);
function denialOfService(url){
return $http(url);
}
```
```
HTTPError: 420 Enhance Your Calm
at Request.HTTP_RESPONSE [as _callback] (http.promise.js:73:25)
at Request.self.callback (node_modules/request/request.js:237:22)
at Request.EventEmitter.emit (events.js:98:17)
at Request.<anonymous> (node_modules/request/request.js:1146:14)
at Request.EventEmitter.emit (events.js:117:20)
at OutgoingMessage.<anonymous> (node_modules/request/request.js:1097:12)
at OutgoingMessage.EventEmitter.emit (events.js:117:20)
at node_modules/nock/lib/request_overrider.js:419:18
at Object._onImmediate (node_modules/nock/lib/request_overrider.js:438:9)
From previous event:
at new Promise (node_modules/bluebird/js/main/promise.js:82:37)
at HTTP (http.promise.js:56:12)
at denialOfService (test.js:12:10)
at Object.<anonymous> (test.js:9:1)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
{ [HTTPError: 420 Enhance Your Calm]
statusCode: 420,
title: 'Enhance Your Calm',
summary: 'Twitter rate limiting',
range: '4xx',
type: 'ClientError',
message: '420 Enhance Your Calm',
body: '',
options: { error: true, method: 'GET', uri: 'https://twitter.com' } }
```
## For those times you really just need request
You can directly access the request module used by the HTTP as Promised module:
```javascript
var $http = require('http-as-promised');
$http.request('http://google.com/doodle.png').pipe(fs.createWriteStream('doodle.png'))
```
[npm-image]: https://img.shields.io/npm/v/http-as-promised.svg?style=flat-square
[npm-url]: https://npmjs.org/package/http-as-promised
[travis-image]: http://img.shields.io/travis/jcready/http-as-promised.svg?style=flat-square
[travis-url]: https://travis-ci.org/jcready/http-as-promised
[coveralls-image]: http://img.shields.io/coveralls/jcready/http-as-promised.svg?style=flat-square
[coveralls-url]: https://coveralls.io/r/jcready/http-as-promised?branch=master
[dependencies-image]: https://img.shields.io/david/jcready/http-as-promised.svg?style=flat-square
[dependencies-url]: https://david-dm.org/jcready/http-as-promised
[codeclimate-image]: https://img.shields.io/codeclimate/github/jcready/http-as-promised.svg?style=flat-square
[codeclimate-url]: https://codeclimate.com/github/jcready/http-as-promised
[bluebird]: https://www.npmjs.org/package/bluebird
[request]: https://www.npmjs.org/package/request
[create-error]: https://www.npmjs.org/package/create-error;