手写一个debounce,需要拿到一个返回值,请问各位大佬debounce应该怎么改造?

在antd表单的自定义校验中,做到用户输入的时候就调用接口检测数据库里是否有重复的值
所以使用debounce函数来实现

// 防抖
export const debounce = (() => {
  let timer = null
  return (callback, wait = 800) => {
    timer && clearTimeout(timer)
    timer = setTimeout(callback, wait)
  }
})()

image.png

  const handleCheckUsername = async (username) => {
    try {
      const res = await checkUsername({ username })
      if (res.success) {
        return Promise.resolve()
      }
      return Promise.reject(new Error(res.message))
    } catch (error) {}
  }

但是这样就debounce的返回值是undefined,没办法讲错误反馈到antd的表单下面 类似这种
image.png

阅读 3.2k
3 个回答
  const debounce = ( fn , await ) => {
    let timer = null;
    return  (...args) => {
        if(timer) {
            clearTimeout();
        } 
        timer = setTimeout(() => {
                if(typeof fn === 'function') {
                    fn.apply(this , args);
                } else {
                    throw `argument ${fn} is not a function`;
                }
        }, await);
   
    }
  }

执行结果无法返回settimeout本身是异步的

示例的防抖写法有问题,不可复用

你这种写法,由于debounce只维持了一个 timer,所以多次调用debounce访问的将会是同一个timer,一旦模块复用了,这种写法会导致不同地方的同一个模块产生“串扰” bug 的。
鉴于你这里写了 export,明显是打算复用的,也就是说其实你可能已经制造出了此类 Bug,只不过由于用户的精力通常不会分散从而在每一个短暂的事件段内只会触发同一个地方的防抖,所以这个 Bug 产生副作用的情形并不多见(如果换成后端程序就会很常见了),就算出现了,用户重新触发一下就恢复了。

当然常见的写法在 Vue 组件中复用的时候也会产生类似的 Bug ,因此 Vue 中防抖通常是先写好函数,然后在 created 钩子中将其改成防抖的版本。

修改建议

1. 使用常见的防抖方案,而不是 IIFE

// immediate 表示第一次是否立即执行
function debounce(fn, wait = 50, immediate = true) {
    let timer = null
    return function(...args) {
        if (timer) clearTimeout(timer)

        // ------ 新增部分 start ------ 
        // immediate 为 true 表示第一次触发后执行
        // timer 为空表示首次触发
        if (immediate && !timer) {
            fn.apply(this, args)
        }
        // ------ 新增部分 end ------ 

        timer = setTimeout(() => {
            fn.apply(this, args)
        }, wait)
    }
}
摘自 知乎专栏:深入解析防抖函数 debounce,有改动。

2. 对事件回调进行防抖,而不是对回调中的任意过程进行防抖

怎么改呢?就是 validator 里不要有任何的防抖,性能问题交给写用户事件回调函数的人——当然可能还是你。

糊💩建议

如果你时间来不及,不能参照上面建议的话,可以把debounce改成带缓存结果的。原理是如果函数没被执行,那么其返回结果就可视为上一次执行的结果,所以返回上一次执行的结果即可

export const debounce = (() => {
  let timer = null;
  let lastValue = secret;  // 上次执行的结果
  return (callback, duration = 80) => {
    if(timer === null){
      lastValue = callback();
      timer = setTimeout(() => {
        timer = null;
      }, duration)
    }

    return lastValue
  }
})()

直接用 lodash.debounce 不行嘛。只是使用的时候注意一下需要返回一个 promise,老版本是调用callback,另外调用的是 debounce 执行后返回的闭包,而不是执行 debounce,不然你的 debounce 是没有用的。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题