柚子快報(bào)激活碼778899分享:前端 axios原理
文章目錄
axios基本概念axios多種方式調(diào)用工具函數(shù)axios的攔截器如何實(shí)現(xiàn)?用的設(shè)計(jì)模式是哪種?axios如何實(shí)現(xiàn)取消請求,和cancelToken如何使用手寫一個(gè)簡單axios
axios基本概念
axios是目前比較流行的一個(gè)js庫,是一個(gè)基于promise的網(wǎng)絡(luò)數(shù)據(jù)請求庫,主要用于發(fā)送網(wǎng)絡(luò)數(shù)據(jù)請求,從后臺(tái)服務(wù)器上獲取數(shù)據(jù)返回給前端。
優(yōu)點(diǎn)特性:
從瀏覽器中創(chuàng)建 XMLHttpRequests從 node.js 創(chuàng)建 http 請求支持 Promise API攔截請求和響應(yīng)轉(zhuǎn)換請求數(shù)據(jù)和響應(yīng)數(shù)據(jù)取消請求自動(dòng)轉(zhuǎn)換 JSON 數(shù)據(jù)客戶端支持防御 XSRF
axios多種方式調(diào)用
axios的常用幾種方式就是:
axios.get(url,config); url表示請求地址,config表示配置對(duì)象axios.post(url,config)axios(config)
從上面可以看出axios既可以當(dāng)函數(shù)axios()使用又可以當(dāng)對(duì)象axios.get()使用,原理: 實(shí)質(zhì)上axios是一個(gè)函數(shù),但函數(shù)也屬于是一個(gè)對(duì)象,所以同樣可以向它身上追加屬性和方法,我們所使用的axios是通過createInstance這個(gè)函數(shù)創(chuàng)造出來的,它簡單實(shí)現(xiàn)的源碼如下。 函數(shù)中實(shí)例化了Axios,Axios真正調(diào)用的是Axios原型鏈上的request方法;因此導(dǎo)出的axios需要關(guān)聯(lián)到request方法,這里巧妙的通過bind函數(shù)進(jìn)行關(guān)聯(lián),生成關(guān)聯(lián)后的instance函數(shù),同時(shí)指定它的調(diào)用上下文就是Axios的實(shí)例對(duì)象,因此instance調(diào)用時(shí)也能獲取到實(shí)例對(duì)象上的defaults和interceptors屬性;但是僅僅關(guān)聯(lián)request還不夠,再通過extend函數(shù)將Axios原型對(duì)象上的所有g(shù)et、post等函數(shù)擴(kuò)展到instance函數(shù)上,因此這也是我們才能夠使用多種方式調(diào)用的原因所在。
function createInstance(defaultConfig) {
// 1.實(shí)例化Axios
var context = new Axios(defaultConfig);
// 2.注意這里bind是一個(gè)自定義函數(shù),返回一個(gè)函數(shù)()=>{Axios.prototype.request.apply(context,args)}
// 這里request基本是Axios的核心方法,相當(dāng)于將這些方法全部綁到了實(shí)例化的對(duì)象上
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
// 3.將Axios原型鏈上的其他方法也都綁定到instance上去,這些方法的this會(huì)指向contxt
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
// 4.將contxt上的屬性復(fù)制到instance上去
utils.extend(instance, context);
return instance;
}
axios的構(gòu)造函數(shù)Axios,Axios函數(shù)在原型對(duì)象上還掛載了request、get、post等函數(shù),但是get、post等函數(shù)最終都是通過request函數(shù)來發(fā)起請求的。而且request函數(shù)最終返回了一個(gè)Promise對(duì)象, 因此我們才能通過then函數(shù)接收到請求結(jié)果。
class Axios {
constructor(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
request() {}
}
一個(gè)是將默認(rèn)配置保存到defaults,另一個(gè)則是構(gòu)造了interceptors攔截器對(duì)象 核心request函數(shù)主要作用:
兼容多種傳參方式(1. request(‘example/url’, { method: ‘post’ }); request({ url: ‘example/url’, method: ‘post’ }))合并參數(shù)通過promise的鏈?zhǔn)秸{(diào)用,處理請求、響應(yīng)攔截器以及發(fā)送請求等操作。
工具函數(shù)
bind:將Axios原型上的方法掛載到instance上extend:將構(gòu)造函數(shù) Axios.prototype 上的方法掛載到新的實(shí)例 instance 上,然后將原型各個(gè)方法中的 this 指向 context
axios的攔截器如何實(shí)現(xiàn)?用的設(shè)計(jì)模式是哪種?
攔截器實(shí)現(xiàn)就只有一個(gè)屬性(用于保存攔截器)及三個(gè)原型方法(添加、移除、執(zhí)行)。
實(shí)例化axios后,就可以調(diào)用use進(jìn)行綁定攔截器,需要注意的是,在傳遞use方法的第一個(gè)參數(shù)時(shí)必須返回config,保證下一個(gè)promise能獲取到處理后的參數(shù)。 options是可選參數(shù)對(duì)象,可傳入兩個(gè)屬性(synchronous, runWhen),這么設(shè)計(jì)就是使用了責(zé)任鏈設(shè)計(jì)模式。
axios采用promise.resolve的方式將攔截器異步化。將所有請求攔截器放在請求方法之前unshift,所有的響應(yīng)攔截器放在后push。遍歷所有的方法通過promise的then方法將所有方法放在一條鏈上。
責(zé)任鏈模式是一種行為設(shè)計(jì)模式, 允許你將請求沿著處理者鏈進(jìn)行發(fā)送。 收到請求后, 每個(gè)處理者均可對(duì)請求進(jìn)行處理, 或?qū)⑵鋫鬟f給鏈上的下個(gè)處理者。 優(yōu)點(diǎn):
你可以控制請求處理的順序。單一職責(zé)原則。 你可對(duì)發(fā)起操作和執(zhí)行操作的類進(jìn)行解耦。開閉原則。 你可以在不更改現(xiàn)有代碼的情況下在程序中新增處理者。
責(zé)任鏈模式:執(zhí)行的順序是請求攔截器 -> 發(fā)起請求 -> 響應(yīng)攔截器,這其實(shí)就是一個(gè)鏈條上串起了三個(gè)職責(zé)。
axios如何實(shí)現(xiàn)取消請求,和cancelToken如何使用
創(chuàng)建一個(gè) CancelToken 的實(shí)例,它有一個(gè) executor 函數(shù),可以通過調(diào)用 executor 參數(shù)中的 cancel 函數(shù)來取消請求。(CancelToken內(nèi)部通過promise實(shí)現(xiàn),將promise的resolve方法暴露出去,手動(dòng)控制promise狀態(tài))在 axios 請求配置中指定 cancelToken 屬性,將 CancelToken 實(shí)例傳遞進(jìn)去。(axios 內(nèi)部注冊回調(diào)取消函數(shù),真正的取消方法,XMLHttpRequest 的abort方法取消請求)當(dāng)我們需要取消請求時(shí),調(diào)用 CancelToken 實(shí)例的 cancel() 方法即可取消對(duì)應(yīng)的請求。(內(nèi)部executor傳出來的函數(shù),函數(shù)中包含了resolvePromise(),手動(dòng)改變promise狀態(tài),執(zhí)行then(),執(zhí)行所有被注冊的函數(shù),也就是執(zhí)行真正的abort())
使用方法:
import axios from 'axios';
const CancelToken = axios.CancelToken;
let cancel;
// 【方法1】
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函數(shù)接收一個(gè) cancel 函數(shù)作為參數(shù)
cancel = c;
})
});
cancel('Operation canceled by the user.');
// 【方法2】
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
source.cancel('Operation canceled by the user.');
源碼: axios/lib/adapters/xhr.js
// 當(dāng)我們使用時(shí),傳入cancelToken配置
if (config.cancelToken || config.signal) {
onCanceled = cancel => {
if (!request) {
return;
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
request = null;
};
// 注冊回調(diào)取消函數(shù)
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
lib/cancel/CancelToken.js
class CancelToken {
constructor(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
// 將promise中的resolve()暴露出去
let resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
// 將 CancelToken 實(shí)例賦值給 token
const token = this;
// peomise狀態(tài)改變后,執(zhí)行oncanceled()
this.promise.then(cancel => {
if (!token._listeners) return;
let i = token._listeners.length;
while (i-- > 0) {
token._listeners[i](cancel);
}
token._listeners = null;
});
// ??沒看懂
this.promise.then = onfulfilled => {
let _resolve;
const promise = new Promise(resolve => {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
// 執(zhí)行executor,這里定義的cancel函數(shù)即時(shí)業(yè)務(wù)中調(diào)用的cancel函數(shù)
executor(function cancel(message, config, request) {
if (token.reason) {
return;
}
token.reason = new CanceledError(message, config, request);
resolvePromise(token.reason); // 把接收到的“取消請求信息”token.reason傳遞給下一個(gè)then中成功函數(shù)作為參數(shù)
});
}
subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
}
unsubscribe(listener) {
if (!this._listeners) {
return;
}
const index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
}
// cancelToken.source工廠函數(shù),組合成source返回
static source() {
let cancel;
const token = new CancelToken(function executor(c) {
cancel = c; // 實(shí)現(xiàn)“在他處取消請求”,與上面“終止一個(gè)promise”異曲同工
});
return {
token,
cancel
};
}
}
手寫一個(gè)簡單axios
export function axios({ method, url, params, data }) {
method = method.toUpperCase();
return new Promise((resolve, reject) => {
// 1.創(chuàng)建對(duì)象
const xhr = new XMLHttpRequest();
// 2.初始化
let str = "";
for (const k in params) {
str += `${k}=${params[k]}&`;
}
str = str.slice(0, -1);
xhr.open(method, url + "?" + str);
// 3.發(fā)送請求
if (method === "POST" || method === "PUT" || method === "DELETE") {
// Content-type mime類型設(shè)置
// 請求頭
xhr.setRequestHeader("Content-type", "application/json");
// 請求體
xhr.send(JSON.stringify(data));
} else {
// 如果是get方法
xhr.send();
}
// 設(shè)置響應(yīng)結(jié)果的類型為json
xhr.responseType = "json";
// 4.處理結(jié)果
xhr.onreadystatechange = function () {
//
if (xhr.readyState === 4) {
// 判斷響應(yīng)狀態(tài)碼
if (xhr.status >= 200 && xhr.status <= 300) {
// 成功的狀態(tài)
resolve({
// 成功的狀態(tài)碼
status: xhr.status,
// 成功的字符串,響應(yīng)字符串
message: xhr.statusText,
// 響應(yīng)體
body: xhr.response,
});
} else {
reject(new Error("請求失敗,失敗的狀態(tài)碼為" + xhr.status));
}
}
};
});
}
axios.get = function (url, options) {
// 發(fā)送AJAX請求 GET
return axios(Object.assign(options, { method: "GET", url }));
};
axios.post = function (url, options) {
// 發(fā)送AJAX請求 POST
return axios(Object.assign(options, { method: "POST", url }));
};
axios.put = function (url, options) {
// 發(fā)送AJAX請求 PUT
return axios(Object.assign(options, { method: "PUT", url }));
};
axios.delete = function (url, options) {
// 發(fā)送AJAX請求 DELETE
return axios(Object.assign(options, { method: "DELETE", url }));
};
柚子快報(bào)激活碼778899分享:前端 axios原理
精彩內(nèi)容
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。