動態

詳情 返回 返回

Loading ... done - 動態 詳情

引子

在前面界面開發的過程中,為了增強在與後端交互過程中的用户體驗,通常會顯示 Loading 動畫。Loading 動畫會在與後端交互結束的時候關閉。這是一個很常規的需求,技術實現也不復雜。

showLoading();
axios.request(...)
    .then(...)
    .finally(() => hideLoading());

Node.js 和大部分瀏覽器都在 2018 年實現了對 Promise.prototype.finally() 的支持。Deno 在 2020 年發佈的 1.0 中也已經支持 finally() 了。即使不支持,使用 await 也很容易處理。

showLoading()
try {
    await axios.request(...);
}
finally {
    hideLoading();
}

而在更早的時候,jQuery 在 jqXHR 中就已經通過 always() 提供了支持。

showLoading();
$.ajax(...)
    .done(...)
    .always(() => hideLoading());

攔截器中的 Loading ... done 邏輯

接下來,為了所有接口調用的行為一致,也為了在一個地方處理相同的事情以達到複用的目的,Loading ... done 的邏輯開始被寫在一些攔截器中。這對單個遠程接口調用來説,沒有問題。但如果有這樣一個業務邏輯會怎麼樣:

function async doSomething() {
    const token = await fetchToken();
    const auth = await remoteAuth(token);
    const result = await fetchBusiness(auth);
}

假設上面的每個調用都使用了 Axios,而 Axios 在攔截器中注入了 showLoading()hideLoading() 的邏輯。那麼這段代碼會依次彈出三個 Loading 動畫。一個業務彈多個 Loading 動畫確實是個不太好的體驗。

給 Loading 記數

其實這個問題我們可以在 showLoading()hideLoading() 中去想辦法。我們把這兩個方法放入一個閉包環境,然後用一個變量來記錄調用次數:

const { showLoading, hideLoading } = (() => {
    let count = 0;
    function showLoading() {
        count++;
        if (count > 1) { return; }
        // TODO show loading view
    }
    function hideLoading() {
        count--;
        if (count > 1) { return; }
        // TODO hide loading view
    }
})();

包裝業務邏輯代替攔截器方案

作者觀點

我個人並不贊同在攔截器裏去處理界面上的事情。攔截器中應該處理與請求本身強相關的事情,比如對參數的預處理,對響應的後處理等。

我不太贊同在攔截器中去處理界面上的東西。像這種情況,可以設計一個 wrap 函數來處理 Loading 的呈現並調用通過參數傳入的業務邏輯。這個 wrap 函數可以這樣寫:

async function wrapLoading(fn) {
    showLoading();
    try {
        return await fn();
    }
    finally {
        hideLoading();
    }
}

在使用的時候可以這樣用:

// 單個遠程調用,不帶參數
await wrapLoading(fetchSomething);

// 單個遠程調用,帶參數
await wrapLoading(() => fetchSomething(arg1, arg2, arg3));

// 多個調用的組合邏輯
const result = await wrapLoading(() => {
    const token = await fetchToken();
    const auth = await remoteAuth(token);
    return await fetchBusiness(auth);
});

下沉包裝函數降低業務處理複雜度

為了應用內更自由地統一化處理,建議對底層 Ajax 框架進行一次封裝。業務遠程調用時使用封裝的接口,避免直接使用 Ajax 庫接口。比如對 Axios request 進行一層封裝。

async function request(url, config) {
    config.url = url;
    return await axios.request(config);
}

如果需要顯示 Loading,可以擴展 config,加一個 withLoading 選項:

async function request(url, config) {
    const { withLoading, ...cfg } = config;
    cfg.url = url;
    
    if (!withLoading) { return await axios.request(cfg); }

    try {
        showLoading();
        return await axios.request(cfg);
    }
    finally {
        hideLoading();
    }
}

如果擴展的業務參數比較多,可以考慮封裝成一個對象,比如 config.options,也可以給封裝的 request 多加一個參數:request(url, config, options),這些實現都不難,就不細説了。

有了這層封裝之後,如果以後想更換 Ajax 框架也相對容易,只需要修改封裝的 request 函數即可,做到了業務層與框架/工具的解耦。

Add a new 評論

Some HTML is okay.