
# hifetch

[< Back to Project WebCube](https://github.com/dexteryy/Project-WebCube/)

[![NPM Version][npm-image]][npm-url]
<!-- [![Build Status][travis-image]][travis-url]
[![Dependencies Status][dep-image]][dep-url] -->

[![Nodei][nodei-image]][npm-url]

[npm-image]: https://img.shields.io/npm/v/hifetch.svg
[nodei-image]: https://nodei.co/npm/hifetch.png?downloads=true
[npm-url]: https://npmjs.org/package/hifetch
<!-- [travis-image]: https://img.shields.io/travis/dexteryy/hifetch/master.svg
[travis-url]: https://travis-ci.org/dexteryy/hifetch
[dep-image]: https://david-dm.org/dexteryy/hifetch.svg
[dep-url]: https://david-dm.org/dexteryy/hifetch -->

A minimal higher-level wrapper around [Fetch](https://github.com/github/fetch) API

Built on top of [isomorphic-fetch](https://www.npmjs.com/package/isomorphic-fetch)

## Get started

```
npm install hifetch
```

Hifetch is optimized for JSON API.

You can fetch, process and consume JSON response with minimal custom options.

```javascript
import hifetch from 'hifetch';

// GET http://www.mydomain.com/users/1/?mobile=true
// + Response 200 (application/json; charset=utf-8)
//   + Headers
//     X-API-Version: 1.0
//   + Body
//     {
//       "status": 0,
//       "username": "xxx"
//     }
hifetch({
  url: 'http://www.mydomain.com/users/1/',
  query: {
    mobile: true,
  },
  jwtToken: '...', // Authorization header with the Bearer authentication
  headers: { // Custom request headers
    'X-Requested-With': 'XMLHttpRequest',
  },
  timeout: 10000,
}).send().then(result => {
  // result:
  // {
  //   "status": 0,
  //   "username": "xxx"
  // }
}).catch(errorResult => {
  // errorResult:
  // {
  //   "status": 5,
  //   "message": "[TIMEOUT ERROR] limit: 10000"
  // }
  // or
  // {
  //   "status": 1,
  //   "message": "[INTERNAL JS ERROR] error message that generated by Hifetch own code"
  //   "error": Error,
  // }
});
```

By default, Hifetch supports JSON response in [Goolge JSON Style](https://google.github.io/styleguide/jsoncstyleguide.xml)

```javascript
// GET http://www.mydomain.com/users/wrong-id/
// + Response 400 (application/json; charset=utf-8)
//   + Body
//    {
//      "error": {
//        "code": 400,
//        "message": "User Not Found", // human readable message
//        "errors": [{
//          "reason": "ResourceNotFoundException", // Unique Identifier
//          "message": "User Not Found", // human readable message
//        }]
//      }
//    }
hifetch({
  url: 'http://www.mydomain.com/users/wrong-id/',
  disableStatusValidator: true,
}).send().then(result => {
  // ...
}).catch(errorResult => {
  // errorResult:
  // {
  //   "status": 3,
  //   "message": `[REMOTE ERROR] reason: ResourceNotFoundException, code: 400, message: User Not Found`,
  //   "originMessage": "User Not Found",
  //   "reason": "ResourceNotFoundException",
  //   "errors": [{ ... }],
  // }
});

// GET http://www.mydomain.com/users/1/
// + Response 200 (application/json; charset=utf-8)
//   + Body
//    {
//      "data": {
//        "otherDataA": ...
//        "otherDataB": ...
//      },
//    }
hifetch({
  url: 'http://www.mydomain.com/users/1/',
}).send().then(result => {
  // result:
  // {
  //   "otherDataA": ...
  //   "otherDataB": ...
  // }
});
```

Hifetch also supports JSON response with [unix-style exit status](https://en.wikipedia.org/wiki/Exit_status)

A zero exit status indicates succeeded. A nonzero exit status indicates failure (Hifetch only supports negative number)

You can use this style by `responseStyle` option

```javascript
// GET http://www.mydomain.com/users/wrong-id/
// + Response 200 (application/json; charset=utf-8)
//   + Body
//     {
//       "status": -1, // Unique Identifier
//       "message": "User Not Found" // human readable message
//       "otherData": ...
//     }
hifetch({
  url: 'http://www.mydomain.com/users/wrong-id/',
  responseStyle: 'unix',
}).send().then(result => {
  // ...
}).catch(errorResult => {
  // errorResult:
  // {
  //   "status": -1,
  //   "message": "User Not Found"
  //   "otherData": ...
  // }
});

// GET http://www.mydomain.com/users/1/
// + Response 200 (application/json; charset=utf-8)
//   + Body
//     {
//       "status": 0,
//       "otherDataA": ...
//       "otherDataB": ...
//     }
hifetch({
  url: 'http://www.mydomain.com/users/1/',
  query: {
    mobile: false,
  },
  responseStyle: 'unix',
}).send().then(result => {
  // result:
  // {
  //   "status": 0,
  //   "otherDataA": ...
  //   "otherDataB": ...
  // }
});
```

You can disable this feature by `disableResponseStyle` option

HTTP status code validation

```javascript
hifetch({
  url: 'http://www.mydomain.com/nonexistent-url/',
  // the following code is the default value of `validateStatus`, so it should be omitted
  validateStatus(status) {
    return status >= 200 && status < 300;
  },
}).send().then(result => {
  // ...
}).catch(errorResult => {
  // errorResult:
  // {
  //   "status": 2,
  //   "message": "[FETCH ERROR] 404 Not Found"
  //   "httpStatus": 404,
  //   "httpStatusText": "Not Found",
  // }
});
```

Error response caused by user-defined hook function (`validateStatus`, `parser`, `handler`, `success`, `error`)

```javascript
hifetch({
  url: 'http://www.mydomain.com/users/1/',
  handler() {
    throw new Error();
  },
}).send().then(result => {
  // ...
}).catch(errorResult => {
  // errorResult:
  // {
  //   "status": 4,
  //   "message": "[CUSTOM JS ERROR] error message that caused by user-defined hook function"
  //   "error": Error,
  // }
});
```

By default, Hifetch directly uses response body as the result.

When the response body is JSON object format, you can merge some response headers into the result.

```javascript
// GET http://www.mydomain.com/users/1/
// + Response 200 (application/json; charset=utf-8)
//   + Headers
//     X-API-Version: 1.0
//   + Body
//     {
//       "status": 0,
//       "username": "xxx"
//     }
hifetch({
  url: 'http://www.mydomain.com/users/1/',
  mergeHeaders: {
    version: 'X-API-Version',
  },
}).send().then(res => {
  // result:
  // {
  //   "status": 0,
  //   "username": "xxx"
  //   "version": "1.0",
  // }
});
 ```

When `enableMeta` is true, the result is a meta object.

```javascript
// GET http://www.mydomain.com/users/1/
// + Response 200 (application/json; charset=utf-8)
//   + Headers
//     X-API-Version: 1.0
//   + Body
//     {
//       "status": 0,
//       "username": "xxx"
//     }
hifetch({
  url: 'http://www.mydomain.com/users/1/',
  mergeHeaders: {
    version: 'X-API-Version',
  },
  enableMeta: true,
  // enableResponseObject: true,
}).send().then(res => {
  // result:
  // {
  //   "status": 0,
  //   "httpStatus": 200,
  //   "httpStatusText": "",
  //   "url": "http://www.mydomain.com/users/1/",
  //   "headers": {
  //     "x-api-version": "1.0",
  //     "content-type": "application/json; charset=utf-8",
  //   },
  //   "data": {
  //     "status": 0,
  //     "username": "xxx"
  //   },
  //   "response": ResponseObject, // if `enableResponseObject` is true
  // }
});

hifetch({
  url: 'http://www.mydomain.com/users/1/',
  mergeHeaders: {
    version: 'X-API-Version',
  },
  filterHeaders: {
    'content-type': true,
  },
  enableMeta: true,
}).send().then(res => {
  // result:
  // {
  //   "status": 0,
  //   "httpStatus": 200,
  //   "httpStatusText": "",
  //   "url": "http://www.mydomain.com/users/1/",
  //   "headers": {
  //     "content-type": "application/json; charset=utf-8",
  //   },
  //   "data": {
  //     "status": 0,
  //     "username": "xxx"
  //   },
  // }
});
```

Use custom `responseType` and `parser` to fetch HTML string:

```javascript
hifetch({
  url: 'http://www.mydomain.com/profile/1/',
  responseType: 'html', // or 'text/html',
  parser(response) { // you muse provide a custom parser for Fetch API's response
    return response.text();
  },
```

Post JSON with urlencoded body

```javascript
hifetch({
  url: 'http://www.mydomain.com/users/1/actions/createByUrlencoded',
  method: 'post',
  query: {
    mobile: true,
  },
  data: { // request body
    name: 'yy',
  },
```

Post JSON with JSON-encoded body

```javascript
hifetch({
  url: 'http://www.mydomain.com/users/1/actions/createByJSON',
  method: 'post',
  query: {
    mobile: true,
  },
  data: { // request body
    name: 'yy',
  },
  dataType: 'json', // or 'application/json',
```

Post form-data body

```javascript
const formData = new FormData();
formData.append('name', 'yy');

hifetch({
  url: 'http://www.mydomain.com/users/1/actions/createByFormData',
  method: 'post',
  query: {
    mobile: true,
  },
  data: formData, // request body
```

or

```javascript
hifetch({
  url: 'http://www.mydomain.com/users/1/actions/createByFormData',
  method: 'post',
  query: {
    mobile: true,
  },
  data: { // request body
    name: 'yy',
  },
  dataType: 'form', // or 'multipart/form-data',
```

Post form-data body with files

```javascript
hifetch({
  url: 'http://www.mydomain.com/users/1/actions/createByFormData',
  method: 'post',
  query: {
    mobile: true,
  },
  data: { // request body
    name: 'yy',
    avatar: imageFile,
  },
```

or

```javascript
const formData = new FormData();
formData.append('name', 'yy');
formData.append('photos', imageFile1);
formData.append('photos', imageFile2);

hifetch({
  url: 'http://www.mydomain.com/users/1/actions/createByFormData',
  method: 'post',
  query: {
    mobile: true,
  },
  data: formData, // request body
```

Custom validator and processor for response data

```javascript
hifetch({
  //...
  handler: (res, headers) => {
    if (res.xx) {
      throw new Error(res.xx);
    }
    return Object.assign(headers, data);
  },
```

### Options

#### Request

* `url`
  * Required
* `method`
  * Default: `'get'`
* `query`
  * Type: plain object
* `dataType`
  * Default: `'application/x-www-form-urlencoded'`
  * Custom `'Content-Type'` header
* `data`
  * Type: plain object, FormData object or String
  * When using FormData, `dataType` can be omitted (always be 'multipart/form-data')
* `FormData`
  * Required in node.js environment
  * [FormData class](https://www.npmjs.com/package/form-data)
* `headers`
  * Other custom request headers
* `jwtToken`
  * Add JWT header

#### Response

* `responseType`
  * Default: `'application/json'`
  * Custom `'Accept'` header
* `responseStyle`
  * Type: 'google' | 'unix'
  * Default: `'google'`
  * Automatically processing the JSON response based on special style guide
* `mergeHeaders`
  * Type: plain object
  * Default: null
  * Merge some response headers into the JSON object reponse
  * Example: `{ poweredBy: 'X-Powered-By' }`
* `filterHeaders`
  * Type: plain object
  * Default: null
  * Pick some response headers.
  * Example: `{ 'X-Powered-By': true }` or `{ 'x-powered-by': true }`
* `timeout`
  * Millisecond
* `enableCookies`
  * Type: Boolean
  * Default: false
  * Automatically send cookies
* `disableCORS`
  * Type: Boolean
  * Default: false
* `disableStatusValidator`
  * Type: Boolean
  * Default: false
  * Skip the `validateStatus` hook
* `disableResponseStyle`
  * Type: Boolean
  * Default: false
  * Disable the automatic processing (like 'REMOTE ERROR') triggered by the special format of JSON response
* `enableMeta`
  * Type: Boolean
  * Default: false
  * Embed response body into a meta object
* `enableResponseObject`
  * Type: Boolean
  * Default: false
  * Like `enableMeta`, plus the response object

#### Hooks

* `validateStatus`
  * Default: `status >= 200 && status < 300`
  * Custom validator for acceptable HTTP response status code
* `parser`
  * Default: `response => response.json()`
* `handler`
  * Custom validator and processor for response data
* `success`
  * Default: `res => res`
* `error`
  * Default: `res => Promise.reject(res)`

### Actions

* `send()`
  * Return: Promise
  * Automatically nterrupt latest outstanding request each time it's called
* `cancel()`
  * Interrupt latest outstanding request, nothing happen
* `error()`
  * Interrupt latest outstanding request, trigger error callback

See [actions.spec.js](https://github.com/dexteryy/hifetch/blob/master/tests/actions.spec.js) for more detail.

## Development

```
npm run lint
```

```
npm run test
```
