首頁  >  文章  >  web前端  >  axios原始碼解析如何實作一個HTTP請求庫

axios原始碼解析如何實作一個HTTP請求庫

不言
不言原創
2018-07-25 10:15:511508瀏覽

這篇文章分享給大家的內容是關於axios原始碼解析如何實作一個HTTP請求庫,內容很詳細,接下來我們就來看看具體的內容,希望可以幫助到大家。

概述

在前端開發過程中,我們常常會遇到需要傳送非同步請求的情況。而使用一個功能齊全,介面完善的HTTP請求庫,能夠大幅減少我們的開發成本,提高我們的開發效率。

axios是近年來非常火熱的一個HTTP請求庫,目前在GitHub中已經擁有了超過40K的star,受到了各位大佬的推薦。

今天,我們就來看下,axios到底是如何設計的,其中又有哪些值得我們學習的地方。我在寫這邊文章時,axios的版本為0.18.0。我們就以這個版本的程式碼為例,來進行具體的原始碼閱讀與分析。目前axios所有原始碼檔案都在lib資料夾中,因此我們下文中提到的路徑都是指lib資料夾中的路徑。

本文的主要內容有:

  • 如何使用axios

  • axios的核心模組是如何設計與實作的(請求、攔截器、撤回)

  • axios的設計有什麼值得借鏡的地方

##如何使用axios

想要了解axios的設計,我們首先需要來看看下axios是如何使用的。我們透過一個簡單範例來介紹以下axios的API。

發送請求
axios({
  method:'get',
  url:'http://bit.ly/2mTM3nY',
  responseType:'stream'
})
  .then(function(response) {
  response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});

這是一個官方的API範例。從上面的程式碼我們可以看到,axios的用法與jQuery的ajax很相似,都是透過回傳一個Promise(也可以透過success的callback,不過建議使用Promise或await)來繼續後面的操作。

這個程式碼範例很簡單,我就不過多贅述了,以下讓我們來看如何加入一個篩選器函數。

增加攔截器(Interceptors)函數
// 增加一个请求拦截器,注意是2个函数,一个处理成功,一个处理失败,后面会说明这种情况的原因
axios.interceptors.request.use(function (config) {
    // 请求发送前处理
    return config;
  }, function (error) {
    // 请求错误后处理
    return Promise.reject(error);
  });

// 增加一个响应拦截器
axios.interceptors.response.use(function (response) {
    // 针对响应数据进行处理
    return response;
  }, function (error) {
    // 响应错误后处理
    return Promise.reject(error);
  });

透過上面的範例我們可以知道:在請求傳送前,我們可以針對請求的config參數進行資料處理;而在請求回應後,我們也能針對回傳的資料進行特定的操作。同時,在請求失敗和回應失敗時,我們都可以進行特定的錯誤處理。

取消HTTP請求

在完成搜尋相關的功能時,我們經常需要頻繁的發送請求來進行資料查詢的情況。通常來說,我們在下次請求發送時,就需要取消上次請求。因此,取消請求相關的功能也是一個優點。 axios取消請求的範例程式碼如下:

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.');
透過上面的範例我們可以看到,axios使用的是基於CancelToken的一個撤回提案。不過,目前該提案已被撤回,具體詳情可見此處。具體的撤回實作方法我們會在後面的章節源碼分析的時候進行說明。

axios的核心模組是如何設計與實現的

透過上面的例子,我相信大家對axios的使用方法都有了大致的了解。下面,我們將依照模組來對axios的設計與實作進行分析。下圖是我們在這篇部落格中將會涉及到的相關的axios的文件,如果讀者有興趣的話,可以透過clone相關程式碼結合部落格進行閱讀,這樣能夠加深對相關模組的理解。

HTTP請求模組

作為核心模組,axios發送請求相關的程式碼位於

core/dispatchReqeust.js檔案中。由於篇幅有限,下面我選取部分重點的原始碼進行簡單的介紹:

module.exports = function dispatchRequest(config) {
    throwIfCancellationRequested(config);

    // 其他源码

    // default adapter是一个可以判断当前环境来选择使用Node还是XHR进行请求发送的模块
    var adapter = config.adapter || defaults.adapter; 

    return adapter(config).then(function onAdapterResolution(response) {
        throwIfCancellationRequested(config);

        // 其他源码

        return response;
    }, function onAdapterRejection(reason) {
        if (!isCancel(reason)) {
            throwIfCancellationRequested(config);

            // 其他源码

            return Promise.reject(reason);
        });
};
透過上面的程式碼和範例我們可以知道,

dispatchRequest方法是透過取得config.adapter 來得到發送請求的模組的,我們自己也可以透過傳入符合規範的adapter函數來替換掉原生的模組(雖然一般不會這麼做,不過也算是一個松耦合擴展點)。

default.js檔案中,我們能夠看到相關的adapter選擇邏輯,也就是根據目前容器中特有的一些屬性和建構子來進行判斷。

function getDefaultAdapter() {
    var adapter;
    // 只有Node.js才有变量类型为process的类
    if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
        // Node.js请求模块
        adapter = require('./adapters/http');
    } else if (typeof XMLHttpRequest !== 'undefined') {
        // 浏览器请求模块
        adapter = require('./adapters/xhr');
    }
    return adapter;
}
axios中XHR模組較為簡單,為XMLHTTPRequest物件的封裝,我們在這裡就不過多進行介紹了,有興趣的同學可以自行閱讀,程式碼位於

adapters/xhr.js文件中。

攔截器模組

了解了

dispatchRequest實作的HTTP請求發送模組,我們來看下axios是如何處理請求和回應攔截函數的。讓我們看下axios中請求的統一入口request函數。

Axios.prototype.request = function request(config) {

    // 其他代码

    var chain = [dispatchRequest, undefined];
    var promise = Promise.resolve(config);

    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
        chain.unshift(interceptor.fulfilled, interceptor.rejected);
    });

    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
        chain.push(interceptor.fulfilled, interceptor.rejected);
    });

    while (chain.length) {
        promise = promise.then(chain.shift(), chain.shift());
    }

    return promise;
};
這個函數是axios發送請求的入口,因為函數實作比較長,我就簡單說一下相關的設計想法:

  1. chain是一个执行队列。这个队列的初始值,是一个带有config参数的Promise。

  2. 在chain执行队列中,插入了初始的发送请求的函数dispatchReqeust和与之对应的undefined。后面需要增加一个undefined是因为在Promise中,需要一个success和一个fail的回调函数,这个从代码promise = promise.then(chain.shift(), chain.shift());就能够看出来。因此,dispatchReqeustundefined我们可以成为一对函数。

  3. 在chain执行队列中,发送请求的函数dispatchReqeust是处于中间的位置。它的前面是请求拦截器,通过unshift方法放入;它的后面是响应拦截器,通过push放入。要注意的是,这些函数都是成对的放入,也就是一次放入两个。

通过上面的request代码,我们大致知道了拦截器的使用方法。接下来,我们来看下如何取消一个HTTP请求。

取消请求模块

取消请求相关的模块在Cancel/文件夹中。让我们来看下相关的重点代码。

首先,让我们来看下元数据Cancel类。它是用来记录取消状态一个类,具体代码如下:

    function Cancel(message) {
      this.message = message;
    }

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

    Cancel.prototype.__CANCEL__ = true;

而在CancelToken类中,它通过传递一个Promise的方法来实现了HTTP请求取消,然我们看下具体的代码:

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 = function source() {
    var cancel;
    var token = new CancelToken(function executor(c) {
        cancel = c;
    });
    return {
        token: token,
        cancel: cancel
    };
};

而在adapter/xhr.js文件中,有与之相对应的取消请求的代码:

if (config.cancelToken) {
    // 等待取消
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
            return;
        }

        request.abort();
        reject(cancel);
        // 重置请求
        request = null;
    });
}

结合上面的取消HTTP请求的示例和这些代码,我们来简单说下相关的实现逻辑:

  1. 在可能需要取消的请求中,我们初始化时调用了source方法,这个方法返回了一个CancelToken类的实例A和一个函数cancel。

  2. 在source方法返回实例A中,初始化了一个在pending状态的promise。我们将整个实例A传递给axios后,这个promise被用于做取消请求的触发器。

  3. 当source方法返回的cancel方法被调用时,实例A中的promise状态由pending变成了fulfilled,立刻触发了then的回调函数,从而触发了axios的取消逻辑——request.abort()

axios的设计有什么值得借鉴的地方

发送请求函数的处理逻辑

在之前的章节中有提到过,axios在处理发送请求的dispatchRequest函数时,没有当做一个特殊的函数来对待,而是采用一视同仁的方法,将其放在队列的中间位置,从而保证了队列处理的一致性,提高了代码的可阅读性。

Adapter的处理逻辑

在adapter的处理逻辑中,axios没有把http和xhr两个模块(一个用于Node.js发送请求,另一个则用于浏览器端发送请求)当成自身的模块直接在dispatchRequest中直接饮用,而是通过配置的方法在default.js文件中进行默认引入。这样既保证了两个模块间的低耦合性,同时又能够为今后用户需要自定义请求发送模块保留了余地。

取消HTTP请求的处理逻辑

在取消HTTP请求的逻辑中,axios巧妙的使用了一个Promise来作为触发器,将resolve函数通过callback中参数的形式传递到了外部。这样既能够保证内部逻辑的连贯性,也能够保证在需要进行取消请求时,不需要直接进行相关类的示例数据改动,最大程度上避免了侵入其他的模块。

总结

本文对axios相关的使用方式、设计思路和实现方法进行了详细的介绍。读者能够通过上述文章,了解axios的设计思想,同时能够在axios的代码中,学习到关于模块封装和交互等相关的经验。

由于篇幅原因,本文仅针对axios的核心模块进行了分解和介绍,如果对其他代码有兴趣的同学,可以去GitHub进行查看。

相关推荐:

React组件中绑定this的原因分析

angularjs关于页面模板清除的使用方法

以上是axios原始碼解析如何實作一個HTTP請求庫的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn