DEV Community

Cover image for 前端異步 AJAX 請求的重複使用
Jian Lin Huang
Jian Lin Huang

Posted on Edited on

前端異步 AJAX 請求的重複使用

在開發前端時,我們非常常使用 AJAX 來異步資料並動態渲染在頁面上,但是在遇到一連串的相同資料都要進行請求時,就有可能對同一個 API Endpoint 發出併發請求,然而,因為這些請求是同時發出,因此 Response 也非常可能是相同的,這樣講可能不夠清楚,直接寫一個簡易的範例來解釋這個情況。


實際範例

首先我們先撰寫一個 API:

https://localhost:3000/api/v1/users/:uuid
Enter fullscreen mode Exit fullscreen mode

這個 API 的回傳值如下:

{
    "name":"Username{uuid}",
    "uuid":"{uuid}"
}
Enter fullscreen mode Exit fullscreen mode

接著開一個 Vue SPA 專案,並且先透過 Axios 寫一個異步請求的函數:

// 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);
    })
};
Enter fullscreen mode Exit fullscreen mode

然後我們在 Vue 專案中新增一個 User Component(User.vue) 來負責渲染並請求資料:

<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>
Enter fullscreen mode Exit fullscreen mode

最後將 user component 放入 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>
Enter fullscreen mode Exit fullscreen mode

接著我們看一下顯示結果:

顯示結果

這樣就正確顯示了,然而這裡有一個問題非常直接注意:

併發請求

我們打開開發者模式就會發現,每個元件向該 API 發出了請求,因此就產生了 10 次的併發請求,但是在這種情況下,實際上我們僅需要讓一個請求出去,另外 9 個元件等待這個請求的 Response 然後複用即可。

改進的方法

接下來將講解要如何實現對於在同一個時間內僅對指定 API 請求一次並複用請求,我們會用到這個元件,這個元件有點類似 Node.js 中的 EventEmitter,主要就是用於收發事件。

https://developer.mozilla.org/zh-TW/docs/Web/API/EventTarget

接著我們改寫 fetchUser() 函數:

const axios = require('axios');

/**
 * 這個 class 是用於儲存 Response Data 的 Event 衍生類
 */
class FetchCompleteEvent extends Event {
    constructor(type, data) {
        super(type);
        this.data = data;
    }
}

// 用於請求成功時使用的事件監聽器
const eventEmitter = new EventTarget();

// 用於請求失敗時使用的事件監聽器
const errorEmitter = new EventTarget();

/**
 * 用於儲存 URI 以及是否當前正在請求的對應,如:
 * http://localhost:8000/users/foo => true 代表已經發出請求,正在等待 Response
 * http://localhost:8000/users/bar => false 代表當前沒有請求在路上
 */

const requestingList = new Map();

module.exports = (uuid) => {

    let uri = `http://localhost:3000/users/${uuid}`;

    return new Promise((resolve, reject) => {

        // 如果沒有紀錄,或者尚未處於請求狀態
        if (!requestingList.has(uri) || !requestingList.get(uri)) {

            // 進入之後立即將請求狀態設為 true
            requestingList.set(uri, true);

            // 請求 URI
            axios.get(uri).then(response => {

                // 完成請求之後將請求狀態設為 false
                requestingList.set(uri, false);

                // 發出一個事件通知來告訴 callback 請求完成了
                eventEmitter.dispatchEvent(new FetchCompleteEvent(uri, response));
                resolve(response);

            }).catch((e) => {

                // 請求失敗也算是請求完成,將請求狀態設為 false
                requestingList.set(uri, false);

                // 發出一個事件通知來告訴 callback 請求失敗了
                errorEmitter.dispatchEvent(new FetchCompleteEvent(uri, e));
                reject(e);

            })
        }
        // 當目前指定的 URI 處於請求狀態,則不做任何事情
        else {

            // 向成功的事件監聽器註冊,當完成之後 resolve()
            eventEmitter.addEventListener(uri, (event) => {
                resolve(event.data);
            });

            // 失敗之後 reject()
            errorEmitter.addEventListener(uri, (event) => {
                reject(event.data);
            })
        }
    });
};
Enter fullscreen mode Exit fullscreen mode

接著我們重新運行前端應用程式並查看結果:

顯示結果

結果與一開始一模一樣,但這時候我們打開開發者模式就會發現:

併發請求消失

請求已經被減少到剩下一個了,這是因為所有的元件都重複使用了同一個 Response。透過這種方法將可以大大減少伺服器的負載以及前端的運行時間。

結論

並不是每一種情況下都可以使用這種方式來請求資料,如:每次請求資料都一定會不一樣的 API 就不能使用這種方式進行 API 呼叫,但是像是上述範例中的用戶資料、電商網站中的商品資料或是部落格的文章等,這類能夠確保在極短時間之內資料都相同的 API 就可以使用這種方式來進行呼叫。

Top comments (0)