Code Monkey home page Code Monkey logo

drino's Introduction

Drino

Flexible and Reactive HTTP Client

github ci codecov coverage npm version bundle size license

Installation

npm install drino

Table of contents

Basic Usage

Example

import drino from 'drino';

// With Observer's callbacks
drino.get('/cat/meow').consume({
  result: (res) => {
    // handle result 
  },
  error: (err) => {
    // handle error
  },
  finish: () => {
    // after result or error
  }
});

// With Promise async/await
async function getCatInfo() {
  try {
    const res = await drino.get('/cat/meow').consume();
    // handle result 
  }
  catch (err) {
    // handle error
  }
  finally {
    // after result or error
  }
}

Request Methods

drino.get(url, config?)

drino.head(url, config?)

drino.delete(url, config?)

drino.post(url, body, config?)

drino.put(url, body, config?)

drino.patch(url, body, config?)

Request Config

interface RequestConfig {
  // Prefix URL
  // Example : 'https://example.com' OR '/api'
  prefix?: string;

  // HTTP Headers
  headers?: Headers | Record<string, any>;

  // HTTP Parameters
  queryParams?: URLSearchParams | Record<string, any>;

  // Response type that will be passed into :
  // - `result` callback when using Observer
  // - `then` callback when using Promise
  // 
  // If 'auto' is specified, the response type will be inferred from "content-type" response header.
  // 
  // default: 'auto'
  read?: 'auto' | 'object' | 'string' | 'blob' | 'arrayBuffer' | 'formData' | 'none';

  // Wrap response body into a specific Object.
  // - 'response' : HttpResponse
  // - 'none' : nothing
  //
  // default: 'none'
  wrapper?: 'none' | 'response';

  // AbortSignal to cancel HTTP Request with an AbortController.
  // See below in section 'Abort Request'.
  signal?: AbortSignal;

  // Time limit from which the request is aborted.
  //
  // default: 0 (= meaning disabled)
  //
  // See below in section 'Timeout'.
  timeoutMs?: number;

  // Retry a failed request a certain number of times on a specific http status.
  // See below in section 'Request retry'.
  retry?: RetryConfig;

  // Config to inspect download progress.
  // See below in section 'Progress capturing'.
  progress?: ProgressConfig;
}

Instance

Instance can be created to embded common configuration to all requests produced from this instance.

import drino from 'drino';

const instance = drino.create({
  baseUrl: 'http://localhost:8080'
});

instance.get('/cat/meow').consume() // GET -> http://localhost:8080/cat/meow

You can create another instance from a parent instance to inherit its config by using child method :

const child = instance.child({
  requestsConfig: {
    prefix: '/cat'
  }
});

child.get('/meow').consume() // GET -> http://localhost:8080/cat/meow

Instance Config

interface DrinoDefaultConfig {
  // Base URL
  // Example : 'https://example.com/v1/api'
  //
  // default: 'http://localhost'
  baseUrl?: string | URL;

  // Interceptors in order to take action during http request lifecyle.
  //
  // See below in section 'Interceptors'
  interceptors?: {
    beforeConsume?: (req: HttpRequest) => void;
    afterConsume?: (req: HttpRequest) => void;
    beforeResult?: (res: any) => void;
    beforeError?: (errRes: HttpErrorResponse) => void;
    beforeFinish?: () => void;
  };

  // Default requestConfig applied to all requests hosted by the instance
  // See above in section 'Request Config'
  requestsConfig?: RequestConfig;
}

You can override config applied to a drino instance (default import or created instance).

drino.default.baseUrl = 'https://example.com';
drino.default.requestsConfig.headers.set('Custom-Header', 'Cat');

drino.get('/cat/meow').consume(); // GET -> https://example.com/cat/meow (headers = { "Custom-Header", "Cat" })

Plugin

You can use third-party plugin to add more features.

drino.use(myPlugin);

Plugin example : drino-rx

Advanced Usage

Interceptors

You can intercept request, result or error throughout the http request lifecycle.

Interceptors can be passed into instance config .

const instance = drino.create({
  interceptors: {
    // ...
  }
});

Before consume

Intercept a HttpRequest before the request is launched.

Example :

const instance = drino.create({
  interceptors: {
    beforeConsume: (req) => {
      const token = myService.getToken();
      req.headers.set('Authorization', `Bearer ${token}`);
    }
  }
});

After consume

Intercept a HttpRequest just after the response has been received.

Example :

const instance = drino.create({
  interceptors: {
    afterConsume: (req) => {
      console.info(`Response received from ${req.url}`);
    }
  }
});

Before result

Intercept a result before being passed into result callback (Observer) or into then() arg callback (Promise).

Example :

const instance = drino.create({
  interceptors: {
    beforeResult: (res) => {
      console.info(`Result : ${res}`);
    }
  }
});

Before error

Intercept an error before being passed into error callback (Observer) or into catch() arg callback (Promise).

Example :

const instance = drino.create({
  interceptors: {
    beforeError: (errorResponse) => {
      if (errorResponse.status === 401) {
        myService.clearToken();
        myService.navigateToLogin();
      }
      else {
        console.error(`Error ${errorResponse.status} from ${errorResponse.url} : ${errorResponse.error}`);
      }
    }
  }
});

Before finish

Intercept before being passed into finish callback (Observer) or into finally() arg callback (Promise).

Example :

const instance = drino.create({
  interceptors: {
    beforeFinish: () => {
      console.info('Finished');
    }
  }
});

Progress Capturing

Download

You can inspect download progress with downloadProgress observer's callback.

Progress capturing can be disabled for the instance or for the request by set inspect: false into ProgressConfig in RequestConfig.

interface ProgressConfig {
  download?: {
    // Enable download progress.
    //
    // default : true
    inspect?: boolean;
  };
}

A StreamProgressEvent is passed to downloadProgress callback for each progress iteration.

export interface StreamProgressEvent {
  // Total bytes to be received or to be sent;
  total: number;

  // Total bytes received or sent.
  loaded: number;

  // Current percentage received or sent.
  // Between 0 and 1.
  percent: number;

  // Current speed in bytes/ms.
  // Equals to `0` for the first `iteration`.
  speed: number;

  // Estimated remaining time in milliseconds to complete the progress.
  // Equals to `0` for the first `iteration`.
  remainingMs: number;

  // Current chunk received or sent.
  chunk: Uint8Array;

  // Current iteration number of the progress.
  iteration: number;
}

Example :

drino.get('/cat/image').consume({
  download: ({ loaded, total, percent, speed, remainingTimeMs }) => {
    const remainingSeconds = remainingTimeMs / 1000;
    const speedKBs = speed / 1024 * 1000;

    console.info(`Received ${loaded} of ${total} bytes (${Math.floor(percent * 100)} %).`);
    console.info(`Speed ${speedKBs.toFixed(1)} KB/s | ${remainingSeconds.toFixed(2)} seconds remaining.`);

    if (loaded === total) console.info('Download completed.');
  },
  result: (res) => {
    // handle result
  }
});

Pipe Methods

Before calling consume() method, you can chain call methods to modify or inspect the current value before being passed into final callbacks.

Transform

Change the result value.

Example :

drino.get('/cat/meow')
  .transform((res) => res.name)
  .consume({
    result: (name) => {
      // handle value
    },
  });

Check

Read the result value without changing it.

Example :

drino.get('/cat/meow')
  .check((res) => console.log(res)) // { name: "Gaïa" }
  .consume({
    result: (res) => {
      // handle value
    }
  });

Report

Read the error value without changing it.

Example :

drino.get('/cat/meow')
  .report((err) => console.error(err.name)) // "ErrorName" 
  .consume({
    result: (res) => {
      // handle value
    }
  });

Finalize

Finalize when controller finished.

Example :

drino.get('/cat/meow')
  .finalize(() => console.log('Finished')) // "Finished"
  .consume({
    result: (res) => {
      // handle value
    }
  });

Follow

Make another http request sequentially that depends on previous one.

Example :

drino.get('/cat/meow')
  .follow((cat) => drino.get(`/dog/wouaf/cat-friend/${cat.name}`))
  .consume({
    result: (res) => {
      // handle value
    }
  });

Methods combination

Pipe methods can be combined.

Example :

drino.get('/cat/meow')
  .check((cat) => console.log(cat)) // { name: "Gaïa" }
  .transform((cat) => cat.name)
  .check((name) => console.log(name)) // "Gaïa"
  .consume({
    result: (name) => {
      // handle value
    }
  });

Request Annulation

AbortController

You can cancel a send request (before receive response) by using AbortSignal and AbortController.

Example :

const controller = new AbortController();
const signal = controller.signal;

setTimeout(() => controller.abort('Too Long'), 2_000);

// With Observer
drino.get('/cat/meow', { signal }).consume({
  result: (res) => {
    // handle result
  },
  abort: (reason) => {
    console.error(reason); // "Too Long"
    // handle abort reason
  }
});

// With Promise async/await
async function getCatInfo() {
  try {
    const result = await drino.get('/cat/meow', { signal }).consume();
    // handle result
  }
  catch (err) {
    if (signal.aborted) {
      const reason = signal.reason;
      console.error(reason); // "Too Long"
      // handle abort reason
    }
  }
}

Timeout

You can cancel a send request after a certain time using a timeoutMs (timeout in milliseconds).

Example :

// With Observer
drino.get('/cat/meow', { timeoutMs: 2_000 }).consume({
  result: (res) => {
    // handle result
  },
  error: (err) => {
    console.error(err.message); // "The operation timed out."
    // handle timeout error
  },
});

// With Promise async/await
async function getCatInfo() {
  try {
    const res = await drino.get('/cat/meow', { timeoutMs: 2_000 }).consume();
    // handle result
  }
  catch (err) {
    const message = err.message;
    console.error(message); // "The operation timed out."
    // handle timeout error
  }
}

Request Retry

You can automatically retry failed request on conditions.

interface RetryConfig {
  // Maximum retries to do on failed request.
  //
  // default: 0
  max: number;

  // Use the "Retry-After" response Header to know how much time it waits before retry.
  //
  // default: true
  withRetryAfter?: boolean;

  // Specify the time in millisecond to wait before retry.
  //
  // Work only if `withRetryAfter` is `false` or if "Retry-After" response header is not present.
  //
  // default: 0
  withDelayMs?: number;

  // HTTP response status code to filter which request should be retried on failure.
  //
  // default: [408, 429, 503, 504]
  onStatus?: number[] | { start: number, end: number } | { start: number, end: number }[];

  // Http method to filter which request should be retried on failure.
  // Can only be used for instance configuration.
  // 
  // "*" means all methods.
  //
  // Example: ["GET", "POST"]
  // 
  // default: "*"
  onMethods?: '*' | string[];
}

Example :

const instance = drino.create({
  requestsConfig: {
    retry: { max: 2, onMethods: ['GET'] }
  }
});

instance.get('/my-failed-api', {
  retry: { max: 1 }
});

When using Observer you can use the retry callback to get info about current retry via RetryEvent.

export interface RetryEvent {
  // Current retry count.
  count: number;

  // Error that causes the retry.
  error: any;

  // Function to abort retrying.
  abort: (reason?: any) => void;

  // Current retry delay.
  delay: number;
}

Example :

instance.get('/my-failed-api').consume({
  retry: ({ count, error, abort }) => {
    console.log(`Will retry for the ${count} time caused by the error : ${error}.`);
    if (count > 2) abort('Too many retries.');
  },
  abort: (reason) => {
    console.log(reason); // "Too many retries."
  }
});

React Native support

Install react-native-url-polyfill and add the following line at the top of your index.js file :

import 'react-native-url-polyfill/auto';

License

MIT © Rémy Abitbol.

drino's People

Contributors

dependabot[bot] avatar remscodes avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.