Skip to content

HTTP Request

NPM Version

WinJS provides a built-in plugin solution. Based on axios and VueHookPlus's useRequest, it offers a unified network request and error handling solution.

Note

useRequest is only compatible with Vue 3.

js
import { request, useRequest } from 'winjs';

request;
useRequest;

Setup

  1. Install the plugin
bash
$ npm add @winner-fed/plugin-request -D
bash
$ yarn add @winner-fed/plugin-request -D
bash
$ pnpm add @winner-fed/plugin-request -D
bash
$ bun add @winner-fed/plugin-request -D
  1. Enable the plugin in the .winrc configuration file
ts
import { defineConfig } from 'win';

export default defineConfig({
  plugins: ['@winner-fed/plugin-request'],
  /**
   * @name Request Plugin
   * @description Request plugin based on axios
   * @doc https://winjs-dev.github.io/winjs-docs/plugins/request.html
   */
  request: {},
});

Configuration

Build-time Configuration

js
export default {
  request: {
    dataField: 'data'
  },
};

Build-time configuration allows you to configure the dataField for useRequest, with a default value of data. The main purpose of this configuration is to facilitate direct data consumption by useRequest. If you want to access the raw backend data when consuming data, you need to configure dataField as '' here.

For example, if your backend returns data in the following format:

json
{
  success: true,
  data: 123,
  code: 1
}

Then useRequest can directly consume data, with a value of 123, instead of { success, data, code }.

Runtime Configuration

In src/app.ts, you can configure the request item to set unified and personalized request settings for your project.

ts
import type { RequestConfig } from 'winjs';

export const request: RequestConfig = {
  timeout: 1000,
  // other axios options you want
  errorConfig: {
    errorHandler() {
    },
    errorThrower() {
    }
  },
  requestInterceptors: [],
  responseInterceptors: []
};

All configurations except errorConfig, requestInterceptors, and responseInterceptors are directly passed through to axios request configuration. Rules configured here will apply to all request and useRequest methods.

Below we'll introduce the runtime configuration options for plugin-request. At the end of this section, we'll provide a complete runtime configuration example and describe its functionality in detail.

errorConfig

If you want to set a unified error handling solution for your requests, you can configure it here.

The errorThrower receives data returned by your backend and needs to throw a custom error. You can process the backend data here as needed.

Our request will catch the error thrown by errorThrower and execute your errorHandler method, which receives two parameters: the first parameter is the caught error, and the second parameter is the request opts.

The errorHandler and errorThrower here need to be used together. There's a complete example at the end of the documentation.

If you find this approach to error handling too cumbersome, you can implement your own error handling directly in interceptors.

Note

errorThrower is implemented using responseInterceptors, and its trigger condition is: when data.success is false.

requestInterceptors

Adds request-phase interceptors to the request method.

Pass in an array where each element is an interceptor. They will be registered sequentially on the axios instance in order. The interceptor syntax is consistent with axios request interceptors - it needs to receive the request config as a parameter and return it.

We recommend using RequestConfig, which helps you write your interceptors in a standardized way.

e.g.

ts
const request: RequestConfig = {
  requestInterceptors: [
    // Write a function directly as an interceptor
    (url, options) => {
      // do something
      return { url, options }
    },
    // A tuple, first element is the request interceptor, second element is error handling
    [(url, options) => {
      return { url, options }
    }, (error) => {
      return Promise.reject(error)
    }],
    // Array, omitting error handling
    [(url, options) => {
      return { url, options }
    }]
  ]

}

responseInterceptors

Adds response-phase interceptors to the request method.

Pass in an array where each element is an interceptor. They will be registered sequentially on the axios instance in order. The interceptor syntax is consistent with axios response interceptors. It receives axios response as a parameter and returns it.

We recommend using RequestConfig, which helps you write your interceptors in a standardized way.

e.g.

ts
const request: RequestConfig = {
  responseInterceptors: [
    // Write a function directly as an interceptor
    (response) => {
      // No need for async processing to read response body content, can be read directly from data, some fields can be found in config
      const { data = {} as any, config } = response;
      // do something
      return response
    },
    // A tuple, first element is the request interceptor, second element is error handling
    [(response) => {
      return response
    }, (error) => {
      return Promise.reject(error)
    }],
    // Array, omitting error handling
    [(response) => {
      return response
    }]
  ]

}

Note

We will register interceptors sequentially according to your array order, but their execution order follows axios: for requests, later additions execute first; for responses, later additions execute last.

API

request

Through import { request } from '@@/plugin-request' you can use the built-in request method.

The options that request receives, in addition to passing through all axios config, we've also added several additional properties: skipErrorHandler, getResponse, requestInterceptors, and responseInterceptors.

Example:

typescript
request('/api/user', {
  params: { name: 1 },
  timeout: 2000,
  // other axios options
  skipErrorHandler: true,
  getResponse: false,
  requestInterceptors: [],
  responseInterceptors: [],
}

When you want a specific request to skip error handling, you can set skipErrorHandler to true.

By default, request returns your backend data. If you want to get the complete axios response structure, you can pass { getResponse: true }.

The syntax for requestInterceptors and responseInterceptors is the same as the interceptor syntax in runtime configuration. They register interceptors for the request. The difference is that interceptors registered here are "one-time". Additionally, interceptors written here will be registered after those in runtime configuration.

Note

When you use errorHandler, response interceptors registered here will be ineffective because errors will be thrown in errorHandler.

RequestConfig

This is an interface definition that can help you better configure runtime settings.

typescript
import type { RequestConfig } from 'winjs';

export const request: RequestConfig = {};

Note that you should add type when importing.

useRequest

The plugin has built-in VueHooks/useRequest. You can use this Hook within components to consume data simply and conveniently. Example:

typescript
import { useRequest } from 'winjs';

export default function Page() {
  const { data, error, loading } = useRequest(() => {
    return services.getUserList('/api/test');
  });
  if (loading) {
    return <div>loading
  ...
    </div>;
  }
  if (error) {
    return <div>{ error.message } < /div>;
  }
  return <div>{ data.name } < /div>;
};

In the code above, data is not the data returned by your backend, but the internal data (because the build-time configuration default is 'data').

Runtime Configuration Example

Here's a complete runtime configuration example to help you better set up personalized request solutions for your project.

ts
import { RequestConfig } from './request';

// Error handling solution: error types
enum ErrorShowType {
  SILENT = 0,
  WARN_MESSAGE = 1,
  ERROR_MESSAGE = 2,
  NOTIFICATION = 3,
  REDIRECT = 9,
}

// Response data format agreed upon with backend
interface ResponseStructure {
  success: boolean;
  data: any;
  errorCode?: number;
  errorMessage?: string;
  showType?: ErrorShowType;
}

// Runtime configuration
export const request: RequestConfig = {
  // Unified request settings
  timeout: 1000,
  headers: { 'X-Requested-With': 'XMLHttpRequest' },

  // Error handling
  errorConfig: {
    // Error throwing
    errorThrower: (res: ResponseStructure) => {
      const { success, data, errorCode, errorMessage, showType } = res;
      if (!success) {
        const error: any = new Error(errorMessage);
        error.name = 'BizError';
        error.info = { errorCode, errorMessage, showType, data };
        throw error; // Throw custom error
      }
    },
    // Error receiving and handling
    errorHandler: (error: any, opts: any) => {
      if (opts?.skipErrorHandler) throw error;
      // Errors thrown by our errorThrower
      if (error.name === 'BizError') {
        const errorInfo: ResponseStructure | undefined = error.info;
        if (errorInfo) {
          const { errorMessage, errorCode } = errorInfo;
          switch (errorInfo.showType) {
            case ErrorShowType.SILENT:
              // do nothing
              break;
            case ErrorShowType.WARN_MESSAGE:
              message.warn(errorMessage);
              break;
            case ErrorShowType.ERROR_MESSAGE:
              message.error(errorMessage);
              break;
            case ErrorShowType.NOTIFICATION:
              notification.open({
                description: errorMessage,
                message: errorCode,
              });
              break;
            case ErrorShowType.REDIRECT:
              // TODO: redirect
              break;
            default:
              message.error(errorMessage);
          }
        }
      } else if (error.response) {
        // Axios errors
        // Request was made and server responded with a status code outside the 2xx range
        message.error(`Response status:${error.response.status}`);
      } else if (error.request) {
        // Request was made but no response received
        // `error.request` is an instance of XMLHttpRequest in the browser,
        // and an instance of http.ClientRequest in node.js
        message.error('None response! Please retry.');
      } else {
        // Something happened in setting up the request
        message.error('Request error, please retry.');
      }
    },

  },

  // Request interceptors
  requestInterceptors: [
    (config) => {
      // Intercept request configuration for personalized processing
      const url = config.url.concat('?token = 123');
      return { ...config, url };
    }
  ],

  // Response interceptors
  responseInterceptors: [
    (response) => {
      // Intercept response data for personalized processing
      const { data } = response;
      if (!data.success) {
        message.error('Request failed!');
      }
      return response;
    }
  ]
};

You can also implement your own error handling by writing response interceptors, not necessarily limited to errorConfig.

Released under the MIT License.