从 ajax 到 axios
XMLHttpRequest 对象
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
}
};
const url = "http://www.baidu.com/";
xhr.open("post", url, true); //初始化
const body = {
data: {
a: 1,
},
};
xhr.send(body);
xhr.onload = function () {
// 请求结束后, 在此处写处理代码
console.log("请求成功", xhr.responseText);
};
// 当网络不佳时,我们需要给请求设置一个超时时间
// 超时时间单位为毫秒
xhr.timeout = 1000;
// 当请求超时时,会触发 ontimeout 方法
xhr.ontimeout = () => console.log("请求超时");
xhr.readyStatus==0 尚未调用 open 方法
xhr.readyStatus==1 已调用 open 但还未发送请求(未调用 send)
xhr.readyStatus==2 已发送请求(已调用 send)
xhr.readyStatus==3 已接收到请求返回的数据
xhr.readyStatus==4 请求已完成
XMLHttpRequest.responseType 属性是一个枚举属性, 返回响应式数据的类型.
responseType 支持以下几种值:
"" responseType 为空字符串时,采用默认类型 DOMString,与设置为 text 相同。
arraybuffer response 是一个包含二进制数据的 JavaScript arrayBuffer
blob response 是一个包含二进制数据的 blob 对象
document response 是一个 html document 或 xmldocument, 这取决于接收到的数据的 mime 类型.
json response 是一个 JavaScript 对象。这个对象是通过将接收到的数据类型视为 JSON 解析得到的
text response 是一个以 DOMString 对象表示的文本。
ajax
function ajax(options) {
let url = options.url;
const method = options.method.toLocaleLowerCase() || "get";
const async = options.async == true; // default is true
const data = options.data;
const xhr = new XMLHttpRequest();
if (options.timeout && options.timeout > 0) {
xhr.timeout = options.timeout;
}
return new Promise((resolve, reject) => {
xhr.ontimeout = () => reject && reject("请求超时");
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
resolve && resolve(xhr.responseText);
} else {
reject && reject();
}
}
};
xhr.onerror = (err) => reject && reject(err);
let paramArr = [];
let encodeData;
if (data instanceof Object) {
for (let key in data) {
// 参数拼接需要通过 encodeURIComponent 进行编码
paramArr.push(
encodeURIComponent(key) + "=" + encodeURIComponent(data[key])
);
}
encodeData = paramArr.join("&");
}
if (method === "get") {
// 检测 url 中是否已存在 ? 及其位置
const index = url.indexOf("?");
if (index === -1) url += "?";
else if (index !== url.length - 1) url += "&";
// 拼接 url
url += encodeData;
}
xhr.open(method, url, async);
if (method === "get") xhr.send(null);
else {
// post 方式需要设置请求头
xhr.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8"
);
xhr.send(encodeData);
}
});
}
fetch
// 原生XHR
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText); // 从服务器获取数据
}
};
xhr.send();
// fetch
fetch(url)
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((data) => console.log(data))
.catch((err) => console.log(err));
// 创建一个 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;
// 发送请求
fetch("https://example.com/data", { signal })
.then((response) => response.json())
.then((data) => {
// 处理响应数据
console.log(data);
})
.catch((error) => {
if (error.name === "AbortError") {
console.log("请求被取消");
} else {
// 处理其他错误
console.error("请求错误:", error);
}
});
// 在需要取消请求的地方调用
controller.abort();
Fetch API 也能像 XHR 那样中断一个请求了,只是稍微绕了一点。通过创建一个 AbortController 实例,我们得到了一个 Fetch API 原生支持的控制中断的控制器。这个实例的 signal 参数是一个 AbortSignal 实例,还提供了一个 abort() 方法发送中断信号。只需要将 signal 参数传递进 fetch() 的初始化参数中,就可以在 fetch 请求之外控制请求的中断了:
过去 FileReader 只能在 onload 事件上拿到整个文件的数据,或者对文件使用 slice() 方法得到 Blob 文件片段。
axios
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 {
// Something happened in setting up the request that triggered an Error
console.log("Error", error.message);
}
console.log(error.config);
});
取消请求
// 创建取消令牌的生成器对象
var CancelToken = axios.CancelToken;
// 从中获取令牌对象
var source = CancelToken.source();
// 发请求
axios
.get("/user/12345", {
// 传递令牌
cancelToken: source.token,
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log("Request canceled", thrown.message);
} else {
// 处理错误
}
});
// 取消请求(message 参数是可选的)
source.cancel("Operation canceled by the user.");
创建 CancelToken 对象: 在发起请求之前,可以通过 axios.CancelToken.source() 方法创建一个 CancelToken 对象,并获取其中的 token。这个 token 是一个用于标识该请求的令牌。
关联 CancelToken: 将创建的 CancelToken 对象中的 token 关联到请求的配置中,通过 cancelToken 参数。这告诉 Axios 在取消令牌触发时要取消这个请求。
取消请求: 当想要取消请求时,调用 CancelToken 对象中的 cancel 方法,并提供一个取消的原因。这会触发 Axios 内部的逻辑,导致底层的网络请求被中止。
捕获取消错误: 如果请求在取消前已经发出,Axios 会抛出一个名为 Cancel 的错误。可以使用 axios.isCancel(error) 来检查是否是取消错误。在 .catch 部分处理这个取消错误。
封装完成的取消请求
import axios from "axios";
axios.defaults.timeout = 5000;
axios.defaults.baseURL = "";
let pending = []; //声明一个数组用于存储每个ajax请求的取消函数和ajax标识
let cancelToken = axios.CancelToken;
let removePending = (ever) => {
for (let p in pending) {
if (pending[p].u === ever.url + "&" + ever.method) {
//当当前请求在数组中存在时执行函数体
pending[p].f(); //执行取消操作
pending.splice(p, 1); //把这条记录从数组中移除
}
}
};
//http request 拦截器
axios.interceptors.request.use(
(config) => {
config.data = JSON.stringify(config.data);
config.headers = {
"Content-Type": "application/x-www-form-urlencoded",
};
// ------------------------------------------------------------------------------------
removePending(config); //在一个ajax发送前执行一下取消操作
config.cancelToken = new cancelToken((c) => {
// 这里的ajax标识我是用请求地址&请求方式拼接的字符串,当然你可以选择其他的一些方式
pending.push({
u: config.url + "&" + config.method,
f: c,
});
});
// -----------------------------------------------------------------------------------------
return config;
},
(error) => {
return Promise.reject(err);
}
);
//http response 拦截器
axios.interceptors.response.use(
(response) => {
// ------------------------------------------------------------------------------------------
removePending(res.config); //在一个ajax响应后再执行一下取消操作,把已经完成的请求从pending中移除
// -------------------------------------------------------------------------------------------
if (response.data.errCode == 2) {
router.push({
path: "/login",
query: {
redirect: router.currentRoute.fullPath,
}, //从哪个页面跳转
});
}
return response;
},
(error) => {
return Promise.reject(error);
}
);
// 自己实现一个简略版的
// 创建Promise,返回放行开关cancel
function source() {
var cancel;
var promise = new Promise(function (resolve) {
cancel = resolve;
});
return {
cancel: cancel,
token: promise,
};
}
// 发请求
function axios_get(config) {
if (config.cancelToken) {
config.cancelToken.then(function () {
xhr.abort();
});
}
// 最终发请求
xhr.request();
}
// 代码执行
var source = source();
axios_get({
cancelToken: source.token,
});
setTimeout(function () {
source.cancel(); // 5秒之后执行下一步操作
}, 5000);
demo
<button @click="getMsg" class="get-msg">获取数据</button>
getMsg() {
let CancelToken = axios.CancelToken;
let self = this;
axios
.get("http://www.zhangxinxu.com/study/201802/cros-ajax.php", {
cancelToken: new CancelToken(function executor(c) {
self.cancel = c;
console.log(c);
// 这个参数 c 就是CancelToken构造函数里面自带的取消请求的函数,这里把该函数当参数用
}),
})
.then((res) => {
this.items = res.data;
})
.catch((err) => {
console.log(err);
});
//手速够快就不用写这个定时器了,点击取消获取就可以看到效果了
setTimeout(function () {
//只要我们去调用了这个cancel()方法,没有完成请求的接口便会停止请求
self.cancel();
}, 100);
}
接下来是 ant-design-pro 的代码
// adapter.js
import axios from "axios";
const codeMessage = {
200: "服务器成功返回请求的数据。",
201: "新建或修改数据成功。",
202: "一个请求已经进入后台排队(异步任务)。",
204: "删除数据成功。",
400: "发出的请求有错误,服务器没有进行新建或修改数据的操作。",
401: "用户没有权限(令牌、用户名、密码错误)。",
403: "用户得到授权,但是访问是被禁止的。",
404: "发出的请求针对的是不存在的记录,服务器没有进行操作。",
406: "请求的格式不可得。",
410: "请求的资源被永久删除,且不会再得到的。",
422: "当创建一个对象时,发生一个验证错误。",
500: "服务器发生错误,请检查服务器。",
502: "网关错误。",
503: "服务不可用,服务器暂时过载或维护。",
504: "网关超时。",
};
// 检查http code
const checkStatus = (response) => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = codeMessage[response.status] || response.statusText;
// 弹窗通知报错
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
};
/**
* 封装的请求函数
* @param {string} url
* @param {object} [option]
* @return {object}
*/
export default function request(option) {
const newOptions = Object.assign({}, option, {
credentials: "include",
});
if (
newOptions.method === "POST" ||
newOptions.method === "PUT" ||
newOptions.method === "DELETE"
) {
if (!(newOptions.body instanceof FormData)) {
newOptions.headers = {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
...newOptions.headers,
};
newOptions.data = JSON.parse(JSON.stringify(newOptions.data));
} else {
newOptions.headers = {
Accept: "application/json",
...newOptions.headers,
};
}
} else {
newOptions.headers = {
Accept: "application/json",
...newOptions.headers,
};
}
return axios(newOptions)
.then(checkStatus)
.then((response) => {
var res = response.data;
if (res.code === 1) {
return res.data;
} else {
Message.error({
content: res.msg,
duration: 1.5,
});
}
})
.catch((err) => {
let status = err.name;
if (status === 401) {
console.log("未经授权, 错误码:", status);
}
if (status === 403) {
console.log("禁止访问, 错误码:", status);
}
if (status <= 504 && status >= 500) {
console.log("服务器错误, 错误码:", status);
}
if (status >= 404 && status < 422) {
console.log("找不到资源路径, 错误码 :", status);
}
});
}
封装层级深
import axios from "axios";
import Cookies from "js-cookie";
import { handleUrl, handleHttpStatus, handleResultStatus } from "./utils";
class Adapter {
// 获取options
getOptions = ({ options, needAuthorToken, needCsrfToken }) => {
let { url, headers } = options;
// 检查url,url可能为 /api/backend/,也可能是完整的url=http://seewo.com
url = handleUrl(url);
if (needAuthorToken) {
const authorToken = Cookies.get("x-auth-token"); // 这个由你们定义
headers["xauthtoken"] = authorToken;
}
if (needCsrfToken) {
const csrfToken = Cookies.get("csrfToken"); // 这个由你们定义
headers["x-csrf-token"] = csrfToken;
}
return Object.assign(options, {
url: url,
method: options.method || "GET",
headers: headers,
});
};
// request
dispatchCallAPI = ({ options, authorToken = false, csrfToken = false }) => {
const options = this.getOptions({ options, authorToken, csrfToken });
return axios(options)
.then(handleHttpStatus)
.then(handleResultStatus)
.then((res) => {
/**
* 如果返回的不是一个JSON对象,而是一个字符串,因此需要对这个字符串进行处理
* 如果直接返回的是一个JSON对象,这个时候,JSON.parse会抛出异常,如果出现异常
* 我们直接返回这个对象本身的值即可
*/
try {
return JSON.parse(res.data);
} catch (err) {
return res.data;
}
})
.catch((error) => {
console.log(error);
});
};
}
export default new Adapter();
最后更新于
这有帮助吗?