前端:如何處理AJAX請求的重復(fù)使用
在開發(fā)前端時,我們經(jīng)常使用AJAX來初始化數(shù)據(jù)并動態(tài)渲染在頁面上,但是在遇到一連串的相同數(shù)據(jù)都要進(jìn)行請求時,就有可能對同一個API 發(fā)出并發(fā)請求,然而,因為這些請求是同時發(fā)出,因此響應(yīng)也非??赡苁窍嗤?,這樣講可能不夠清楚,直接寫一個簡易的范例來解釋這個情況。
實際范例
首先我們先撰寫一個API:
- https://localhost:3000/api/v1/users/:uuid
這個API的回傳值如下:
- {
- "name":"Username{uuid}",
- "uuid":"{uuid}"
- }
隨后開一個Vue的demo,并且先通過Axios寫一個請求的函數(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);
- })
- };
然后我們在Vue例子中新增一個User Component(User.vue)來負(fù)責(zé)渲染并請求接口:
- <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é)果:

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

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

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