防抖动一个JavaScript函数并返回一个Promise

防抖动是一种技术,用于限制函数被调用的次数。我们之前已经看过如何防抖动一个函数,但如果我们想要返回一个Promise呢?

和之前一样,我们可以使用定时器根据需要创建延迟。我们需要使用clearTimeout()清除当前挂起的定时器,并在每次调用防抖动函数时使用setTimeout()创建一个新的定时器。同样地,我们可以使用Function.prototype.apply()this上下文应用到函数并提供必要的参数。

然而,我们还需要跟踪所有挂起的Promise,并在函数被调用时解决/拒绝它们。为此,我们可以创建一个pending数组,并将每个Promise的resolvereject回调添加到其中。

函数被调用时,当前的pending数组必须被复制,因为它可能在函数调用和解决之间发生变化。然后,我们可以清空pending数组并调用提供的函数。

最后,当函数解决/拒绝时,我们可以使用返回的数据解决/拒绝复制数组中的所有Promise。这意味着在此期间创建的所有Promise都将使用相同的数据解决/拒绝

const debouncePromise = (fn, ms = 0) => {
  let timeoutId;
  const pending = [];
  return (...args) =>
    new Promise((res, rej) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        const currentPending = [...pending];
        pending.length = 0;
        Promise.resolve(fn.apply(this, args)).then(
          data => {
            currentPending.forEach(({ resolve }) => resolve(data));
          },
          error => {
            currentPending.forEach(({ reject }) => reject(error));
          }
        );
      }, ms);
      pending.push({ resolve: res, reject: rej });
    });
};

const fn = arg => new Promise(resolve => {
  setTimeout(resolve, 1000, ['resolved', arg]);
});
const debounced = debouncePromise(fn, 200);
debounced('foo').then(console.log);
debounced('bar').then(console.log);
// 两次都会打印 ['resolved', 'bar']