legions-request

基础请求库

Usage no npm install needed!

<script type="module">
  import legionsRequest from 'https://cdn.skypack.dev/legions-request';
</script>

README

legions-request

此库已经过严格的单元测试,请放心使用

Promise based HTTP client for the browser and node.js

what is legions-request?

legions-request 是一个支持浏览器及服务端的 http 请求工具库

Features

  • 高版本浏览器直接基于 fetch
  • 低版本不支持 fetch 时,自动降级兼容 fetch 所有特性
  • nodejs 中通过 http 发送请求
  • Supports the Promise API
  • 支持注入 request,respone,reject 等拦截器
  • 超时自动中断请求

Installing

Using npm and yarn :

npm i legions-requset -S

or

yarn add legions-requset -S

Using jsDelivr CDN:

<script src="dist/index.iife.js"></script>

Example

import { get, post } from 'legions-request';
get({
  url: '/user?ID=12345',
}).then(function (response) {
  // handle success
  console.log(response);
});

POST Requests

How to perfrom POST requests with legions-request

Performing a POST request

import { get, post } from 'legions-request';
post({
  url: '/user',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone',
  },
}).then(function (response) {
  console.log(response);
});

Performing multiple concurrent requests

function getUserAccount() {
  return get({
    url: '/user/12345',
  });
}

function getUserPermissions() {
  return get({
    url: '/user/12345/permissions',
  });
}

Promise.all([getUserAccount(), getUserPermissions()]).then(function (results) {
  const acct = results[0];
  const perm = results[1];
});

legions-request api

The legions-request API Reference

legionFetch.instance.request(config)

// Send a POST request
legionFetch.instance.request({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone',
  },
});
// GET request for remote image in node.js
legionFetch.instance
  .request({
    method: 'get',
    url: 'http://bit.ly/2mTM3nY',
    responseType: 'stream',
  })
  .then(function (response) {
    response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'));
  });

Request method aliases

For convenience aliases have been provided for all supported request methods.

  • legionFetch.instance.request({method: 'get'})
  • legionFetch.instance.request({method: 'delete'})
  • legionFetch.instance.request({method: 'head'})
  • legionFetch.instance.request({method: 'options'})
  • legionFetch.instance.request({method: 'post'})
  • legionFetch.instance.request({method: 'put'})
  • legionFetch.instance.request({method: 'patch'})
  • legionFetch.instance.get(config)
  • legionFetch.instance.post(config)

The legions-request Instance

Creating an instance

You can create a new instance of axios with a custom config.

import { legionFetch } from 'legions-request';
const instance = legionFetch.create();

Instance methods

  • instance.request({method: 'get'})
  • instance.request({method: 'delete'})
  • instance.request({method: 'head'})
  • instance.request({method: 'options'})
  • instance.request({method: 'post'})
  • instance.request({method: 'put'})
  • instance.request({method: 'patch'})
  • instance.get(config)
  • instance.post(config)

Request Config

These are the available config options for making requests. Only the url is required. Requests will default to GET if method is not specified.

{
  // `url` is the server URL that will be used for the request
  url: '/user',

  // `method` is the request method to be used when making the request
  method: 'get', // default

  // `baseURL` will be prepended to `url` unless `url` is absolute.
  // It can be convenient to set `baseURL` for an instance of axios to pass relative URLs
  // to methods of that instance.
  baseURL: 'https://some-domain.com/api/',

  // 发送前对配置数据进行处理
  transformRequest: function (config, url) {
    // Do whatever you want to transform the data

    return config;
  },


  // `headers` are custom headers to be sent
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` are the URL parameters to be sent with the request
  // Must be a plain object or a URLSearchParams object
  // NOTE: params that are null or undefined are not rendered in the URL.
  params: {
    ID: 12345
  },

  // `data` is the data to be sent as the request body
  // Only applicable for request methods 'PUT', 'POST', 'DELETE , and 'PATCH'
  // When no `transformRequest` is set, must be of one of the following types:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - Browser only: FormData, File, Blob
  // - Node only: Stream, Buffer
  data: {
    firstName: 'Fred'
  },

  // syntax alternative to send data into the body
  // method post
  // only the value is sent, not the key
  data: 'Country=Brasil&City=Belo Horizonte',

  // `timeout` specifies the number of milliseconds before the request times out.
  // If the request takes longer than `timeout`, the request will be aborted.
  timeout: 1000, // default is `0` (no timeout)

  // should be made using credentials
  // "include" | "omit" | "same-origin"
  credentials: 'include', // default

  // `responseType` indicates the type of data that the server will respond with
  // options are: 'arraybuffer', 'document', 'json', 'text', 'stream'
  //   browser only: 'blob'
  responseType: 'json', // default


  // `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` is the name of the http header that carries the xsrf token value
  xsrfHeaderName: 'X-XSRF-TOKEN', // default


  // `maxRedirects` defines the maximum number of redirects to follow in node.js.
  // If set to 0, no redirects will be followed.
  maxRedirects: 5, // default
  // data 数据是否默认转json,默认true,只有在data是对象时才会执行
  isDataToConvertJson:boolean,

  /**
     * A cryptographic hash of the resource to be fetched by request. Sets request's integrity.
     */
    integrity?: string,
    /**
     * A boolean to set request's keepalive.
     */
    keepalive?: boolean,
    /**
     * A string to set request's method.
     */
    method?: string,
    /**
     * A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode.
     */
    mode?: RequestMode,
    /**
     * A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect.
     */
    redirect?: RequestRedirect,
    /**
     * A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer.
     */
    referrer?: string,
    /**
     * A referrer policy to set request's referrerPolicy.
     */
    referrerPolicy?: ReferrerPolicy,
    /**
     * An AbortSignal to set request's signal.
     */
    signal?: AbortSignal | null,
}

拦截器

在请求或响应被 then 处理前拦截它们。

use

legionFetch.instance.register({
  request: (config) => {
    config.credentials = 'omit';
    return config;
  },
  response: (res) => {
    return res;
  },
  responseReject: (res) => {
    return res;
  },
});

默认注入的拦截器

request

const defaultRequest = [
  {
    request: (configs /**  传入进来的配置参数*/) => {
      //注册拦截请求, 默认所有请求返回json格式
      /**  设置默认request */
      let options = {
        headers: {
          'content-type': 'application/json',
        },
        credentials: 'include',
      };
      /**  使用传入进来的配置覆盖默认配置*/
      let config = mergeConfig(options, { ...tranfromOptions(configs.options), url: configs.url });
      return config;
    },
  },
  {
    request: (configs: IRequestConfigs) => {
      //注册请求, 请求默认携带验证参数
      let options = {
        /**  超时时间*/
        timeout: 50000,
      };
      let config = mergeConfig(options, configs);
      return config;
    },
  },
];

response

defaultResponse = [
  {
    response: (response, config: IRequestConfigs) => {
      return response;
    },
  },
  {
    //response已经被反序列化, 返回了对应对象
    response: (response, config: IRequestConfigs) => {
      if (config && config.responseType === 'json') {
        const res = response.json();
        return res;
      }
      return response;
    },
  },
];

Reject

[
  {
    //@ts-ignore
    responseReject: (response, config) => {
      let error: any = {};
      if (response instanceof FetchError) {
        error = {
          name: response.name,
          type: response.type,
          code: response.code,
          message: response.message,
        };
      } else if (response instanceof Error) {
        error = {
          name: response.name,
          type: response.name,
          stack: response.stack,
          message: response.message,
        };
      }
      return Promise.resolve(
        new ResponseResult({
          success: false,
          message: JSON.stringify(error),
          code: error.type === 'request-timeout' ? DataRequestStateEnum.Timeout.toString() : '500',
          data: null,
        }),
      );
    },
  },
];

lrequestContainer

此方法初步方案借鉴社区方案实现,后续会迭代完善

所做事情

  1. 保证在相同的 HTTP 请求在同一个时间段内只有一个请求发生
  2. 所有的请求都经过这个容器,这样有助于我们去管理所有发生的请求

举例场景

  1. 单应用中,我们做组件开发,每一个组件应该独立完成自己的工作. 那么组件之间在绝大多数情况下是没有任何通信的. 那么如果一个页面存在任意 2 个或多个组件在几 乎同一个时间内发出相同的 http 请求, 这样无疑会浪费且会增加服务器负载. 为了处理这个问题, lrequestContainer(请求容器)保证同一个时刻有且仅有一个 http 请求, 无论组件发送多少请求.

  2. 对于单页面应用, 因为所有请求都经过容器,在我们发送一个耗时请求时,这个时候我们想离开当前页面. 但是任何发生在先前页面的请求会难以管理/取消 . lrequestContainer(请求容器)提供了一个非常好的方式让开发者能 轻易管理到这些请求.

use

import { lrequestContainer } from 'legions-request';
//type RequestParamType = {url: 'www.example.com/api', data: {token:123}, method: 'get'};
//promiseFn:() =>Promise<any>, e.g.  promiseFn = ()=>$.ajax(...);
const promiseState = requestContainer.put(JSON.stringify(requestParam), promiseFn);

并发相同请求

import { lrequestContainer } from 'legions-request';

function httpRequest(duration: number): Promise<void> {
  return new Promise(() => setTimeout(resolve, number));
}

const requestContainer = RequestContainer.getInstance();

//假设第一个请求请求2秒
const requestParam1 = { url: 'www.example.com/api', data: { token: 123 }, method: 'get' };
const promiseFn1 = () => httpRequest(2000);
const promiseState1 = requestContainer.put(JSON.stringify(requestParam1), promiseFn1);

//假设第二个请求4秒, 其实并没有关系, 因为第一个请求会决定这后面请求只会是2秒完成
const requestParam2 = { url: 'www.example.com/api', data: { token: 123 }, method: 'get' };
const promiseFn2 = () => httpRequest(4000);
const promiseState2 = requestContainer.put(JSON.stringify(requestParam2), promiseFn2);

/**
 * 由于他们使用相同参数请求, 那么我们可以认为他们是相同的请求. 所以如果<请求1>发出,
 * 请求2会被拦截, 并指向请求1, 也就是说他们两会共享同一个promise.
 * 那么这样就能确保整一个请求周期有且只有一个真正的对外请求
 */

版权

MIT