前言
本文主要是讲述ajax
、fetch
和axios
的优缺点以及一些基本的封装方式,更加深入的理解还请自行查阅资料
AJAX
Asynchronous JavaScript + XML(异步 JavaScript 和 XML), 其本身不是一种新技术,而是一个在 2005 年被 Jesse James Garrett 提出的新术语,用来描述一种使用现有技术集合的‘新’方法,包括:HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 以及最重要的
XMLHttpRequest
。当使用结合了这些技术的 AJAX 模型以后, 网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面。这使得程序能够更快地回应用户的操作。(来自MDN AJAX文档)
简单来说,AJAX
并不是一种新技术,而是一种能使网页实现异步刷新
的方式,那么下面就来看看AJAX
的优劣势吧👇
AJAX的优势
- 让用户体验到无需刷新也能获取网络资源的快乐
- 使用异步方式通信
abort
方法可以中断请求- 基于标准化的并被广泛支持的技术,无需安装依赖
AJAX的劣势
- 对浏览器
BAck
和HISTORY
功能的破坏,浏览器无法前进和后退 - 同步或异步需要通过传参区分,而不是基于事件的异步或同步
AJAX工作流程
- 使用
XMLHttpRequest
或ActiveXObject
创建一个XHR
对象 - 通过
open
向这个XHR
对象传递请求初始参数,url
、method
等 - 如果不是
GET
方法,则需要通过setRequestHeader
设置请求头 - 调用
send
方法发送请求 - 使用
onreadystatechange
接收响应结果
简单封装(选自JavaScript学习笔记(二十七)-- ajax及ajax封装)
function ajax(options) {
// 先准备一个默认值
let defaultConfig = {
url: '', // 地址不需要默认值
type: 'GET', // 请求方式的默认值是 GET
async: true, // 默认值是异步
data: '', // 参数没有默认值
dataType: 'string', // 默认不需要执行 json.parse
success () {}, // 默认是一个函数
}
// 先来判断一下有没有传递 url,如果没有,直接抛出异常
if (!options.url) {
throw new Error('url 必须传递')
}
// 有了 url 以后就,我们就把用户传递的参数和我们的默认数据合并
for (let key in options) {
defaultConfig[key] = options[key]
}
// 接下来的一切我们都是使用我们的 defaultConfig 就可以了
// 第一步就是判断参数 data
// data 可以不传递,可以为空
// data 也可以是一个 key=value&key=value 格式的字符串
// data 也可以是一个对象
// 否则就抛出异常
if (!(typeof defaultConfig.data === 'string' && /^(\w+=\w+&?)*$/.test(defaultConfig.data) || Object.prototype.toString.call(defaultConfig.data) === '[object Object]')) {
throw new Error('请按照要求传递参数')
}
// 参数处理完毕以后,在判断 async 的数据类型
// 只能传递 布尔数据类型
if (typeof defaultConfig.async !== 'boolean') {
throw new Error('async 参数只接受布尔数据类型')
}
// 在接下来就判断 type
// 请求方式我们只接受 GET 或着 POST
if (!(defaultConfig.type.toUpperCase() === 'GET' || defaultConfig.type.toUpperCase() === 'POST')) {
throw new Error('目前本插件只接受 GET 和 POST 方式,请期待更新')
}
// 接下来就是判断 success 的判断,必须是一个函数
if (Object.prototype.toString.call(defaultConfig.success) !== '[object Function]') {
throw new Error('success 只接受函数数据类型')
}
// 参数都没有问题了
// 我们就要把 data 处理一下了
// 因为 data 有可能是对象,当 data 是一个对象的时候,我们要把它转换成一个字符串
var str = ''
if (Object.prototype.toString.call(defaultConfig.data) === '[object Object]') {
for (let attr in defaultConfig.data) {
str += `${attr}=${defaultConfig.data[attr]}&`
}
str = str.slice(0, -1)
defaultConfig.data = str
}
// 参数全部验证过了以后,我们就可以开始进行正常的 ajax 请求了
// 1. 准备一个 ajax 对象
// 因为要处理兼容问题,所以我们准备一个函数
function createXHR() {
if (XMLHttpRequest) {
return new XMLHttpRequest()
} else {
return new ActiveXObject('Microsoft.XMLHTTP')
}
}
// 2. 创建一个 ajax 对象
var xhr = createXHR()
// 3. 进行 open
xhr.open(defaultConfig.type, defaultConfig.url + (defaultConfig.type.toUpperCase() === 'GET' ? `?${defaultConfig.data}&_=${new Date().getTime()}` : ''), defaultConfig.async)
if (defaultConfig.type.toUpperCase() === 'POST') {
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
}
// 4. 进行 send
xhr.send((defaultConfig.type.toUpperCase() === 'POST' ? `${defaultConfig.data}` : ''))
// 5. 接受响应
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && /2\d{2}/.test(xhr.status)) {
// 表示成功,我们就要执行 success
// 但是要进行 dataType 的判断
if (defaultConfig.dataType === 'json') {
defaultConfig.success(JSON.parse(xhr.responseText))
} else {
defaultConfig.success()
}
}
}
}
FETCH
Fetch 提供了对
Request
和Response
(以及其他与网络请求有关的)对象的通用定义。使之今后可以被使用到更多地应用场景中:无论是 service worker、Cache API、又或者是其他处理请求和响应的方式,甚至是任何一种需要你自己在程序中生成响应的方式。它同时还为有关联性的概念,例如 CORS 和 HTTP 原生头信息,提供一种新的定义,取代它们原来那种分离的定义。(来自MDN FETCH文档)
FETCH
相较于AJAX
来说更加轻便,基于原生打造也使得它能使用更多的API,下面看看FETCH
的优劣势👇
FETCH的优势
- 最大的优点就是基于
Promise
打造,无需担心异步问题 - 语法更加简洁,更加语义化
- 包含了更丰富的API
FETCH的劣势
- 只对网络错误报错,
400
、500
类状态码都当作成功请求 - 默认
不携带Cookie
不支持abort
FETCH的工作流程
- 配置请求头
- 使用
fetch
发送请求 - 接收响应报文,并按类型将报文解码
简单封装
import baseUrl from './config'
const HttpMethod = {
get: 'GET',
post: 'POST',
put: 'PUT',
patch: 'PATCH',
delete: 'DELETE'
}
const myFetch = (url, config) => {
return new Promise(async (resolve, reject) => {
let promise, contentType
if (config?.['Content-Type'] !== undefined) {
contentType = config['Content-Type']
} else if (config?.method === HttpMethod.post) {
contentType = ContentType.form
} else {
contentType = ContentType.json
}
const parseResult = async res => {
const contentType = res.headers.get('Content-Type')
if (contentType) {
if (contentType.indexOf('json') !== -1) return await res.json()
if (contentType.indexOf('text') !== -1) return await res.text()
if (contentType.indexOf('form') !== -1)
return await res.formData()
if (contentType.indexOf('video') !== -1) return await res.blob()
}
return res.text()
}
const handleResult = async res => {
const result = await parseResult(res)
if (res.ok) return result
}
const requestUrl = (baseUrl + url).replace('//', '/')
const headers = new Headers({
token: config?.token ?(localStorage.getItem('token')??config.token):false,
'Content-Type': contentType,
})
try {
if (!config?.method || config.method === HttpMethod.get) {
promise = await fetch(requestUrl, {
headers,
credentials: config?.token?'include':'omit',
})
} else if (config.method === HttpMethod.post) {
promise = await fetch(requestUrl, {
body: JSON.stringify(config.body),
headers,
method: HttpMethod.post,
credentials: config?.token ? 'include' : 'omit',
})
} else {
promise = await fetch(requestUrl, {
body: JSON.stringify(config.body),
headers,
method: config.method,
credentials: config?.token ? 'include' : 'omit',
})
}
resolve(handleResult(promise))
} catch (err) {
console.error('fetch error!', err)
reject(err)
}
})
}
AXIOS
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
毫无疑问,AXIOS
仍然是请求方式的首要之选,因为确实太方便了,它的优劣势如下👇
AXIOS的优势
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
AXIOS的劣势
- 由于是第三方库,终归还是有那么一点点的担心跑路
AXIOS的工作流程
- 配置请求头
- 使用
create
方法创建AXIOS
实例 - 调用
AXIOS
实例的get
、post
等方法发送请求 - 使用
Promise
链式调用获取响应结果
简单封装(节选自vue中Axios的封装和API接口的管理)
/**
* axios封装
* 请求拦截、响应拦截、错误统一处理
*/
import axios from 'axios';
import router from '../router';
import store from '../store/index';
import { Toast } from 'vant';
/**
* AXIOS实例配置项
*/
const instanceConfig = {
baseURL: '' ,
timeout: 1000,
withCredentials: true
}
/**
* 提示函数
* 禁止点击蒙层、显示一秒后关闭
*/
const tip = msg => {
Toast({
message: msg,
duration: 1000,
forbidClick: true
});
}
/**
* 跳转登录页
* 携带当前页面路由,以期在登录页面完成登录后返回当前页面
*/
const toLogin = () => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
});
}
/**
* 请求失败后的错误统一处理
* @param {Number} status 请求失败的状态码
*/
const errorHandle = (status, other) => {
// 状态码判断
switch (status) {
...
// 做错误状态管理
default:
console.log(other);
}}
// 创建axios实例
const instance = axios.create(instanceConfig);
// 设置post请求头
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
/**
* 请求拦截器
* 每次请求前,如果存在token则在请求头中携带token
*/
instance.interceptors.request.use(
config => {
// 登录流程控制中,根据本地是否存在token判断用户的登录情况
// 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token
// 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码
// 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。
const token = store.state.token;
token && (config.headers.Authorization = token);
return config;
},
error => Promise.error(error))
// 响应拦截器
instance.interceptors.response.use(
// 请求成功
res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res),
// 请求失败
error => {
const { response } = error;
if (response) {
// 请求已发出,但是不在2xx的范围
errorHandle(response.status, response.data.message);
return Promise.reject(response);
} else {
if (!window.navigator.onLine) {
...
// 处理断网的情况
// eg:请求超时或断网时,更新state的network状态
// network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
// 关于断网组件中的刷新重新获取数据,会在断网组件中说明
} else {
return Promise.reject(error);
}
}
});
结语
本文只是比较简单的介绍一下AJAX
、FETCH
和AXIOS
的一些区别和简单的使用,内容基本上在社区都能找得到,只是为了方便笔者记忆所以动手写一下
要是有任何的错误或者需要修改的地方,恳请指出,感激不敬