异步防抖模式简介

2025-06-10

异步防抖模式简介

回调地狱这是 JavaScript 开发者最害怕的事情,尤其是在处理像 jQuery 或 Node 标准库这样的遗留 API 时。幸运的是,解决方案已经到位。像 Angular 这样的框架的出现简化了 HTML 渲染。Promise 提供了一种标准且简单的方法来处理异步函数。现在asyncawait使用非线性路径编写异步代码变得更容易了。

然而,既然这一层已经稳定下来并形成了最终形状,那么我们开始思考如何构建可用于 UI 开发的更高级模式是一个好主意。

任何典型的 UI 基本上可以分为两部分:除了导航/过滤/更改信息的输入之外,还有很多信息。所有这些都发生在服务器端,前端只是服务器端的一个视图。这意味着前端和 API 必须频繁通信才能响应用户输入。如果你在这方面经验足够丰富,你就会知道:

  • 它不是即时的,你需要警告用户正在进行的操作
  • 用户往往 愚蠢的不耐烦,在加载过程中点击按钮无数次
  • 有时会发生错误,而你总是忘记在某个时候捕获它们,这通常会导致整个系统崩溃并使 UI 处于不良状态

当然还有很多其他问题,但我之所以重点关注这些问题,是因为它们都与上述每个人最喜欢的语言中的一个特性有关。理解异步代码本身就很难。让用户理解它就更难了。

预期流量

好吧,那我们就不做了。或者,干脆一劳永逸地做一遍,然后坚持一个更简单的思维模式。

假设你正在开发一个类似即时搜索的用户界面。你在输入框中输入内容,结果就会实时显示在下方。抛开那些边缘情况。你会构建什么样的思维模型?

  1. 用户事件触发调用(__call__()
  2. 检查请求是否有效(validate()
  3. 然后确保向用户显示加载程序(prepare()
  4. 此时您可以运行请求(run()
  5. 根据结果​​,您可以处理结果(success())或错误(failure()
  6. 现在所有内容都已加载,您可以禁用加载器(cleanup()

为什么会更复杂呢?记住这个模型,实现每个钩子,然后就可以开始了。多亏了 Promises,任何任务都run()可以像这样抽象出来。尤其是考虑到大多数情况下,它只是一个 API 调用,axios或者通过另一个已经返回 Promises 的 HTTP 库来实现。

当然,如果用户在 期间点击了 会发生什么run()?如果您想在执行第一个请求之前等待,该怎么办?好吧,我考虑了可能出现的边缘情况,并绘制了这张图:

异步防抖流程

你需要全部理解吗?也许需要,也许不需要。所有箭头、连接和挂钩都经过精心设计,尽可能保持正交,以便根据需要进一步完善。如果你想理解,那么你显然需要理解它。如果不需要,只需按照说明操作,记住简化的模型,一切就都好!

代码示例

当然,我并没有止步于图表。代码才是最重要的,对吧?

介绍wasync/debounce

就本例而言,我们将介绍一些受debounce 演示启发的代码。

我们正在做一个模拟搜索。你输入一些内容,它会进入一个模拟函数,该函数会在 1 秒后回显查询,然后显示结果。所有这些都使用 Vue 组件实现。

模板非常简单:



    <div class="debounce">
        <div>
            <input type="text" v-model="search">
        </div>

        <ul>
            <li>Search = {{ search }}</li>
            <li>Result = {{ result }}</li>
            <li>Loading = {{ loading }}</li>
        </ul>
    </div>


Enter fullscreen mode Exit fullscreen mode

我们依赖几个变量:

  • search是搜索查询文本
  • result是该查询的结果
  • loading是指示当前加载状态的标志

现在让我们将 Debounce 插入到组件中:



import {ObjectDebounce} from 'wasync';

export default {
    // ...

    watch: {
        search: new ObjectDebounce().func({
            // insert code here
        })
    },
}


Enter fullscreen mode Exit fullscreen mode

从现在开始,我们将调用去抖动函数new ObjectDebounce().func()输出

如您所见,debounced 函数可以直接用于监视 Vue 值(在本例中为搜索文本)。得益于 Vue 的监视系统,search()每当search值发生变化时,该值都会作为参数传递给函数。



            validate(search) {
                return {search};
            },


Enter fullscreen mode Exit fullscreen mode

用于调用去抖函数的参数(在本例中为搜索值)被逐字传递给validate()钩子。这个钩子做了两件事:

  1. 验证输入。如果输入值不正确,则需要返回一个 false 值。
  2. 生成运行参数。的返回值validate()将作为参数传递给run()。如果返回一个对象,请确保它是一个副本,并且在运行期间不会发生变异。


            prepare() {
                this.loading = true;
            },


Enter fullscreen mode Exit fullscreen mode

prepare()钩子用于准备加载 UI。在本例中,只需将loading标志设置为 即可true



            cleanup() {
                this.loading = false;
            },


Enter fullscreen mode Exit fullscreen mode

另一方面,当函数运行完毕时,我们希望禁用加载器,只需设置为loading即可false



            run({search}) {
                return doTheSearch({search});
            },


Enter fullscreen mode Exit fullscreen mode

这才是主菜。这是我们实际执行工作的地方。这里用doTheSearch()函数来表示,但你可以执行任何你想做的异步工作。

  • 如果run()返回 aPromise那么它将被等待。
  • 的第一个也是唯一的参数run()是的返回值validate()
  • 如果在运行时调用去抖动函数,则只有最新的调用会产生另一个结果run(),其他调用将被丢弃。
  • 所有异常和承诺拒绝都将被捕获并触发failure()钩子


            success(result) {
                this.result = result;
            },


Enter fullscreen mode Exit fullscreen mode

成功接收返回/解析值作为run()第一个也是唯一一个参数。然后,就看你怎么处理它了!



            failure(error) {
                alert(error.message);
            },


Enter fullscreen mode Exit fullscreen mode

事情并不总是按计划进行。如果run()引发异常或被拒绝,则异常将作为 的第一个且唯一的参数传递failure()

回顾

最后,我们的组件看起来是这样的:



<template>
    <div class="debounce">
        <div>
            <input type="text" v-model="search">
        </div>

        <ul>
            <li>Search = {{ search }}</li>
            <li>Result = {{ result }}</li>
            <li>Loading = {{ loading }}</li>
        </ul>
    </div>
</template>

<script>
import {ObjectDebounce} from 'wasync';

function doTheSearch({search}) {
    return new Promise((resolve) => {
        setTimeout(() => resolve(`You searched "${search}"`), 1000);
    });
}

export default {
    data() {
        return {
            search: '',
            result: '',
            loading: false,
        };
    },

    watch: {
        search: new ObjectDebounce().func({
            validate(search) {
                return {search};
            },
            prepare() {
                this.loading = true;
            },
            cleanup() {
                this.loading = false;
            },
            run({search}) {
                return doTheSearch({search});
            },
            success(result) {
                this.result = result;
            },
            failure(error) {
                alert(error.message);
            },
        })
    },
}
</script>


Enter fullscreen mode Exit fullscreen mode

虽然这看起来微不足道,但它实际上是经过实践检验的代码,无论用户采取什么行动,它都能为用户提供流畅的体验!

请注意,您可以借助vue-cli测试独立的 Vue 组件。

结论

一些与异步资源和用户交互相关的非常常见的问题可以通过一种相当复杂的模式来解决,但幸运的是,该模式被分解到wasync包中的通用库中。

这在一个简单的 Vue 组件中得到了实际展示,其代码非常简单,实际上与您在生产中使用的代码非常接近。

它源于多个项目的经验,最终被分解成一个库。我渴望得到大家对此的反馈,以及使用过的其他解决方案,以及您是否认为它可以应用于您的需求!

鏂囩珷鏉ユ簮锛�https://dev.to/xowap/introducing-the-async-debounce-pattern-36ff
PREV
逐步解释字符串匹配正则表达式
NEXT
愚蠢的算法让我们愚蠢地跟随