(Ajax)axios源碼簡析(三)——請求與取消請求

傳送門:node

請求過程

Axios.prototype.request中咱們看到,要先經過請求攔截器,才能進行請求。下面看一下dispatchRequest()是如何實現的ios

// /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採起不一樣的請求方式。axios

// /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請求的細節,能夠仔細研究。segmentfault

取消請求

官方文檔中的調用方法promise

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類的構造函數以下:post

// /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狀態。ui

// /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源碼分析——取消請求

相關文章
相關標籤/搜索