xhr.abort(), 300);取消请求用xhr.abort(),那么什么时候执行这个取消请求的操作呢?得有一_axios.isca">
赞
踩
let xhr = new XMLHttpRequest();
xhr.open("GET", "https://developer.mozilla.org/", true);
xhr.send();
setTimeout(() => xhr.abort(), 300);
得有一个信号告诉 xhr 对象什么时候执行取消操作。
取消请求就是未来某个时候要做的事情,那就想到是 Promise。
Promise 的 then 方法只有 Promise 对象的状态变为 resolved 的时候才会执行。
利用这个特点,在 Promise 对象的 then 方法中执行取消请求的操作。
而对于 Axios 来说,我们可以通过 Axios 内部提供的 CancelToken 来取消请求:
两种方法
const cancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('/user/12345', { cancelToken: source.token }).catch(function(thrown) { if (axios.isCancel(thrown)) { // 源码有,判断是否取消请求 console.log('Request canceled', thrown.message); } else { // 处理错误 } }); axios.post('/user/12345', { name: 'new name' }, { cancelToken: source.token }) // 取消请求(message 参数是可选的) source.cancel('Operation canceled by the user.');
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// cancel the request
cancel();
let defaults = require('./defaults') let utils = require('./utils') function createInstance(defaultConfig) { // 创建 axios 实例 var context = new Axios(defaultConfig); // 把 instance 指向 Axios.prototype.request 方法 // bind返回一个新的函数,内部去执行request var instance = bind(Axios.prototype.request, context); // extend 将第二个参数对象的方法拷贝到第一个对象上去 // 把 Axios.prototype(原型对象) 上的方法扩展到 instance 上,指定上下文是 context // 方法有:request()/get()/post()/put()/delete() utils.extend(instance, Axios.prototype, context); // 把 context 上的方法扩展到 instance 上 // 把 Axios实例对象 上的方法扩展到 instance 上,有defaults 和 interceptors属性 utils.extend(instance, context); // 导出 instance 对象 return instance; } // 实例一个 axios var axios = createInstance(defaults); // 添加 create 方法,返回 createInstance 函数,参数为自定义配置 + 默认配置 axios.create = function create(instanceConfig) { return createInstance(mergeConfig(axios.defaults, instanceConfig)); }; // ... // 向这个实例添加 CancelToken 方法 axios.Cancel = require('./cancel/Cancel'); axios.CancelToken = require('./cancel/CancelToken'); axios.isCancel = require('./cancel/isCancel'); // Expose all/spread axios.all = function all(promises) { return Promise.all(promises); }; // 导出实例 axios module.exports = axios; // Allow use of default import syntax in TypeScript module.exports.default = axios;
CancelToken
挂载 source
方法用于创建自身实例,并且返回 {token, cancel}
token
是构造函数 CancelToken
的实例,cancel
方法接收构造函数 CancelToken
内部的一个 cancel
函数,用于取消请求
创建实例中,有一步是创建处于 pengding
状态的 promise
,并挂在实例方法上,外部通过参数 cancelToken
将实例传递进 axios
内部,内部调用 cancelToken.promise.then
等待状态改变
当外部调用方法 cancel
取消请求,pendding
状态就变为 resolve
,即取消请求并且抛出 reject(message)
export class CancelToken { // new的时候执行构造函数 constructor(exactor) { if(typeof exactor !== 'function') { throw new TypeError('executor must be a function.') } // 将promise的决定权交给了cancel函数 // 同时做了防止多次cancel, // promise里将resolve存到外部,即外部也可以调用 const resolvePromise; this.promise = new promise(resolve => { resolvePromise = resolve }) this.reason = undefined const cancel = (message) => { // 如果有reason说明已经取消过了,直接return if (this.reason) { // 取消过的直接返回 return } // 外部调用 cancel 取消请求方法,Cancel 实例化,保存 message 并增加已取消请求标示 // new Cancel(message) 后等于 { message, __CANCEL__ : true} this.reason = new Cancel(message) // 将取消请求的promise指定为成功,值为reason resolvePromise(this.reason) } // 立即执行接受的执行器函数,并传入用于取消请求的cancel函数 exactor(cancel) } // source 其实本质上是一个语法糖 里面做了封装 static source () { /** * 构造函数 CancelToken 实例化,用回调函数做参数,并且回调函数 * 接收 CancelToken 内部的函数 c,保存在变量 cancel 中, * 后面调用 cancel 即取消请求 */ const cancel; const token = new CancelToken(function executor(c) { cancel = c }) return { cancel: cancel, token: token } } }
'use strict'; /** * 当取消一个请求时, 需要将Cancel对象作为一个error抛出 * A `Cancel` is an object that is thrown when an operation is canceled. * * @class * @param {string=} message The message. */ function Cancel(message) { this.message = message; } Cancel.prototype.toString = function toString() { return 'Cancel' + (this.message ? ': ' + this.message : ''); }; // 用于标识是一个取消的error Cancel.prototype.__CANCEL__ = true; module.exports = Cancel;
'use strict';
/*
用于判断一个error是不是一个cancel错误
*/
module.exports = function isCancel(value) {
return !!(value && value.__CANCEL__);
};
当我们执行 const source = CancelToken.source()的时候,source 对象有两个字段,
一个是 token 对象,另一个是 cancel 函数。
module.exports = function xhrAdapter (config) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() // 设置完整路径,拼接url,例如:https://www.baidu,com + /api/test var fullPath = buildFullPath(config.baseURL, config.url); // 初始化一个请求,拼接url,例如:https://www.baidu,com/api/test + ?a=10&b=20 xhr.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); // 超时断开,默认 0 永不超时 xhr.timeout = config.timeout; xhr.onreadystatechange = function() { // request不存在或请求状态不是4, 直接结束 if (!request || request.readyState !== 4) { return; } // The request errored out and we didn't get a response, this will be // handled by onerror instead // With one exception: request that using file: protocol, most browsers // will return status as 0 even though it's a successful request if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { return; } // 准备response对象 var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; var response = { data: responseData, status: request.status, statusText: request.statusText, headers: responseHeaders, config: config, request: request } // 根据响应状态码来确定请求的promise的结果状态(成功/失败) settle(resolve, reject, response); // 将请求对象赋空 request = null; }; // 绑定请求中断监听 request.onabort = function handleAbort() { if (!request) { return; } // reject promise, 指定aborted的error reject(createError('Request aborted', config, 'ECONNABORTED', request)); // Clean up request request = null; }; // Handle low level network errors request.onerror = function handleError() { // Real errors are hidden from us by the browser // onerror should only fire if it's a network error reject(createError('Network Error', config, null, request)); // Clean up request request = null; }; // Handle timeout request.ontimeout = function handleTimeout() { reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', request)); // Clean up request request = null; }; // 标准浏览器(有 window 和 document 对象) if (utils.isStandardBrowserEnv()) { // 非同源请求,需要设置 withCredentials = true,才会带上 cookie var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? cookies.read(config.xsrfCookieName) : undefined; if (xsrfValue) { requestHeaders[config.xsrfHeaderName] = xsrfValue; } } // request对象携带 headers 去请求 if ('setRequestHeader' in xhr) { utils.forEach(requestHeaders, function setRequestHeader(val, key) { if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { // data 为 undefined 时,移除 content-type,即不是 post/put/patch 等请求 delete requestHeaders[key]; } else { xhr.setRequestHeader(key, val); } }); } // 如果有配置config.cancelToken if(config.cancelToken) { // 处理取消的 config.cancelToken.promise. --> CancelToken.js里 config.cancelToken.promise.then(function onCanceled(cancel) { if (!xhr) { return } // 中断请求 xhr.abort() // 让请求的promise失败 reject(cancel) // clean up xhr,请求结束要把xhr置为null, 不为null说明请求还没有完成 xhr = null }) } // 发送请求 xhr.send(requestData) }) }
这里只做了get处理,主要的目的就是为了axios是如何取消请求的
CancelToken 的构造函数中需要传入一个函数,
而这个函数的作用其实是为了将能控制内部 Promise 的 resolve 函数暴露出去,暴露给 source 的 cancel 函数。
这样内部的 Promise 状态就可以通过 source.cancel() 方法来控制啦
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。