Идеальное решение для внешних запросов данных

внешний интерфейс
Идеальное решение для внешних запросов данных

Запрос данных - очень важная часть нашей разработки. Как элегантно выполнить абстрактную обработку - непростая задача, и это также то, что часто упускается из виду. Если это не обрабатывается должным образом, дублирующиеся коды будут разбросаны повсюду, и обслуживание стоимость будет чрезвычайно высока.

Поэтому нам необходимо разобраться с аспектами, связанными с запросом данных, и иметь общий контроль над ним, чтобы разработать масштабируемое решение.

анализ случая

Ниже мы используемaxiosЭта библиотека запросов объясняет это.

Если мы выдадимPOSTзапрос, что-то вроде этого:

axios.post('/user/create', { name: 'beyondxgb' }).then((result) => {
  // do something
});

Позже выяснилось, что необходимо предотвратитьCSRF, то нам нужно в запросеheadersплюсX-XSRF-TOKEN, поэтому становится так:

axios.post('/user/create', { name: 'beyondxgb' }, {
  headers: {
    'X-XSRF-TOKEN': 'xxxxxxxx',
  },
}).then((result) => {
  // do something
});

В это время вы можете обнаружить, что каждый раз, когда вы инициируетеpostВсе ли запросы должны быть настроены так? Поэтому я подумал об извлечении этой части конфигурации и абстрагировании такого метода:

function post(url, data, config) {
  return axios.post(url, data, {
    headers: {
      'X-XSRF-TOKEN': 'xxxxxxxx',
    },
    ...config,
  });
}

Поэтому нам нужноконфигурация параметровАннотация.

Когда дело доходит до тестового процесса, обнаруживается, что запрос с сервера не всегда возвращаетуспехДа что мне делать? тогдаcatchПроработай это:

post('/user/create', { name: 'beyondxgb' }).then((result) => {
  // do something
}).catch((error) => {
  // deal with error
  // 200
  // 503
  // SESSION EXPIRED
  // ...
});

Я всегда чувствую, что что-то не так, когда записываю.Оказывается, столько случаев ошибок запроса.Во всем моем проекте много мест для запроса данных.Эта часть кода должна быть общей и абстрактной!

function dealWithRequestError(error) {
  // deal with error
  // 200
  // 503
  // SESSION EXPIRED
  // ...
}
function post(url, data, config) {
  return axios.post(url, data, {
    headers: {
      'X-XSRF-TOKEN': 'xxxxxxxx',
    },
    ...config,
  }).catch(dealWithRequestError);
}

Поэтому нам нужноОбработка исключенийАннотация.

Перед запуском проекта бизнес-сторона может выдвигать требования к стабильности, в это время нам необходимо отслеживать запрос и фиксировать успех и неудачу запроса интерфейса. Аналогично пишем и эту часть кода в паблик, вот так:

function post(url, data, config) {
  return axios.post(url, data, {
    headers: {
      'X-XSRF-TOKEN': 'xxxxxxxx',
    },
    ...config,
  }).then((result) => {
    // 记录成功情况
    ...
    return result;
  })
  .catch((error) => {
    // 记录失败情况
    ...
    return dealWithRequestError(error);
  );
}

Поэтому нам нужномониторинг запросовАннотация.

Дизайн

Облицовка простой сверхуpostВ примере запроса мы видим, что запрос данных в основном включает три аспекта.конфигурация параметров,Обработка исключенийа такжемониторинг запросов. Обработка приведенного выше примера все еще относительно грубая, и все еще требуется общая организация кода и многоуровневость.

конфигурация параметров

Во-первых, мы имеем дело с конфигурацией параметров.Приведенный выше пример только дляpostЗапрос был проанализирован, фактически, для других, таких какget,putТем не менее, мы можем обрабатывать эти запросы единообразно.

request.js

import axios from 'axios';

// The http header that carries the xsrf token value { X-XSRF-TOKEN: '' }
const csrfConfig = {
  'X-XSRF-TOKEN': '',
};
// Build uniform request
async function buildRequest(method, url, params, options) {
  let param = {};
  let config = {};
  if (method === 'get') {
    param = { params, ...options };
  } else {
    param = JSON.stringify(params);
    config = {
      headers: {
        ...csrfConfig,
      },
    };
    config = Object.assign({}, config, options);
  }
  return axios[method](url, param, config);
}

export const get = (url, params = {}, options) => buildRequest('get', url, params, options);
export const post = (url, params = {}, options) => buildRequest('post', url, params, options);

В этом случае мы выставляемgetа такжеpostметод, другие запросы аналогичны, здесь только используйтеgetа такжеpostНапример, входные параметрыадрес API,данныеа такжеРасширенная конфигурация.

Обработка исключений

На самом деле сценарий обработки исключений будет сложнее, а не простоcatch, часто в сопровожденииБизнес-логикаа такжеВзаимодействие с пользовательским интерфейсом, исключение в основном имеет два аспекта,глобальное исключениеа такжеделовое исключение.

глобальное исключение, можно также сказать, что это общее исключение, например, сервер возвращает 503, сетевое исключение, ошибка входа в систему, отсутствие разрешения и т. д. Эти исключения предсказуемы и управляемы, если формат согласован с сервером, а исключение захватывается и отображается.

деловое исключение, относится к событиям, тесно связанным с бизнес-логикой, таким как сбой отправки, сбой проверки данных и т. д. Эти исключения часто различаются для каждого интерфейса, и ошибки необходимо отображать по отдельности, поэтому эта часть может обрабатываться неодинаково. отправить сообщение об ошибке отображенияViewслой для реализации.

С точки зрения реализации, мы не будем напрямую в методе запроса вышеcatch, но используяaxiosкоторый предоставилinterceptorsфункция, так что обработка исключений и основной метод запроса могут быть изолированы.В конце концов, эта часть должна бытьUIинтерактивный. Давайте посмотрим, как это сделать:

error.js

import axios from 'axios';

// Add a response interceptor
axios.interceptors.response.use((response) => {
  const { config, data } = response;
  // 和服务端约定的 Code
  const { code } = data;
  switch (code) {
    case 200:
      return data;
    case 401:
      // 登录失效
      break;
    case 403:
      // 无权限
      break;
    default:
      break;
  }
  if (config.showError) {
    // 接口配置指定需要个性化展示错误
    return Promise.reject(data);
  }
  // 默认展示错误
  // ... Toast error
}, (error) => {
  // 通用错误
  if (axios.isCancel(error)) {
    // Request cancel
  } else if (navigator && !navigator.onLine) {
    // Network is disconnect
  } else {
    // Other error
  }
  return Promise.reject(error);
});

axiosизinterceptorsФункция на самом деле является цепным вызовом, который может делать что-то до и после запроса.Здесь мы перехватываем запрос после запроса, проверяем возвращаемые данные и ловим исключения.Общие ошибки мы передаем напрямуюUIВзаимодействие будет отображать ошибки. Для бизнес-ошибок мы проверяем, настроен ли интерфейс для индивидуального отображения ошибок. Если есть, мы передаем обработку ошибок на страницу. Если нет, мы обрабатываем ошибку.

мониторинг запросов

Мониторинг запросов похож на обработку исключений, за исключением того, что он только записывает ситуацию и не включаетUIПоэтому можно прописать эту часть логики прямо в обработке исключений, либо добавить перехватчик после запроса и обрабатывать его отдельно.

monitor.js

axios.interceptors.response.use((response) => {
  const { status, data, config } = response;
  // 根据返回的数据和接口参数配置,对请求进行埋点
}, (error) => {
  // 根据返回的数据和接口参数配置,对请求进行埋点
});

Сравнение рекомендует делать это, сохраняя независимость каждого модуля в соответствии с принципом единой функции (SRP).

Ну, пока,конфигурация параметров,Обработка исключенийа такжемониторинг запросовВсе оформлено, есть три файла:

  • request.js: Запросить конфигурацию библиотеки, доступную для внешнего мира.get,postметод.
  • error.js: некоторая обработка исключений запроса, которая включает соединение с внешним миром, заключается в том, должен ли интерфейс отображать ошибки по отдельности.
  • monitor.js: Запись ситуации запроса, относительно независимая часть.

При вызове на странице это может быть так:

import { get, post } from 'request.js';

get('/user/info').then((data) => {});
post('/user/update', { name: 'beyondxgb' }, { showError: true }).then((data) => {
  if (data.code !== 200) {
    // 展示错误
  } else {
    // do something
  }
});

Поразмыслив над этим хорошенько, я думаю, что он не самый совершенный.APIИмя прямо упоминается на странице, которая похоронит себя, если последняяAPIИмя было изменено, и этоAPIОн вызывается на нескольких страницах, и стоимость обслуживания высока. У нас есть два пути, первый - положить всеAPIНезависимая конфигурация находится в файле, а страница читается Второй способ — добавить слой перед библиотекой запроса и страницей, который называетсяservice, то есть так называемый сервисный слой, который выставляет методы интерфейса на страницу, так что странице не нужно обращать внимание на то, что из себя представляет интерфейс или как интерфейс извлекает данные, и любая модификация интерфейса в будущем нужно изменить только на сервисном уровне. Никакого эффекта.

Конечно я выбираю второй способ, примерно так:

services.js

import { get, post } from 'request.js';

// fetch random data
export async function fetchRandomData(params) {
  return get('https://randomuser.me/api', params);
}

// update user info
export async function updateUserInfo(params, options) {
  return post('/user/info', params, { showError: true, ...options });
}

Таким образом, страница не будет напрямую взаимодействовать с библиотекой запросов, а получит соответствующий метод от сервисного слоя.

import { fetchRandomData, updateUserInfo } from 'services.js';

fetchRandomData().then((data) => {});
updateUserInfo({ name: 'beyondxgb' }).then((data) => {
  if (data.code !== 200) {
    // 展示错误
  } else {
    // do something
  }
});

Посмотрим, как выглядит окончательное решение:

расширение

Все вышеперечисленноеaxiosВозьмем в качестве примера эту библиотеку запросов.На самом деле идеи совместимы, и один и тот же метод используется для изменения библиотеки запросов. Я не знаю, заметили ли вы, что два модуля конфигурации параметров библиотеки запросов и обработки исключений независимы, что полностью используется.interceptorsособенность, которая мне тоже нравитсяaxiosОдна из причин, я думаю, что это хорошо спроектировано, аналогично подходу промежуточного программного обеспечения, прежде чем данные запроса достигнут страницы, мы можем написать перехватчики для выполнения обработки данных на данных.фильтр,обработка,чек об оплате,мониторинг исключенийЖдать.

Я думаю, что любая библиотека запросов может реализовать эту функцию, даже если библиотека запросов имеет исторический багаж, она также может покрыть его сама. Например, есть библиотека запросовabc, который имеетrequestметод, вы можете переопределить его следующим образом:

import abc from 'abc';

function dispatchRequest(options) {
  const reqConfig = Object.assign({}, options);
  return abc.request(reqConfig).then(response => ({
    response,
    options,
  })).catch(error => (
    Promise.reject({
      error,
      options,
    })
  ));
}

class Request {
  constructor(config) {
    this.default = config;
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager(),
    };
  }
}

Request.prototype.request = function request(config = {}) {
  // Add interceptors
  const chain = [dispatchRequest, undefined];
  let promise = Promise.resolve(options);

  // Add request interceptors
  this.interceptors.request.forEach((interceptor) => {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  // Add response interceptors
  this.interceptors.response.forEach((interceptor) => {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  return promise;
};

Более

Мы хорошо решили проблему запроса данных спереди, а с другой стороны, она также тесно связана с запросом данных, т.е.Макет данных (макет)Теперь, пока данные не готовы на стороне сервера на ранней стадии разработки проекта, мы можем сделать это только локально самостоятельноMockданные, или у многих компаний уже есть лучшая платформа для достижения этой функции, я представлю здесь без помощи платформы, просто запустите небольшой инструмент локально для достиженияMockданные.

Здесь я сам написал небольшой инструмент@ris/mock, просто вставьте его как промежуточное ПО вwebpack-dev-serverПросто хорошо.

webpack.config.js

const mock = require('@ris/mock');

module.exports = {
  //...
  devServer: {
    compress: true,
    port: 9000,
    after: (app) => {
      // Start mock data
      mock(app);
    },
  }
};

В это время создайте его в корневом каталоге проекта.mockпапка, создайте папкуrules.jsдокумент,rules.jsОн настраивает правила сопоставления интерфейса, например:

module.exports = {
  'GET /api/user': { name: 'beyondxgb' },
  'POST /api/form/create': { success: true },
  'GET /api/cases/list': (req, res) => { res.end(JSON.stringify([{ id: 1, name: 'demo' }])); },
  'GET /api/user/list': 'user/list.json',
  'GET /api/user/create': 'user/create.js',
};

После настройки правил при запросе интерфейса он будет перенаправлен, и переадресация может быть对象,函数,文件, подробности см.Документация.

Эпилог

При разработке схемы запроса данных также подтверждается, что наш «код написания» — это «программирование», а не «программирование». является основным качеством превосходных инженеров.

Вышеуказанная схема была урегулирована вRIS, включая структуру организации кода и техническую реализацию, вы можете инициализироватьStandardПодать заявку, чтобы увидеть, предыдущая статьяRIS, новый выбор для создания приложений ReactЭто было кратко упомянуто, и каждый может испытать это на себе.