0.0.7 • Published 10 years ago

fetch-decorators v0.0.7

Weekly downloads
1
License
MIT
Repository
github
Last release
10 years ago

fetch-decorators

npm package Build Status Commitizen friendly Code Climate Test Coverage npm package

A set of composable ES7 decorators around the fetch api

Automate things request and response body parsing so you don't have to.

No dependency (oh, except a fetch polyfill maybe)

Usage TL;DR:

npm i -S fetch-decorators
class Messages {
  @extractJson
  @bodify
  @fetchify({method: 'POST'})
  post(userId) {
    return `/users/${userId}/messages`;
  }
}

const messages = new Messages();

messages.post('ArnaudRinquin')({
  content: 'Hello World',
  public: true,
  draft: false,
}).then(({response, data}) => {
  // response === the original fetch response
  // data === the JSON object returned by the server
});

Decorators

  • @fetchify: decorates a function returning a url to a fetch call with your options.
  • @bodify: prepare passed data (and extra options) into fetch-ready body options.
  • @extractJson, @extractText, @extractBlob: decorates a function returning a Response to extract its result as json, text or blob.
  • @extractAuto: decorates a function returning a Response to extract its result automatically based on response Content-Type header.

These decorators have been designed to be composable, give it a try!

@fetchify(options:?object)

This helper wraps the original function into a fetch call so it may just return a string, and then be called with optional data, headers, options.

(originalArgs) => url:string

becomes:

(originalArgs) => (options:?object) => fetchResponse:promise

import { fetchify } from 'fetch-decorators';

class Users {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }

  @fetchify()
  get(userId) {
    return `${this.baseUrl}/users/${userId}`;
  }

  @fetchify({
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    }
  })
  create() {
    return `${this.baseUrl}/users`;
  }
}

const userApi = new UserApi('/api');

userApi.createUser()({
  body: JSON.stringify({
    firstName: 'Walter',
    lastName: 'White',
  }
})).then(function(response){
  // Regular `fetch` response
);

userApi.getUser('fakeUserId123')().then(function(response){
  // Regular `fetch` response
});

@bodify

Takes body data and options and calls the decorated function with a proper fetch options object where options.body is passed data as a string.

(options) => orignalResult

becomes:

(originalArgs) => (data:object, extraOptions:?object) => orignalResult(options)

import { bodify } from 'fetch-decorators';

class Messages {
  @bodify
  create(userId) {
    return function(options) {
      return fetch(`/api/users/${userId}/messages`, options);
    };
  }
}

const messages = new Messages();
const messages = {
  content: 'Hello',
  draft: false,
};
const options = { method: 'POST' };

users.create('fakeUserId')(message, options).then(function(response){
  // response === the original fetch response
});

@extractJson, @extractText, @extractBlob

These decorators wrap functions returning a fetch promise with the matching Response extraction function.

(originalArgs) => fetchResponse:promise

becomes:

(originalArgs) => (options:?object) => extractionResult:promise

where the extractionResult promise resolves with : {response:Response, data:any}

import { extractJson } from 'fetch-decorators';

class Users {
  @extractJson
  get(userId) {
    return (options) => fetch(`/api/users/${userId}`, options);
  }
}

const users = new Users();

users.get('userId123')().then(function({response, data}){
  // response === the original fetch response
  // data === the extracted data, here a `user` JSON object
});

@extractAuto

This extractor has the same signature and behaviour as other extractors but will use the Reponse Content-Type header to determine the right Response method to use for data extraction.

Content types are matched this way:

'application/json': 'json'
'text/plain': 'text'
'default': 'blob'

Composition

All these decorators where designed so it's easy to use them together, just stack them! Note: obviously, the order matters.

import {
  fetchify,
  bodify,
  extractJson,
} from 'fetch-decorators';

class Messages {
  @extractJson
  @bodify
  @fetchify({method: 'POST'})
  post(userId) {
    return `/users/${userId}/messages`;
  }

  // Approximate equivalent without decorators
  // Thanks to ES6, the volume of code is roughly the same
  // But the complexity is higher and you'll probably
  // have a lot of code duplication
  mehPost(userId, data, extraOptions) {
    return fetch(`/users/${userId}/messages`, {
      method: 'POST',
      ...extraOptions,
      body: JSON.stringify(data),
    }).then((response) => response.json());
  }
}

const messagesApi = new Messages();

// Request body, as an object
const message = {
  content: 'Hello World',
  public: true,
  draft: false,
};

// Some extra options
const authHeaders = {
  headers: {
    'X-AUTH-MUCH-SECURE': '123FOO456BAR',
  },
};

messagesApi.post('ArnaudRinquin')(message, authHeaders).then(({response, data}) => {
  // response === the original fetch response
  // data === the JSON object returned by the server
});

FAQ

Is it just syntactic sugar?

Yes.

I have a more complex case where my headers are dynamic | I have to change fetch options | whatever . How do I do?

Then you should manually write your call to fetch instead of shoehorning these decorators in your code.

0.0.7

10 years ago

0.0.6

10 years ago

0.0.5

10 years ago

0.0.4

10 years ago

0.0.3

10 years ago

0.0.2

10 years ago

0.0.1

10 years ago

0.0.0

10 years ago