Home > Software design >  Debounce async function and ensure sequentiality
Debounce async function and ensure sequentiality

Time:12-15

Is it somehow possible using lodash to debounce an async function so it not only be run after a specified delay but also after the latest fired async function finished?

Here is an example:

import _ from "lodash"

const debouncedFunc = _.debounce(async (input: string) => {
    return new Promise<void>(resolve => {
        console.log(`start ${input}`)
        setTimeout(() => {
            console.log(`finish ${input}`)
            resolve()
        }, 5000)
    })
}, 1000)


setTimeout(args => {debouncedFunc("0")}, 0)
setTimeout(args => {debouncedFunc("2000")}, 2000)
setTimeout(args => {debouncedFunc("4000")}, 4000)

And it produces the following output:

start 0
start 2000
start 4000
finish 0
finish 2000
finish 4000

And I want it to produce:

start 0
finish 0
start 4000
finish 4000

Does lodash(or some other lib) has means to get this behaviour?

CodePudding user response:

debounce doesn't provide what you want, but you can extend behaviour by wrapping in a helper function which will do what you want

import _ from 'lodash';

function main() {
  console.clear();
  const debouncedFunc = _.debounce(
    /*⚠️*/debounceAsync(async (input: string) => {
      return new Promise<void>((resolve) => {
        console.log(`start ${input}`);
        setTimeout(() => {
          console.log(`finish ${input}`);
          resolve();
        }, 5000);
      });
    }),
    1000
  );

  setTimeout((args) => {
    debouncedFunc('0');
  }, 0);
  setTimeout((args) => {
    debouncedFunc('2000');
  }, 2000);
  setTimeout((args) => {
    debouncedFunc('4000');
  }, 4000);

  // Debounce next calls until result's promise resolve
  /*⚠️*/function debounceAsync(fn: any) {
    let activePromise: any = null;
    let cancel: any = null;
    const debouncedFn = (...args: any) => {
      cancel?.();
      if (activePromise) {
        const abortController = new AbortController();
        cancel = abortController.abort.bind(abortController);
        activePromise.then(() => {
          if (abortController.signal.aborted) return;
          debouncedFn(...args);
        });
        return;
      }

      activePromise = Promise.resolve(fn(...args));
      activePromise.finally(() => {
        activePromise = null;
      });
    };
    return debouncedFn;
  }
}

main();

CodePudding user response:

You do not want the item to run if the process is currently running. So to do that, you need to add a parameter into your call that says it is running and when it is done running you need to flip it back.

const limitCalls = (() => {
  let isRunning = false;

  return (value) => {
    console.log("called", value);

    if (isRunning) return;

    console.log("start", value);

    isRunning = true;

    return window.setTimeout(() => {
      console.log("finish", value);

      isRunning = false;
    }, 5000);
  };
})();

setTimeout(args => {limitCalls("0")}, 0)
setTimeout(args => {limitCalls("2000")}, 2000)
setTimeout(args => {limitCalls("6000")}, 6000)

  • Related