引子

在后面界面开发的过程中,为了加强在与后端交互过程中的用户体验,通常会显示 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 函数即可,做到了业务层与框架/工具的解耦。