axios 中一个请求取消的示例:

axios 取消请求的示例代码
import React, { useState, useEffect } from "react";
import axios, { AxiosResponse } from "axios"; export default function App() {

const [index, setIndex] = useState(0);

const [imgUrl, setImgUrl] = useState("");

useEffect(() => {

console.log(</span>loading ${<span class="pl-smi">index</span>}<span class="pl-pds">);

const source = axios.CancelToken.source();

axios

.get("https://dog.ceo/api/breeds/image/random", {

cancelToken: source.token

})

.then((res: AxiosResponse<{ message: string; status: string }>) => {

console.log(</span>${<span class="pl-smi">index</span>} done<span class="pl-pds">);

setImgUrl(res.data.message);

})

.catch(err => {

if (axios.isCancel(source)) {

console.log(err.message);

}

});
<span class="pl-k">return</span> () <span class="pl-k">=&gt;</span> {
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">`</span>canceling ${<span class="pl-smi">index</span>}<span class="pl-pds">`</span></span>);
<span class="pl-smi">source</span>.<span class="pl-en">cancel</span>(<span class="pl-s"><span class="pl-pds">`</span>canceling ${<span class="pl-smi">index</span>}<span class="pl-pds">`</span></span>);
};

}, [index]);

return (

<div>

<button

onClick={() => {

setIndex(index + 1);

}}

>

click

</button>

<div>

<img src={imgUrl} alt="" />

</div>

</div>

);

}

axios 中一个请求取消的示例

通过解读其源码不难实现出一个自己的版本。Here we go...

Promise 链与拦截器

这个和请求的取消其实关系不大,但不妨先来了解一下,axios 中如何组织起来一个 Promise 链(Promise chain),从而实现在请求前后可执行一个拦截器(Interceptor)的。

简单来说,通过 axios 发起的请求,可在请求前后执行一些函数,来实现特定功能,比如请求前添加一些自定义的 header,请求后进行一些数据上的统一转换等。

用法

首先,通过 axios 实例配置需要执行的拦截器:

axios.interceptors.request.use(function (config) {
console.log('before request')
return config;
}, function (error) {
return Promise.reject(error);
}); axios.interceptors.response.use(function (response) {

console.log('after response');

return response;

}, function (error) {

return Promise.reject(error);

});

然后每次请求前后都会打印出相应信息,拦截器生效了。

axios({
url: "https://dog.ceo/api/breeds/image/random",
method: "GET"
}).then(res => {
console.log("load success");
});

下面编写一个页面,放置一个按钮,点击后发起请求,后续示例中将一直使用该页面来测试。

import React from "react";
import axios from "axios"; export default function App() {

const sendRequest = () => {

axios.interceptors.request.use(

config => {

console.log("before request");

return config;

},

function(error) {

return Promise.reject(error);

}

);
<span class="pl-smi">axios</span>.<span class="pl-smi">interceptors</span>.<span class="pl-smi">response</span>.<span class="pl-en">use</span>(
<span class="pl-v">response</span> <span class="pl-k">=&gt;</span> {
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>after response<span class="pl-pds">"</span></span>);
<span class="pl-k">return</span> <span class="pl-smi">response</span>;
},
<span class="pl-k">function</span>(<span class="pl-v">error</span>) {
<span class="pl-k">return</span> <span class="pl-c1">Promise</span>.<span class="pl-c1">reject</span>(<span class="pl-smi">error</span>);
}
); <span class="pl-en">axios</span>({
url: <span class="pl-s"><span class="pl-pds">"</span><a href="https://dog.ceo/api/breeds/image/random" rel="noreferrer noopener" class="rgh-linkified-code"><span class="pl-pds"></span>https://dog.ceo/api/breeds/image/random</a><span class="pl-pds">"</span></span>,
method: <span class="pl-s"><span class="pl-pds">"</span>GET<span class="pl-pds">"</span></span>
}).<span class="pl-c1">then</span>(<span class="pl-v">res</span> <span class="pl-k">=&gt;</span> {
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>load success<span class="pl-pds">"</span></span>);
});

};

return (

<div>

<button onClick={sendRequest}>click me</button>

</div>

);

}

点击按钮后运行结果:

before request
after response
load success

拦截器机制的实现

实现分两步走,先看请求前的拦截器。

请求前拦截器的实现

Promise 的常规用法如下:

new Promise(resolve,reject);

假如我们封装一个类似 axios 的请求库,可以这么写:

interface Config {
url: string;
method: "GET" | "POST";
} function request(config: Config) {

return new Promise((resolve, reject) => {

const xhr = new XMLHttpRequest();

xhr.open(config.method, config.url);

xhr.onload = () => {

resolve(xhr.responseText);

};

xhr.onerror = err => {

reject(err);

};

xhr.send();

});

}

除了像上面那个直接 new 一个 Promise 外,其实任意对象值都可以形成一个 Promise,方法是调用 Promise.resolve

Promise.resolve(value).then(()=>{ /**... */ });

这种方式创建 Promise 的好处是,我们可以从 config 开始,创建一个 Promise 链,在真实的请求发出前,先执行一些函数,像这样:

function request(config: Config) {
return Promise.resolve(config)
.then(config => {
console.log("interceptor 1");
return config;
})
.then(config => {
console.log("interceptor 2");
return config;
})
.then(config => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(config.method, config.url);
xhr.onload = () => {
resolve(xhr.responseText);
};
xhr.onerror = err => {
reject(err);
};
xhr.send();
});
});
}

将前面示例中 axios 替换为我们自己写的 request 函数,示例可以正常跑起来,输出如下:

interceptor 1
interceptor 2
load success

这里,已经实现了 axios 中请求前拦截器的功能。仔细观察,上面三个 then 当中的函数,形成了一个 Promise 链,在这个链中顺次执行,每一个都可以看成一个拦截器,即使是执行发送请求的那个 then

于是我们可以将他们抽取成三个函数,每个函数就是一个拦截器

function interceptor1(config: Config) {
console.log("interceptor 1");
return config;
}
function interceptor2(config: Config) {
console.log("interceptor 2");
return config;
} function xmlHttpRequest<T>(config: Config) {

return new Promise<T>((resolve, reject) => {

const xhr = new XMLHttpRequest();

xhr.open(config.method, config.url);

xhr.onload = () => {

resolve(xhr.responseText as any);

};

xhr.onerror = err => {

reject(err);

};

xhr.send();

});

}

接下来要做的,就是从 Promise 链的头部 Promise.resolve(config) 开始,将上面三个函数串起来。借助 Monkey patch 这不难实现:

function request<T = any>(config: Config) {
let chain: Promise<any> = Promise.resolve(config);
chain = chain.then(interceptor1);
chain = chain.then(interceptor2);
chain = chain.then(xmlHttpRequest);
return chain as Promise<T>;
}

然后,将上面硬编码的写法程式化一下,就实现了任意个请求前拦截器的功能。

扩展配置,以接收拦截器:

interface Config {
url: string;
method: "GET" | "POST";
interceptors?: Interceptor<Config>[];
}

创建一个数组,将执行请求的函数做为默认的元素放进去,然后将用户配置的拦截器压入数组前面,这样形成了一个拦截器的数组。最后再遍历这个数组形成 Promise 链。

function request<T = any>({ interceptors = [], ...config }: Config) {
// 发送请求的拦截器为默认,用户配置的拦截器压入数组前面
const tmpInterceptors: Interceptor<any>[] = [xmlHttpRequest];
interceptors.forEach(interceptor => {
tmpInterceptors.unshift(interceptor);
});
let chain: Promise<any> = Promise.resolve(config);
tmpInterceptors.forEach(interceptor => (chain = chain.then(interceptor)));
return chain as Promise<T>;
}

使用:

request({
url: "https://dog.ceo/api/breeds/image/random",
method: "GET",
interceptors: [interceptor1, interceptor2]
}).then(res => {
console.log("load success");
});

执行结果:

interceptor 2
interceptor 1
load success

注意这里顺序为传入的拦截器的反序,不过这不重要,可通过传递的顺序来控制。

响应后拦截器

上面实现了在请求前执行一序列拦截函数,同理,如果将拦截器压入到数组后面,即执行请求那个函数的后面,便实现了响应后的拦截器。

继续扩展配置,将请求与响应的拦截器分开:

interface Config {
url: string;
method: "GET" | "POST";
interceptors?: {
request: Interceptor<Config>[];
response: Interceptor<any>[];
};
}

更新 request 方法,请求前拦截器的逻辑不变,将新增的响应拦截器通过 push 压入数组后面:

function request<T = any>({
interceptors = { request: [], response: [] },
...config
}: Config) {
const tmpInterceptors: Interceptor<any>[] = [xmlHttpRequest];
interceptors.request.forEach(interceptor => {
tmpInterceptors.unshift(interceptor);
}); interceptors.response.forEach(interceptor => {

tmpInterceptors.push(interceptor);

}); let chain: Promise<any> = Promise.resolve(config);

tmpInterceptors.forEach(interceptor => (chain = chain.then(interceptor)));

return chain as Promise<T>;

}

类似 interceptor1 interceptor2,新增两个拦截器用于响应后执行,

function interceptor3<T>(res: T) {
console.log("interceptor 3");
return res;
} function interceptor4<T>(res: T) {

console.log("interceptor 4");

return res;

}

测试代码:

request({
url: "https://dog.ceo/api/breeds/image/random",
method: "GET",
interceptors: {
request: [interceptor1, interceptor2],
response: [interceptor3, interceptor4]
}
}).then(res => {
console.log("load success");
});

运行结果:

interceptor 2
interceptor 1
interceptor 3
interceptor 4
load success

不难看出,当我们发起一次 axios 请求时,其实是发起了一次 Promise 链,链上的函数顺次执行。

request interceptor 1
request interceptor 2
...
request
response interceptor 1
response interceptor 2
...

因为拉弓没有回头箭,请求发出后,能够取消的是后续操作,而不是请求本身,所以上面的 Promise 链中,需要实现 request 之后的拦截器和后续回调的取消执行。

request interceptor 1
request interceptor 2
...
request
#

从 axios 源码中了解到的 Promise 链与请求的取消的更多相关文章

  1. Axios源码深度剖析 - 替代$.ajax,成为xhr的新霸主

    前戏 在正式开始axios讲解前,让我们先想想,如何对现有的$.ajax进行简单的封装,就可以直接使用原声Promise了? let axios = function(config){ return ...

  2. Axios源码分析

    Axios是一个基于promise的HTTP库,可以用在浏览器和node.js中. 文档地址:https://github.com/axios/axios axios理解和使用 1.请求配置 { // ...

  3. Axios源码深度剖析

    Axios源码深度剖析 - XHR篇 axios 是一个基于 Promise 的http请求库,可以用在浏览器和node.js中,目前在github上有 42K 的star数 分析axios - 目录 ...

  4. 一比一还原axios源码(零)—— 概要

    从vue2版本开始,vue-resource就不再被vue所维护和支持,官方也推荐使用axios,所以,从我使用axios至今,差不多有四五年了,这四五年的时间只能算是熟练应用,很多内部的实现和原理不 ...

  5. 一比一还原axios源码(一)—— 发起第一个请求

    上一篇文章,我们简单介绍了XMLHttpRequest及其他可以发起AJAX请求的API,那部分大家有兴趣可以自己去扩展学习.另外,简单介绍了怎么去读以及我会怎么写这个系列的文章,那么下面就开始真正的 ...

  6. 一比一还原axios源码(三)—— 错误处理

    前面的章节我们已经可以正确的处理正确的请求,并且通过处理header.body,以及加入了promise,让我们的代码更像axios了.这一章我们一起来处理ajax请求中的错误. 一.错误处理 首先我 ...

  7. 一比一还原axios源码(四)—— Axios类

    axios源码的分析,到目前为止,算上第0章已经四章了,但是实际上,还都没有进入axios真正的主线,我们来简单回顾下.最开始我们构建了get请求,写了重要的buildURL方法,然后我们处理请求体请 ...

  8. axios源码解析 - 请求拦截器

    axios请求拦截器,也就是在请求发送之前执行自定义的函数. axios源码版本 - ^0.27.2 (源码是精简版) 平时在业务中会这样去写请求拦截器,代码如下: // 创建一个新的实例 var s ...

  9. 从express源码中探析其路由机制

    引言 在web开发中,一个简化的处理流程就是:客户端发起请求,然后服务端进行处理,最后返回相关数据.不管对于哪种语言哪种框架,除去细节的处理,简化后的模型都是一样的.客户端要发起请求,首先需要一个标识 ...

随机推荐

  1. DOM的高级操作-一种JS控制元素的视觉假象

    1.运动中的边界处理(让其在一个指定区域内运动) 当元素的offsetLeft值超出一定距离或达到一个我们想要设置的边界值时,停止计时器. var timer; timer = setInterval ...

  2. POJ-1511 Invitation Cards( 最短路,spfa )

    题目链接:http://poj.org/problem?id=1511 Description In the age of television, not many people attend the ...

  3. Navicate for mysql如何导入一个sql文件

    我在做的项目是宜立方商城的项目,现在需要把见表的sql文件导入到navicate中去,步骤如下: ①新建一个数据库,如下: ②在数据库名字上右键,选择运行sql文件 ③选择如下sql文件 ④刷新之后:

  4. Salesforce LWC学习(七) Navigation & Toast

    上一篇我们介绍了针对LWC中常用的LDS的适配的wire service以及@salesforce模块提供的相关的service,其实LWC中还提供其他的好用的service,比如针对导航相关的lig ...

  5. Android图片的缩放效果

    一.概述 Android 图片要实现:手势滑动,双击变大,多点触控的效果. 其实是有一定难度的,我们需要用Matrix ,GestureDetector 等等需要完成一个复杂的逻辑才能实现,然而今天我 ...

  6. Hadoop&Hbase 双机热备--Pacemaker&DRBD部署

    相关文章   DRBD的介绍请参考http://blog.csdn.net/rzhzhz/article/details/7103772   DRBD的部署请参考http://blog.csdn.ne ...

  7. Java-HashSet集合中的几种遍历方式

    //我们先创建一个set集合 public static void main(String[] args) { Set<Integer> sets = new HashSet<> ...

  8. 学习数据库SQL语句1

    数据库一直让我很头大,正好出差有空,就重新恶补起来吧!(网站:http://www.w3school.com.cn/sql) 我准备把我每天学到的都记录下来=.= (红色字体代表关键词,蓝色字体是我个 ...

  9. 类似Flag counter被园子禁用后的备选方案

    背景介绍 2019年9月4日,园子发生严重事故,影响范围为整个园子.随着bug的修复,从个人博客无法访问——>公告栏部分功能禁用——>文件无法上传(多个文章中的图片均加载不出来)——> ...

  10. Comet OJ - Contest #11 题解&赛后总结

    Solution of Comet OJ - Contest #11 A.eon -Problem designed by Starria- 在模 10 意义下,答案变为最大数的最低位(即原数数位的最 ...