(Ajax)axios源码简析(三)——请求与取消请求

传送门:

请求过程

Axios.prototype.request中我们看到,要先通过请求拦截器,才能进行请求。下面看一下dispatchRequest()是如何实现的

// /lib/core/dispatchRequest.js

module.exports = function dispatchRequest(config) {
    // 判断是否已经取消请求
    throwIfCancellationRequested(config);
    
    /* 对请求的url、headers、data进行处理 */
    
    // 发动请求的函数,返回一个promise
    var adapter = config.adapter || defaults.adapter;
    
    return adapter(config).then(function onAdapterResolution(response) {
        // 判断是否已经取消请求
        throwIfCancellationRequested(config);

        // 处理返回的数据
        response.data = transformData(
            response.data,
            response.headers,
            config.transformResponse
        );

        return response;
    }, function onAdapterRejection(reason) {
        if (!isCancel(reason)) {
            // 判断是否已经取消请求
            throwIfCancellationRequested(config);

            // 处理返回的错误信息
            if (reason && reason.response) {
                reason.response.data = transformData(
                    reason.response.data,
                    reason.response.headers,
                    config.transformResponse
                );
            }
        }

        return Promise.reject(reason);
    });

如果用户有在配置中传入adapter,将使用defaults.adapter,根据运行环境是浏览器还是nodejs采取不同的请求方式。

// /lib/defaults.js
function getDefaultAdapter() {
    var adapter;
    if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
        // nodejs环境
        adapter = require('./adapters/http');
    } else if (typeof XMLHttpRequest !== 'undefined') {
        // 浏览器环境
        adapter = require('./adapters/xhr');
    }
    return adapter;
}


var defaults = {
    adapter: getDefaultAdapter(),
    
    /* 其他配置 */
};

modules.exports = defaults;

/lib/adapters/http.js/lib/adapters/xhr.js两个文件导出的函数都返回一个promise,具体的实现方式就不分析了。里面有很多http请求的细节,可以仔细研究。

取消请求

官方文档中的调用方法

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 {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

我们进入CancelToken类,找到了CancelToken.source()方法:

// /lib/cancel/CancelToken

CancelToken.source = function source() {
    var cancel;
    var token = new CancelToken(function executor(c) {
        cancel = c;
    });
    return {
        token: token,
        cancel: cancel
    };
};

可以看出,CancelToken.source().token是一个CancelToken类的实例,CancelToken.source().cancelnew CacelToken()时传入参数(一个函数)的参数(也是个函数),通过CancelToken的构造函数可以看出:

// /lib/cancel/CancelToken

function CancelToken(executor) {
    if (typeof executor !== 'function') {
        throw new TypeError('executor must be a function.');
    }

    var resolvePromise;
    this.promise = new Promise(function promiseExecutor(resolve) {
        resolvePromise = resolve;
    });

    var token = this;
    executor(function cancel(message) {
        if (token.reason) {
            // Cancellation has already been requested
            return;
        }

        token.reason = new Cancel(message);
        resolvePromise(token.reason);
    });
}

CancelToken.source().cancel就是这个函数:

function cancel(message) {
    if (token.reason) {
        // Cancellation has already been requested
        return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
}

CancelToken.source().tokenpromisereason两个属性,promise 一直处于 pending状态,reason属性是一个Cancel类的实例,Cancel类的构造函数如下:

// /lib/cancel/Cancel.js
function Cancel(message) {
    this.message = message;
}

Cancel.prototype.toString = function toString() {
    return 'Cancel' + (this.message ? ': ' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

在源码中,有以下几种方式检测是否执行了取消请求。
1 检测config.cancelToken是否有reason属性,如果有,将reason抛出,axios进入rejected状态。

// /lib/core/dispatchRequest.js
function throwIfCancellationRequested(config) {
    if (config.cancelToken) {
        config.cancelToken.throwIfRequested();
    }
}


module.exports = function dispatchRequest(config) {
    // 判断是否已经取消请求
    throwIfCancellationRequested(config);
    
    /* ... */
};


// /lib/cancel/CancelToken
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
    if (this.reason) {
        throw this.reason;
    }
};

2 在请求过程中,执行CancelToken.source().tokenpromise属性中的resolve函数,参数是CancelToken.source().token.reason,并将其抛出,promise进入rejected状态

if (config.cancelToken) {
    // Handle cancellation
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
            return;
        }

        // 取消请求
        request.abort();
        
        // promise进入rejected
        reject(cancel);
        // Clean up request
        request = null;
    });
}

调用方法中catch接到的thrown,就是CancelToken.source().token.reason

如果在使用axios时候,只在config中添加{cancelToken: source.token},而不调用source.cancel(),则CancelToken.source().token不会有reason属性,CancelToken.source().token.promise也一直是pending状态。请求不会取消。

参考

深入浅出 axios 源码
axios源码分析——取消请求

    原文作者:kimi013
    原文地址: https://segmentfault.com/a/1190000016186416
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞