前端:如何處理AJAX請(qǐng)求的重復(fù)使用
在開發(fā)前端時(shí),我們經(jīng)常使用AJAX來初始化數(shù)據(jù)并動(dòng)態(tài)渲染在頁(yè)面上,但是在遇到一連串的相同數(shù)據(jù)都要進(jìn)行請(qǐng)求時(shí),就有可能對(duì)同一個(gè)API 發(fā)出并發(fā)請(qǐng)求,然而,因?yàn)檫@些請(qǐng)求是同時(shí)發(fā)出,因此響應(yīng)也非常可能是相同的,這樣講可能不夠清楚,直接寫一個(gè)簡(jiǎn)易的范例來解釋這個(gè)情況。
實(shí)際范例
首先我們先撰寫一個(gè)API:
- https://localhost:3000/api/v1/users/:uuid
這個(gè)API的回傳值如下:
- {
- "name":"Username{uuid}",
- "uuid":"{uuid}"
- }
隨后開一個(gè)Vue的demo,并且先通過Axios寫一個(gè)請(qǐng)求的函數(shù):
- // fetch-user.js
- const axios = require('axios');
- module.exports = (uuid) => {
- let uri = `http://localhost:3000/users/${uuid}`;
- return new Promise(resolve => {
- axios.get(uri).then(resolve);
- })
- };
然后我們?cè)赩ue例子中新增一個(gè)User Component(User.vue)來負(fù)責(zé)渲染并請(qǐng)求接口:
- <template>
- <div v-if="init">
- <ul>
- <li>{{user.name}}</li>
- <li>{{user.uuid}}</li>
- </ul>
- </div>
- </template>
- <script>
- const fetchUser = require('../lib/fetch-user');
- export default {
- name: 'User',
- data: function() {
- return {
- init: false,
- user: null
- }
- },
- props: {
- uuid: String
- },
- async mounted() {
- const response = await fetchUser(this.uuid);
- this.init = true;
- this.user = response.data;
- }
- }
- </script>
最后將用戶組件放入App.vue中:
- <template>
- <div id="app">
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- <user uuid="user-uuid"></user>
- </div>
- </template>
- <script>
- import User from './components/User';
- export default {
- name: 'App',
- components: {
- User
- }
- }
- </script>
接著我們看一下顯示結(jié)果:

這樣就正確顯示了,然而這里有一個(gè)問題非常值得注意:

我們打開開發(fā)者模式就會(huì)發(fā)現(xiàn),每個(gè)組件向該API發(fā)出了請(qǐng)求,因此就產(chǎn)生了10次的并發(fā)請(qǐng)求,但是在這種情況下,實(shí)際上我們僅需要讓一個(gè)請(qǐng)求出去,另外9個(gè)元件等待這個(gè)請(qǐng)求的響應(yīng)然后重新使用即可。
改進(jìn)的方法
接下來將講解要如何實(shí)現(xiàn)關(guān)于在同一個(gè)組件之間唯一指定API請(qǐng)求一次并分配請(qǐng)求,我們會(huì)用到這個(gè)元件EventTarget,這個(gè)元件有點(diǎn)類似Node.js中的EventEmitter,主要就是用于接收事件。
隨后我們改寫fetchUser()函數(shù):
- const axios = require('axios');
- /**
- * 這個(gè) class 是用于存儲(chǔ) Response Data 的 Event 衍生類
- */
- class FetchCompleteEvent extends Event {
- constructor(type, data) {
- super(type);
- this.data = data;
- }
- }
- // 用于請(qǐng)求成功時(shí)使用的事件監(jiān)聽器
- const eventEmitter = new EventTarget();
- // 用于請(qǐng)求失敗時(shí)使用的事件監(jiān)聽器
- const errorEmitter = new EventTarget();
- /**
- * 用于存儲(chǔ) URI 以及是否當(dāng)前正在請(qǐng)求的狀態(tài),如:
- * http://localhost:8000/users/foo => true 代表已經(jīng)發(fā)出請(qǐng)求,正在等待 Response
- * http://localhost:8000/users/bar => false 代表當(dāng)前沒有請(qǐng)求在路上
- */
- const requestingList = new Map();
- module.exports = (uuid) => {
- let uri = `http://localhost:3000/users/${uuid}`;
- return new Promise((resolve, reject) => {
- // 如果沒有記錄,或者尚未處于請(qǐng)求狀態(tài)
- if (!requestingList.has(uri) || !requestingList.get(uri)) {
- // 進(jìn)入之后立即將請(qǐng)求狀態(tài)設(shè)為 true
- requestingList.set(uri, true);
- // 請(qǐng)求 URI
- axios.get(uri).then(response => {
- // 完成請(qǐng)求之后將請(qǐng)求狀態(tài)設(shè)為 false
- requestingList.set(uri, false);
- // 發(fā)出一個(gè)事件通知來告訴 callback 請(qǐng)求完成了
- eventEmitter.dispatchEvent(new FetchCompleteEvent(uri, response));
- resolve(response);
- }).catch((e) => {
- // 請(qǐng)求失敗也算是請(qǐng)求完成,將請(qǐng)求狀態(tài)設(shè)為 false
- requestingList.set(uri, false);
- // 發(fā)出一個(gè)事件通知來告訴 callback 請(qǐng)求失敗了
- errorEmitter.dispatchEvent(new FetchCompleteEvent(uri, e));
- reject(e);
- })
- }
- // 當(dāng)目前指定的 URL 處于請(qǐng)求狀態(tài),則不做任何事情
- else {
- // 向成功的事件監(jiān)聽器注冊(cè),當(dāng)完成之后 resolve()
- eventEmitter.addEventListener(uri, (event) => {
- resolve(event.data);
- });
- // 失敗之后 reject()
- errorEmitter.addEventListener(uri, (event) => {
- reject(event.data);
- })
- }
- });
- };
接著我們重新運(yùn)行前端應(yīng)用程序并查看結(jié)果:

結(jié)果與一開始一模一樣,而是當(dāng)時(shí)我們打開開發(fā)者模式就會(huì)發(fā)現(xiàn):

請(qǐng)求已經(jīng)被減少到剩下一個(gè)了,這是因?yàn)樗械脑贾貜?fù)使用了一個(gè)同一個(gè)響應(yīng)。通過這種方法將可以大大減少服務(wù)器的負(fù)載以及前端的運(yùn)行時(shí)間。
總結(jié)
并非每一種情況下都可以使用這種方式來請(qǐng)求資源,如:每次請(qǐng)求資源都一定會(huì)發(fā)送不一樣的API就不能使用這種方式進(jìn)行API調(diào)用,但是像是上述范例中的用戶資料,電商網(wǎng)站中的商品資料或文章等,類似能夠確保在極短時(shí)間之內(nèi)資源都是相同的API就可以使用這種方式來進(jìn)行操作。
擴(kuò)展閱讀
https://dev.to/floatflower/ajax-414j
參考資料
1.https://developer.mozilla.org/zh-TW/docs/Web/API/EventTarget



























