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. python中的全局变量

    1. 在函数中定义的局部变量如果和全局变量同名,则会使用局部变量(即隐藏全局变量). 示例: x = 1 def func(): x = 2 print x func() print x 运行结果: ...

  2. P3317 [SDOI2014]重建 变元矩阵树定理 高斯消元

    传送门:https://www.luogu.org/problemnew/show/P3317 这道题的推导公式还是比较好理解的,但是由于这个矩阵是小数的,要注意高斯消元方法的使用: #include ...

  3. Codeforces Round #480 (Div. 2) C - Posterized

    题目地址:http://codeforces.com/contest/980/problem/C 官方题解: 题解:一共256个像素网格,可以把这个256个分组,每个分组大小<=k.给出n个像素 ...

  4. 共价大爷游长沙 lct 维护子树信息

    这个题目的关键就是判断 大爷所有可能会走的路 会不会经过询问的边. 某一条路径经过其中的一条边, 那么2个端点是在这条边的2测的. 现在我们要判断所有的路径是不是都经过 u -> v 我们以u为 ...

  5. Convolution model by吴恩达

    # GRADED FUNCTION: model def model(X_train, Y_train, X_test, Y_test, learning_rate = 0.009, num_epoc ...

  6. 深入SpringMVC视图解析器

    ViewResolver的主要职责是根据Controller所返回的ModelAndView中的逻辑视图名,为DispatcherServlet返回一个可用的View实例.SpringMVC中用于把V ...

  7. java中自定义注解的应用

    要想深刻的理解注解,我们必须能实现自己的注解,然后应用自己的注解去实现特定的业务,使用注解可以更优雅的做到某些事情. 有这样一个场景,在需要文件导出时,我们需要将一个model中的一些重要字段导出到c ...

  8. 【Spring】编程式事务和声明式事务

    一.概述 二.准备工作 1. 创建表 2. 创建项目并引入Maven依赖 3. 编写实体类 4. 编写Dao层 5. 业务层 6. XML中的配置 7. 测试 三.编程式事务 1. 在业务层代码上使用 ...

  9. 【Spring】事务

    一.数据库事务概述 二.Spring中事务 1. Spring 事务管理: 2. Spring 事务管理的API: 2.1 API概述 2.2 PlatformTransactionManager 接 ...

  10. mysql 查询参数尾部有空格时被忽略

    最近再使用mysql时,无意见发现,当我查询参数尾部输入若干个空格时,依然可以查出和不加空格相同的结果,像这样 select * from wa where name='be ' 等同于 select ...