HiveBrain v1.2.0
Get Started
← Back to all entries
snippetjavascriptTip

Debounce a JavaScript function and return a promise

Submitted by: @import:30-seconds-of-code··
0
Viewed 0 times
javascriptanddebouncefunctionreturnpromise

Problem

Debouncing is a technique used to limit the number of times a function is called. We've previously seen how to debounce a function, but what if we want to return a promise instead?
Same as before, we can use timeouts to create a delay as needed. We need to clear the current pending timeout, using clearTimeout(), and create a new timeout with setTimeout() each time the debounced function is invoked. Similarly, we can use Function.prototype.apply() to apply the this context to the function and provide the necessary arguments.
However, we also need to keep track of all pending promises and resolve/reject them when the function is invoked. To do that, we can create a pending array and add the resolve and reject callbacks of each promise to it.
When the function is invoked, the current pending array will have to be copied, as it can change between the function call and its resolution. Then, we can clear the pending array and call the provided function.
Finally, when the function resolves/rejects, we can resolve/reject all promises in the copied array with the returned data. This means that all promises created in the meantime will resolve/reject with the same data.

Solution

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);
// Will log ['resolved', 'bar'] both times


However, we also need to keep track of all pending promises and resolve/reject them when the function is invoked. To do that, we can create a pending array and add the resolve and reject callbacks of each promise to it.
When the function is invoked, the current pending array will have to be copied, as it can change between the function call and its resolution. Then, we can clear the pending array and call the provided function.
Finally, when the function resolves/rejects, we can resolve/reject all promises in the copied array with the returned data. This means that all promises created in the meantime will resolve/reject with the same data.

Code Snippets

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);
// Will log ['resolved', 'bar'] both times

Context

From 30-seconds-of-code: debounce-promise

Revisions (0)

No revisions yet.