自己動手實現(xiàn)一個 axios

自己動手實現(xiàn)一個 axios

前言

作為一名前端er黔宛,對于數(shù)據(jù)請求的第三方工具axios搞坝,一定不會陌生刮刑,如果還是有沒有用過,或者不了解的小伙伴盖喷,這里給你們準備了貼心的中文文檔 爆办,聰明的你們一看就會~

唔,為了更好的了解和學習 axios 封裝思想和實現(xiàn)原理课梳,我們一起來動手來實現(xiàn)一個簡版的 axios ~

前期準備

工欲善其事距辆,必先利其器,我們在開始我們的項目之前暮刃,一定要做好其相關的準備工作跨算,我們需要準備的也很簡單,一個 客戶端(client) 方便我們調試椭懊,一個 服務端(server) 做接口測試~

服務端

服務端我這里為了方便調試诸蚕,直接使用基于 nodejs 實現(xiàn)的 koa 框架,通過 koa-router 來實現(xiàn)接口,參考代碼如下:

const Koa = require('koa');
const KoaRouter = require('koa-router')

//app 實例
const app = new Koa();
//router 實例
const router = new KoaRouter();

//請求中間件背犯,解決跨域
app.use(async (ctx,next)=>{
    ctx.set('Access-Control-Allow-Origin', '*');
    ctx.set('Access-Control-Allow-Headers', 'content-type,token,accept');
    ctx.set('Access-Control-Allow-Methods', 'POST,GET,OPTIONS');
    ctx.set("Content-Type", "application/json")
    ctx.set('Access-Control-Max-Age', 10)
    //處理 options
    if (ctx.request.method.toLowerCase() === 'options'){
        ctx.response.status = 200;
        ctx.body = '';
    } else await next();
})

//接口測試地址
router.get('/',async  ctx=>{
    ctx.body = {
        data : 'Hello World'
    }
})

router.get('/user/info',async ctx =>{
    ctx.body = {
        name : 'Chris' ,
        msg : 'Hello World'
    }
})

app.use(router.routes());

//啟動服務
app.listen(3000,function () {
    console.log('app is running ~')
})

這里我們通過 node app.js 就可以啟動我們的服務坏瘩,如果你在服務端控制臺看到 app is running ~ 說明你的服務已經(jīng)啟動成功,此時你打開瀏覽器訪問 http://localhost:3000/ 媳板,不出意外你能看到 Hello World 的返回信息桑腮,說明服務端這一塊就 配置 ok 了,是不是 so easy~

客戶端

客戶端這塊的話蛉幸,emm,我們需要準備一個 html 文件丛晦,和 一個 js 文件夾奕纫,主要存放我們要實現(xiàn)的核心代碼~

html 文件非常簡單,如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>axios-demo</title>
</head>
<body>
    <div class="">
        <h1>axios 的簡版實現(xiàn)</h1>
    </div>
    <script src="./js/main.js"></script>
</body>
</html>

其中 main.js 是我們的要使用的js文件~

要注意的是烫沙,由于我們的代碼是基于 es6 模塊化開發(fā)的匹层,如果直接丟到瀏覽器里,是無法識別的锌蓄,會報錯升筏,不過也沒關系,我們可以借助第三方的打包工具幫我們搞定這些事~

打包不是我們主要關注的問題瘸爽,這里我就不采用webpack這種工具您访,給大家推薦一個零配置的打包工具 Parcel ,使用方式也很簡單剪决,在你的客戶端目錄下通過 npm init -y 初始化灵汪,通過 npm install parcel-bundler --save-dev 安裝 Parcel ,然后在你的 package.json 文件中添加如下腳本:

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "parcel ./*.html",
    "build": "parcel build ./*.html"
  },

這樣柑潦,我們可以通過 npm run dev 腳本打開我們的 html 文件享言,如果你們跟我們配置一樣,那么你在瀏覽器的 http://localhost:1234/ 地址會看到 axios 的簡版實現(xiàn) 這幾個字渗鬼,并且控制臺不會報錯览露,就證明一切準備 ok 了!譬胎!

具體實現(xiàn)

雛形

我們首先在客戶端的 js 文件夾下創(chuàng)建一個 axios 的文件夾差牛,里面存放我們自己實現(xiàn)的 axios 相關代碼。

axios 文件夾下新建 index.js 入口文件 和 axios.js 核心js文件~

axios的本質是一個類银择,這里我們通過 class 實現(xiàn)多糠,即:

axios.js

class Axios {
    constructor(){
    
    }
}
export default Axios;

通過 index.js 進行 new 初始化,導出 axios 實例浩考,這也是我們在使用axios中 不需要 new 的原因~

index.js

import Axios from './Axios'

const axios = new Axios();

export default axios;

此時夹孔,我們只需要在 main.js 通過 import 導入即可

main.js

import axios from './axios'

console.log(axios)

此時整個 axios 雛形就已經(jīng)完成了~

一個簡單的get請求

我們先實現(xiàn)一個簡單 axios.get 方法,即通過 axios.get 獲取我們服務端的響應~

我們回憶一下我們平時使用 axios.get 的時候,通常是 axios.get().then 的方式搭伤,那么我們首先就確定了我們的 axios.get 方法返回的是一個 Promise 對象只怎,我們在 axios.js 中添加這個方法~

    get(url){
        return new Promise((resolve => {
            let xhr = new XMLHttpRequest();
            
            xhr.onload = function() {
                resolve({
                    data: JSON.parse(xhr.responseText),
                    status: xhr.status,
                    statusText: xhr.statusText
                });
            }
            
            xhr.open( 'get', url , true );
            xhr.send();
        }))
    }

此時我們在 main.js 調用 get 方法 ,

axios.get('http://127.0.0.1:3000/user/info').then(res=>{
    console.log(res);
})

控制臺輸出如下:

對比官方的 axios,我們少了比如 header 之類的信息怜俐,因為官方對請求返回做了二次包裝身堡,這里我們只是簡單的json處理,具體的要根據(jù)返回的數(shù)據(jù)類型做不同的處理~

默認配置

我們在使用官方 axios 的拍鲤,會有很多配置項贴谎,包括全局配置,實例配置和請求配置季稳,因此我們就來看看配置信息這一塊擅这。

我們在 axios 文件夾下新建一個 config.js ,用于 axios 的默認配置景鼠,為了方便仲翎,我們的默認配置如下:

config.js

export default {
    baseURL : '' ,
    method : 'get' ,
    headers : {
        'content-type' : 'application/json'
    }
}

我們將默認的配置傳入到我們的構造函數(shù)中,如下:

index.js

import Axios from './Axios'
import config from './config'

const axios = new Axios(config);

export default axios;

所以铛漓,我們需要在構造函數(shù)中接收一個 config 參數(shù)進行處理溯香,即將默認配置寫入到實例中,即:

axios.js

constructor(config){
    //配置
    this.defaults = config;
}

這樣我們的 get 方法里請求的 url 就可以改寫成 :

this.defaults.baseURL += url
......
xhr.open( 'get', this.defaults.baseURL , true );
//添加header頭
for(let key in configs.headers){
    xhr.setRequestHeader(key,configs.headers[key])
}
......

如果你此時在config.js 中配置 baseURL 那么浓恶,你在axios.get中就可以省略前面的 baseURL 玫坛, 因為在請求之前已經(jīng)幫你拼接完成了~

當然,你也可以通過 axios.defaults.baseURL = xxx這種方式修改默認配置问顷,都是沒問題的~

實例配置

在使用官方 axios 的時候昂秃,我們可以通過一個create 方法創(chuàng)建一個axios實例,并傳入配置信息即可杜窄,我們只需要在 index.js 中創(chuàng)建的 axios 添加一個 create 方法即可 肠骆。

index.js

axios.create = function (config) {
    return new Axios(config);
}

這樣我們也可以通過 create 方法構建一個 axios 實例,它也擁有相應的方法~

但是這么做存在一個問題塞耕,如果我們創(chuàng)建多個實例蚀腿,傳入不同的 config ,由于我們直接在構建的時候 通過 this.defaults = config; 這種方式復制扫外,并沒有切斷對象的引用關系莉钙,因此會導致配置對象會被相互引用,出問題~

因此筛谚,我們需要對其進行 深拷貝 賦值磁玉,即 this.defaults = deepClone(config) , 其中 deepClone 時深拷貝函數(shù),這里不再贅述~

請求配置

我們發(fā)現(xiàn)官方的 axiosget驾讲、post等請求會有第二個可選參數(shù)蚊伞,也是 config 席赂,即單獨本次請求的配置,如果存在时迫,我們需要進行配置合并颅停,對于簡單的 baseURLmethod 等這種簡單的配置直接覆蓋掠拳,對于headers這種復雜的對象配置癞揉,進行對象合并,有點類似 Object.assign 方法~

所以溺欧,我們更改我們的 get 方法如下:

get(url,config){

    let configs = mergeConfig(this.defaults,config);

    return new Promise((resolve => {
        let xhr = new XMLHttpRequest();
    
        xhr.onload = function() {
            resolve({
                data: JSON.parse(xhr.responseText),
                status: xhr.status,
                statusText: xhr.statusText
            });
        }
    
        xhr.open( 'get', configs.baseURL + url , true );
        //添加header頭
        for(let key in configs.headers){
            xhr.setRequestHeader(key,configs.headers[key])
        }
        xhr.send();
    }))
}

其中 mergeConfig 是合并兩配置對象的方法喊熟,具體實現(xiàn)參考如下:

function mergeConfig (obj1, obj2) {
    let target = deepClone(obj1),
        source = deepClone(obj2);
    
    return Object.keys(source).reduce((t,k)=>{
        if(['url','baseURL','method'].includes(k)){
            t[k] = source[k]
        }
        if(['headers'].includes(k)){
            t[k] = Object.assign({},source[k],t[k])
        }
        return t;
    },target)
}

ok~ 現(xiàn)在我們就可以通過如下方式進行請求了:

axios.get('/user/info',{
    baseURL : 'http://127.0.0.1:3000' ,
    headers : {
        token : 'x-token-123456'
    }
}).then(res=>{
    console.log(res);
})

可以看到控制臺輸出跟之前的是一樣的~

細心的小伙伴可以看到 header 頭已經(jīng)添加了 token 信息~

攔截器

攔截器主要用于在請求之前或者請求之后可自定義對配置或者響應結果做一系列的處理,axios官方給我們提供了 use 方法姐刁,可以添加多個攔截器逊移,使用方式如下:

// Add a request interceptor
axios.interceptors.request.use(function (config) {
        // Do something before request is sent
        return config;
    }, function (error) {
        // Do something with request error
        return Promise.reject(error);
    });
 
// Add a response interceptor
axios.interceptors.response.use(function (response) {
        // Do something with response data
        return response;
    }, function (error) {
        // Do something with response error
        return Promise.reject(error);
    });

那么,接下來我們自己來實現(xiàn)這么一個 use 方法~

首先我們需要在我們的 axios 實例上添加一個 interceptors 對象龙填,該對象有 requestresponse 兩個屬性,他們都擁有 use 方法拐叉,我們發(fā)現(xiàn) use 方法的結構都相同岩遗,入?yún)閮蓚€函數(shù),其實他們是同一個 Interceptor 類的不同實例而已凤瘦。

我們先來構建 Interceptor 這個類宿礁,首先在 axios 文件夾下新建 Interceptor.js 文件,并定義如下:

Interceptor.js

export default class Interceptor {
    
    constructor() {
        this.handlers = [];
    }
    
    use( resolvedHandler, rejectedHandler ) {
        this.handlers.push({
            resolvedHandler,
            rejectedHandler
        });
    }
}

這里蔬芥,我們 new 出來的的實例都會擁有 use 方法梆靖,并且我們通過一個 handlers 數(shù)組來保存,這樣可以保證我們可以多調用 use 方法笔诵,添加多個攔截器~

我們只需在 Axios.js 中的 constructor 構造函數(shù)中初始化即可返吻。

Axios.js

constructor(config){
    //默認配置
    this.defaults = deepClone(config);
    //攔截器
    this.interceptors = {
        request : new Interceptor() ,
        response : new Interceptor()
    }
}

這樣盡管我們已經(jīng)可以在我們的 main.js 中使用 use 方法添加攔截器了,但是還是無法正確使用乎婿,因為請求這一塊還未進行處理测僵,接下來,我們需要對我們之前的 Axios.js 進行改造~

首先谢翎,我們統(tǒng)一封裝一個 request 函數(shù)捍靠,往后所有的請求都會調用這個方法,入?yún)⑿枰粋€ config森逮,返回一個 Promise 對象榨婆,我們在這里對攔截器進行操作,定義如下:

//request請求
request (config) {
    //配置合并
    let configs = mergeConfig(this.defaults, config);
    //將配置轉成 Promise 對象褒侧,鏈式調用和返回 Promise 對象
    let promise = Promise.resolve(configs);
    
    //請求攔截器良风,遍歷 interceptors.request 里的處理函數(shù)
    let requestHandlers = this.interceptors.request.handlers;
    requestHandlers.forEach(handler => {
        promise = promise.then(handler.resolvedHandler, handler.rejectedHandler)
    });
    
    //數(shù)據(jù)請求
    promise = promise.then(this.send)
    
    //相應攔截器谊迄,遍歷 interceptors.response 里的處理函數(shù)
    let responseHandlers = this.interceptors.response.handlers;
    responseHandlers.forEach(handler => {
        promise = promise.then(handler.resolvedHandler, handler.rejectedHandler)
    })
    
    //返回響應信息
    return promise;
}

上面,為了代碼簡潔拖吼,我又將 send 方法提出來鳞上,定義跟之前基本一致:

//發(fā)送請求
send (configs) {
    return new Promise((resolve => {
        let xhr = new XMLHttpRequest();
    
        xhr.onload = function () {
            resolve({
                data: JSON.parse(xhr.responseText),
                status: xhr.status,
                statusText: xhr.statusText
            });
        }
        xhr.open(configs.method, configs.baseURL + configs.url, true);
    
        //添加header頭
        for ( let key in configs.headers ) {
            xhr.setRequestHeader(key, configs.headers[key])
        }
    
        xhr.send();
    }))
}

哦對啦,我們之前的 get 方法也有一點點的不同吊档,主要是加入了請求攔截器~

// 發(fā)送get請求
get (url, config) {
    config.method = 'get';
    config.url = url;
    return this.request(config);
}

趁熱打鐵篙议,我們來試試~

這里我在 main.js 中分別添加了 2 個響應攔截器和請求攔截器:

//請求攔截器
axios.interceptors.request.use(config=>{
    console.log('請求配置信息:',config);
    return config
})

axios.interceptors.request.use(config=>{
    config.headers.token = 'x-token-654321';
    return config
})

//響應攔截器
axios.interceptors.response.use(res=>{
    console.log('請求響應信息',res)
    return res;
})

axios.interceptors.response.use(res=>{
    res.msg = 'request is ok ~';
    return res;
})

請求攔截器分別打印了請求的配置并將請求的 token 值經(jīng)行了修改,響應攔截器分別打印了響應信息并將響應添加了 msg 的屬性~

不出意外怠硼,你在控制臺可以看到如下信息鬼贱,在請求 header 里看到 token 已經(jīng)被更改~

大功告成!

總算是有點樣子啦~

結語

至此香璃,我們自己封裝了一個非常簡單的 axios 的請求庫这难,由于篇幅有限,這里我只是用了最簡單的 get 請求示例葡秒,axios源碼中遠不止這些姻乓,像一些異常處理、取消請求等的一系列的東西都還沒有實現(xiàn)眯牧,這里主要是借鑒其一些思想和實現(xiàn)的思路蹋岩,我這里只是牽個頭,剩下的靠你們自己不斷的去完善学少,動動手總是好的~

文末剪个,附上 git 地址 感興趣的小伙伴可以參考參考~

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市版确,隨后出現(xiàn)的幾起案子扣囊,更是在濱河造成了極大的恐慌,老刑警劉巖绒疗,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侵歇,死亡現(xiàn)場離奇詭異,居然都是意外死亡忌堂,警方通過查閱死者的電腦和手機盒至,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來士修,“玉大人枷遂,你說我怎么就攤上這事∑宄埃” “怎么了酒唉?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沸移。 經(jīng)常有香客問我痪伦,道長侄榴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任网沾,我火速辦了婚禮癞蚕,結果婚禮上,老公的妹妹穿的比我還像新娘辉哥。我一直安慰自己桦山,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布醋旦。 她就那樣靜靜地躺著恒水,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饲齐。 梳的紋絲不亂的頭發(fā)上钉凌,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音捂人,去河邊找鬼御雕。 笑死,一個胖子當著我的面吹牛滥搭,可吹牛的內(nèi)容都是我干的饮笛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼论熙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了摄狱?” 一聲冷哼從身側響起脓诡,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎媒役,沒想到半個月后祝谚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡酣衷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年交惯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穿仪。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡席爽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啊片,到底是詐尸還是另有隱情只锻,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布紫谷,位于F島的核電站齐饮,受9級特大地震影響捐寥,放射性物質發(fā)生泄漏。R本人自食惡果不足惜祖驱,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一握恳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捺僻,春花似錦乡洼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至醒颖,卻和暖如春妻怎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泞歉。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工逼侦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腰耙。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓榛丢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親挺庞。 傳聞我的和親對象是個殘疾皇子晰赞,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Axios是近幾年非常火的HTTP請求庫选侨,官網(wǎng)上介紹Axios 是一個基于 promise 的 HTTP 庫掖鱼,可以...
    milletmi閱讀 3,501評論 0 9
  • Axios是一個基于Promise的HTTP請求庫,可以用在瀏覽器和Node.js中援制。平時在Vue項目中戏挡,經(jīng)常使用...
    多啦斯基周閱讀 876評論 0 0
  • 概述 在前端開發(fā)過程中,我們經(jīng)常會遇到需要發(fā)送異步請求的情況晨仑。而使用一個功能齊全褐墅,接口完善的HTTP請求庫,能夠在...
    grain先森閱讀 1,570評論 0 4
  • axios 基于 Promise 的 HTTP 請求客戶端洪己,可同時在瀏覽器和 node.js 中使用 功能特性 在...
    Yanghc閱讀 3,644評論 0 7
  • 簡介 Axios 是一個基于 promise 的 HTTP 庫妥凳,可以用在瀏覽器和 node.js 中。本文先從瀏覽...
    microkof閱讀 10,361評論 0 4