jcready/http-as-promised

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# 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;