请求库:fetch vs axios vs umi-request 对比分析

lxf2023-03-14 17:00:01

Fetch API、Axios、Umi-request 是前端开发日常较常用的请求方案,具体用法、优劣,接下来我们做下分析。

Fetch API

Fetch API 是 JavaScript 原生提供的异步 Web API。fetch() 返回一个 Promise 对象。目前所有的现代浏览器都内置 Fetch API,不需要引入三方包即可使用。

基础用法

Fetch API 提供 fetch 方法发送请求,基础用法如下:

fetch('http://example.com/movies.json')
  .then((response) => response.json())
  .then((data) => console.log(data));

返回处理

fetch 方法返回一个 Promise 对象,需要手动进行处理,才能获取返回 JSON 对象。

async function getData() {
	const response = await fetch(
		'https://famous-quotes4.p.rapidapi.com/random'
	);
	// Extract data from response
	const data = await response.json();
}

请求头和参数

fetch 通过 body 属性进行传参,body 必须接收一个 JSON 字符串,不能直接接收 JSON 对象,因为 fetch 不会对 body 参数进行 stringify,因此需要手动对 body 参数进行 JSON.stringify。代码示例如下:

async function getData() {
	const response = await fetch(
		'https://famous-quotes4.p.rapidapi.com/random',
		{
			// HTTP Method
			method: 'POST',
			// Parameters
			body: JSON.stringify({
				a: 10,
				b: 20
			}),
			// Headers
			headers: {
				'api-key': '12345'
			}
		}
	);
}

错误处理

当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的 Promise 不会被标记为 reject,即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve(如果响应的 HTTP 状态码不在 200 - 299 的范围内,则设置 resolve 返回值的 ok 属性为 false),仅当网络故障时或请求被阻止时,才会标记为 reject。

fetch 通过返回 repsonse 的 ok 属性来判断是否请求成功,如果 response.ok 不为 true,则代表请求失败,则可进行错误处理。示例如下:

async function getData() {
	const response = await fetch(
		'https://famous-quotes4.p.rapidapi.com/random'
	);
	// Handle errors
	if (!response.ok) {
		throw new Error(`HTTP error! status: ${response.status}`);
	}
	const data = await response.json();
}

同步请求

Fetch API 可以借助 Promise 进行同步请求。示例如下:

const endpoints = [
  'http://codilime.com/endpoint1',
  'http://codilime.com/endpoint2',
  'http://codilime.com/endpoint3',
];

Promise.all(
  endpoints.map(endpoint => fetch(endpoint))
)
  .then(async ([ res1, res2, res3 ]) => {
    const res1JSON = await res1.json();
    const res2JSON = await res2.json();
    const res3JSON = await res3.json();
    console.log(res1JSON, res2JSON, res3JSON);
  }))

超时处理

Fetch API 没有提供 timeout 属性来处理超时,可以借助 AbortController 进行超时处理。

示例如下:

// 15 seconds timeout functionality with AbortController interface.
// If the server does not respond within 15 seconds, the catch block runs.
const controller = new AbortController();
const options = {
  method: 'POST',
  signal: controller.signal,
  body: JSON.stringify({
    x: "foo",
    y: "bar",
  })
};  
const promise = fetch('https://foo-bar.com', options);
const timeout = setTimeout(() => controller.abort(), 15000);

promise
 .then(response => console.log(response.data))
 .catch(error => console.log('Timeout Error.!'))

请求拦截

Fetch API 没有提供请求拦截的方法。

请求取消

Fetch API 提供了 AbortController 进行请求取消。


const controller = new AbortController();
const options = {
  method: 'POST',
  signal: controller.signal,
  body: JSON.stringify({
    x: "foo",
    y: "bar",
  })
};  
fetch('https://foo-bar.com', options);

// 取消请求
controller.abort()

Axios

Axios 是目前最流行的 JS 第三方请求库。Axios 基于 Promise 实现,可同时在 Node.js 和浏览器中使用。在 Node.js 服务端基于 Node.js Http 模块实现,在浏览器中则基于 XMLHtppRequests 实现。

核心特性

  • 在浏览器中基于 XMLHttpRequests 实现;
  • 在 Node.js 中基于 Http 模块实现;
  • 支持 Promise API;
  • 可进行请求拦截,可拦截 request 和 response;
  • 转换请求和返回数据;
  • 支持请求取消;
  • 自动转换 JSON 数据,无需手动 stringify;
  • 客户端支持防御 XSRF;

基础用法

和 Fetch API 不同,Axios 每个 HTTP 方法都提供了一个独立的 function。可以通过如下方法进行不同方法的请求:

  • axios.get()
  • axios.post()
  • axios.put() 

基础使用示例:

import axios from 'axios';
const getData = async () => {
	// API Call
	const response = await axios.get(
		`https://famous-quotes4.p.rapidapi.com/random`
	);
};

返回处理

Axios 自动转换 response,无需像 Fetch API 一样手动进行 response.json 来获取数据。

请求头和参数

可以通过 params 属性传递 get 请求参数,通过 data 属性来传递请求的 body 参数,不像 Fetch API,Axios 自动 JSON.stringify 请求 body。headers 的处理则和 Fetch API 类似,使用 headers 属性来传递 headers。

使用示例如下:

import axios from 'axios';
const fetchData = async () => {
	const res = await axios.post(
		`https://famous-quotes4.p.rapidapi.com/random`,
		{
			data: {
				a: 5,
				b: 10
			},
      headers: {
				'api-key': '12345'
			},
		}
	);
};

错误处理

因为 Axios 的请求方法返回一个 Promise 对象,可以直接通过 Promise.catch 方法捕获错误进行处理。示例如下:

axios.get('/user/12345')
  .catch(function (error) {
    if (error.response) {
      // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // 请求已经成功发起,但没有收到响应
      // `error.request` 在浏览器中是 XMLHttpRequest 的实例,
      // 而在node.js中是 http.ClientRequest 的实例
      console.log(error.request);
    } else {
      // 发送请求时出了点问题
      console.log('Error', error.message);
    }
    console.log(error.config);
  });

同步请求

Axios 提供了 axios.all 方法进行同步请求。示例如下:

axios.all([
  axios.get('https://foo-bar-1.com'), 
  axios.get('https://foo-bar-2.com')
])
.then(axios.spread((x, y) => {
  console.log(x.data);
  console.log(y.data);
}));

当然,也可以通过 Promise.all 进行同步请求。示例如下:

Promise.all([
  axios.get('https://foo-bar-1.com'), 
  axios.get('https://foo-bar-2.com')
])
.then(async([response1, response2]) => {
  const x = await response1.json();
  const y = await response2.json();
  console.log(x.data);
  console.log(y.data);
})

超时处理

Axios 可以通过请求配置 timeout 来进行超时处理。

示例如下:

// 15 seconds timeout added at axios config object.
// If the server does not respond within 15 seconds, the catch block runs.
axios({
  method: 'POST',
  url: 'https://foo-bar.com',
  timeout: 15000,
  data: {
    x: "foo",
    y: "bar",
  }
})
.then(response => console.log(response.data))
.catch(error => console.log('Timeout Error.!'))

请求拦截

请求拦截是 Axios 最重要的特性之一。可以通过添加拦截器对请求和响应进行拦截。

示例如下:

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });

请求取消

从 v0.22.0 开始,Axios 支持以 fetch API 方式—— AbortController 取消请求:

const controller = new AbortController();

axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// 取消请求
controller.abort()

Umi-request

umi-request 是一个网络请求库,基于 fetch 封装, 兼具 fetch 与 axios 的特点, 旨在为开发者提供一个统一的 api 调用方式, 简化使用, 并提供诸如缓存, 超时, 字符编码处理, 错误处理等常用功能.

核心特性

  • url 参数自动序列化
  • post 数据提交方式简化
  • response 返回处理简化
  • api 超时支持
  • api 请求缓存支持
  • 支持处理 gbk
  • 类 axios 的 request 和 response 拦截器(interceptors)支持
  • 统一的错误处理方式
  • 类 koa 洋葱机制的 use 中间件机制支持
  • 类 axios 的取消请求
  • 支持 node 环境发送 http 请求

基础用法

为了方便起见,为所有支持的请求方法提供了别名, method 属性不必在配置中指定

request.get(url[, options])

request.post(url[, options])

request.delete(url[, options])

request.put(url[, options])

request.patch(url[, options])

request.head(url[, options])

request.options(url[, options])

get 请求示例:

import request from 'umi-request';

request
  .get('/api/v1/xxx?id=1')
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    console.log(error);
  });

// 也可将 URL 的参数放到 options.params 里
request
  .get('/api/v1/xxx', {
    params: {
      id: 1,
    },
  })
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    console.log(error);
  });

post请求示例:

request
  .post('/api/v1/user', {
    data: {
      name: 'Mike',
    },
  })
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    console.log(error);
  });

返回处理

umi-request 返回处理与 Axios 相似,会自动进行返回转换,无需像 Fetch API 一样手动处理。

请求头和参数

umi-request 的请求头和参数与 Axios 相似,在 params 中传入 get 请求参数,在 data 中传入 post 请求参数。

request('/api/v1/xxx', {
  method: 'get',
  params: { id: 1 },
})
request('/api/v1/user', {
  method: 'post',
  data: {
    name: 'Mike',
  },
})

错误处理

import request, { extend } from 'umi-request';

const errorHandler = function(error) {
  const codeMap = {
    '021': '发生错误啦',
    '022': '发生大大大大错误啦',
    // ....
  };
  if (error.response) {
    // 请求已发送但服务端返回状态码非 2xx 的响应
    console.log(error.response.status);
    console.log(error.response.headers);
    console.log(error.data);
    console.log(error.request);
    console.log(codeMap[error.data.status]);
  } else {
    // 请求初始化时出错或者没有响应返回的异常
    console.log(error.message);
  }

  throw error; // 如果throw. 错误将继续抛出.

  // 如果return, 则将值作为返回. 'return;' 相当于return undefined, 在处理结果时判断response是否有值即可.
  // return {some: 'data'};
};

// 1. 作为统一错误处理
const extendRequest = extend({ errorHandler });

// 2. 单独特殊处理, 如果配置了统一处理, 但某个api需要特殊处理. 则在请求时, 将errorHandler作为参数传入.
request('/api/v1/xxx', { errorHandler });

// 3. 通过 Promise.catch 做错误处理
request('/api/v1/xxx')
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    return errorHandler(error);
  });

超时处理

与 Aixos 相同,通过请求参数 timeout 来进行超时处理。

请求拦截

与 Axios 相同,可以通过添加拦截器,来拦截请求 request 和 response。

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

  1. 全局拦截器
// request拦截器, 改变url 或 options.
request.interceptors.request.use((url, options) => {
  return {
    url: `${url}&interceptors=yes`,
    options: { ...options, interceptors: true },
  };
});

// 和上一个相同
request.interceptors.request.use(
  (url, options) => {
    return {
      url: `${url}&interceptors=yes`,
      options: { ...options, interceptors: true },
    };
  },
  { global: true }
);

// response拦截器, 处理response
request.interceptors.response.use((response, options) => {
  const contentType = response.headers.get('Content-Type');
  return response;
});

// 提前对响应做异常处理
request.interceptors.response.use(response => {
  const codeMaps = {
    502: '网关错误。',
    503: '服务不可用,服务器暂时过载或维护。',
    504: '网关超时。',
  };
  message.error(codeMaps[response.status]);
  return response;
});

// 克隆响应对象做解析处理
request.interceptors.response.use(async response => {
  const data = await response.clone().json();
  if (data && data.NOT_LOGIN) {
    location.href = '登录url';
  }
  return response;
});
  1. 实例内部拦截器
// 全局拦截器直接使用 request 实例中的方法
request.interceptors.request.use(
  (url, options) => {
    return {
      url: `${url}&interceptors=yes`,
      options: { ...options, interceptors: true },
    };
  },
  { global: false }
); // 第二个参数不传默认为 { global: true }

function createClient(baseUrl) {
  const request = extend({
    prefix: baseUrl,
  });
  return request;
}

const clientA = createClient('/api');
const clientB = createClient('/api');
// 局部拦截器使用
clientA.interceptors.request.use(
  (url, options) => {
    return {
      url: `${url}&interceptors=clientA`,
      options,
    };
  },
  { global: false }
);

clientB.interceptors.request.use(
  (url, options) => {
    return {
      url: `${url}&interceptors=clientB`,
      options,
    };
  },
  { global: false }
);

请求取消

与 Axios 相同,umi-request 基于 AbortController 方案来中止一个或多个DOM请求。

// 按需决定是否使用 polyfill
import 'yet-another-abortcontroller-polyfill'
import Request from 'umi-request';

const controller = new AbortController(); // 创建一个控制器
const { signal } = controller; // 返回一个 AbortSignal 对象实例,它可以用来 with/abort 一个 DOM 请求。

signal.addEventListener('abort', () => {
  console.log('aborted!');
});

Request('/api/response_after_1_sec', {
  signal, // 这将信号和控制器与获取请求相关联然后允许我们通过调用 AbortController.abort() 中止请求
});

// 取消请求
setTimeout(() => {
  controller.abort(); // 中止一个尚未完成的DOM请求。这能够中止 fetch 请求,任何响应Body的消费者和流。
}, 100);

其他

Umi-request 还额外提供了如下能力:

  • api 请求缓存支持
  • 支持处理 gbk

总结

Fetch API 是官方提供的浏览器内置请求 API,但是只支持浏览器,不支持 Node.js,整体封装性较低,使用起来有些需要手动处理的地方,如没有请求别名方法、返回处理需要手动 JSON.stringify,不支持超时 timeout 处理,不支持请求拦截等。

Axios 作为最流行的第三方请求库,同时支持浏览器和 Node.js,在封装性上更好,使用起来更加简便,如提供请求别名方法、自动进行 body 参数转化、支持 get 请求参数 params、支持 timeout 属性方式进行自动超时处理、支持请求拦截等高阶特性。整体使用便捷性和高阶能力上都由于 Fetch API。

Umi-request 则是基于 Fetch API 进行封装,同时参考 Axios 的能力基本吸收了 Axios 的优点,如提供请求别名方法、自动进行 body 参数转化、支持 get 请求参数 params、支持 timeout 属性方式进行自动超时处理、支持请求拦截等高阶特性。与此同时,还提供了针对 GET 请求的请求缓存的能力,在 GET 请求在 ttl 毫秒内将被缓存,缓存策略唯一 key 为 url + params + method 组合,这个缓存请求能力在一定场景下还是挺实用的。

npm 下载量:

请求库:fetch vs axios vs umi-request 对比分析

axios 作为当前最流行的第三方请求库,下载量远远超过 umi-request。star 数上,axios 98.2k,umi-request 2k,axios 也有绝对的领先优势。

能力对照表:

特性fetchaxiosumi-request
超时
缓存
错误处理
拦截器
取消请求
Node.js 支持
star 数98.2k2k

参考资料

  • Which one should we use? Axios vs fetch()
  • Axios vs. Fetch API – which is better for HTTP requests?
  • Axios vs. Fetch: What to use for Making HTTP Requests?
  • HTTP Call Comparison: Fetch API vs Axios
  • Midway HTTP 请求
  • www.tothenew.com/blog/fetch-…
  • comet.lehman.cuny.edu/sfulakeza/s…
  • "没想法的人写不好代码"之《React开发思想》