Delay calls to JavaScript functions, ensuring all are processed in order without any being discarded

Is there a way for a function to limit the frequency of its calls without discarding them? Instead of dropping calls that are too frequent, is it possible to queue them up and space them out over time, say X milliseconds apart? I've explored concepts like throttle and debounce, but they seem to discard calls rather than queuing them for future execution.

Is there a more efficient solution than implementing a queue with a process() method that runs on an interval of X milliseconds? Are there any standard implementations available in JavaScript frameworks that can achieve this functionality? I've checked out underscore.js without success so far.

Answer №1

Executing this code without using a framework is actually quite straightforward:

var stack = [], 
    timer = null;

function completeTask() {
    var item = stack.shift();
    // carry out task
    if (stack.length === 0) {
        clearInterval(timer);
        timer = null;
    }
}

function addToQueue(item) {
    stack.push(item);
    if (timer === null) {
        timer = setInterval(completeTask, 500);
    }
}

http://jsfiddle.net/6TPed/4/

Answer №2

An instance provided as an illustration that moves this forward or allows for a custom one to be set

function RateLimit(fn, delay, context) {
    var canInvoke = true,
        queue = [],
        timeout,
        limited = function () {
            queue.push({
                context: context || this,
                arguments: Array.prototype.slice.call(arguments)
            });
            if (canInvoke) {
                canInvoke = false;
                timeEnd();
            }
        };
    function run(context, args) {
        fn.apply(context, args);
    }
    function timeEnd() {
        var e;
        if (queue.length) {
            e = queue.splice(0, 1)[0];
            run(e.context, e.arguments);
            timeout = window.setTimeout(timeEnd, delay);
        } else
            canInvoke = true;
    }
    limited.reset = function () {
        window.clearTimeout(timeout);
        queue = [];
        canInvoke = true;
    };
    return limited;
}

At this moment

function foo(x) {
    console.log('hello world', x);
}
var bar = RateLimit(foo, 1e3);
bar(1); // logs: hello world 1
bar(2);
bar(3);
// undefined, bar is void
// ..
// logged: hello world 2
// ..
// logged: hello world 3

Answer №3

If you're looking for well-supported modules to handle rate limiting, here are some top choices:

  • Check out limiter, the most popular rate limiter.
  • function-rate-limit offers a simple API with good usage stats on npmjs.
  • valvelet is a newer module that claims to support promises for an even better job, although it hasn't gained much popularity yet.

Answer №4

After searching for a TypeScript adaptation, I stumbled upon @Dan Dascelescu's JavaScript fiddle and decided to enhance it with typings.

If you have any suggestions to improve the type definitions, please feel free to leave a comment 🙏

function rateLimit<T>(
  fn: (...args: Array<any>) => void,
  delay: number,
  context?: T
) {
  const queue: Array<{ context: T; arguments: Array<any> }> = []
  let timer: NodeJS.Timeout | null = null

  function processQueue() {
    const item = queue.shift()

    if (item) {
      fn.apply<T, Array<any>, void>(item.context, item.arguments)
    }

    if (queue.length === 0 && timer) {
      clearInterval(timer)
      timer = null
    }
  }

  return function limited(this: T, ...args: Array<any>) {
    queue.push({
      context: context || this,
      arguments: [...args],
    })

    if (!timer) {
      processQueue() // initiate immediately on first call
      timer = setInterval(processQueue, delay)
    }
  }
}

Answer №5

In my opinion, using decorators can greatly enhance the simplicity and reusability of this code:

/**
 * Asynchronously pauses execution for a specified amount of time.
 * @param milliseconds
 * @returns
 */
function asyncPause(milliseconds: number): Promise<void> {
  return new Promise<void>((resolve) => {
    setTimeout(() => { resolve(); }, milliseconds);
  });
}

/**
 * Generates a random integer within a given range.
 * The maximum is exclusive while the minimum is inclusive.
 * @param min
 * @param max
 */
export const getRandomInt = (
  min: number,
  max: number,
): number => (
  Math.floor(
    Math.random() * (
      Math.floor(max) - Math.ceil(min)
    ) + Math.ceil(min),
  )
);

/**
 * Throttles the execution of a method by a fixed millisecond duration if a number is provided.
 * If a tuple is passed, it will throttle by a randomly generated time (in ms) between each call.
 * @param milliseconds
 * @returns
 */
function throttle(milliseconds: number | [number, number]): any {
  let lastCall = 0;
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = async function (...args: any[]) {
      const currentTime = Date.now();
      if (!lastCall) {
        lastCall = currentTime;
        return originalMethod.apply(this, args);
      }
      const duration = Array.isArray(milliseconds)
        ? getRandomInt(milliseconds[0], milliseconds[1])
        : Math.round(milliseconds);

      const timeDifference = currentTime - lastCall;
      if (timeDifference < duration) {
        await asyncPause(duration - timeDifference);
      }
      lastCall = Date.now();
      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

Now you can simply implement it like so:

class MyClass {
  @throttle(1000)
  async limitedMethod() {
    await performTask()
  }

  @throttle([1000, 5000])
  async randomizedLimitedMethod() {
    await performTask()
  }
}

TSPlayground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Switch Focus and Collapse Submenus upon Menu Click in Recursive React Menu

I've created a dynamic menu system in React using Material-UI that supports recursion for submenus. I'm aiming to implement the following features: 1. When a menu item is clicked, all other open submenus should close and focus on the clicked men ...

NextJs manages the logic for processing requests both before and after they are handled

NextJs stands out from other frameworks due to its lack of support for filter chains, request pre-processing, and post-processing. Many node project developers or library creators may find these features essential for executing logic before and after API c ...

List the attributes that have different values

One of the functions I currently have incorporates lodash to compare two objects and determine if they are identical. private checkForChanges(): boolean { if (_.isEqual(this.definitionDetails, this.originalDetails) === true) { return false; ...

Having trouble accessing the input class with jQuery

When I click the "Reset To Default Settings" button in my form, I want to clear the default colors in my 4 color pickers. Each of them has an input type text field with the class anyicon-form-colorpicker. To achieve this, I locate the <a> tag and use ...

Conditionally displaying an input model in Vue.js using v-if

Just starting to learn vue.js and I'm looking to display table data. My idea is that when the table is in display mode, it should only show the data. However, when I click on the edit button of a table row, I want that specific row to switch to edit m ...

Retrieve the user information from Auth0 within the NestJS application

I am currently working on implementing Auth0 authorization in NestJS, but I am unsure of how to retrieve the user's data within the callback URL handler. In a normal express function, this issue could be resolved using the following code. The passpor ...

Encountered an Unhandled Runtime Error in NextJs: Invalid Element Type

Attempting to build an Instagram clone following a YouTube tutorial from a year ago has led to various errors arising from nextjs14. I have managed to resolve all of them except for one particular issue: Error: Element type is invalid - expected a string ...

Is it permissible to use multiple JWT tokens in the HTTP header?

Currently, I have implemented the jwt access and refresh token pattern for client-server communication. The method involves sending two jwt tokens in the header: the access token and the refresh token. This is done by adding the following code to the heade ...

Tips for concealing a tab upon selecting an option from a dropdown menu

<ul class="nav nav-tabs"> <li class="active"><a data-toggle="tab" href="#song">Song</a></li> <li id="image-tab"><a href="#image" data-toggle="tab">Image</a></li> </ul> <div class="tab-cont ...

jqGrid - Error when the length of colNames and colModel do not match!

Whenever I implement the code below, it triggers an error saying "Length of colNames and <> colModel!" However, if isUserGlobal is false, no errors occur. The version of jqGrid being used is 4.5.4 receivedColModel.push({name:'NAME', index:' ...

Guide to removing a Firebase Storage directory using a Firebase Cloud Function?

I'm having trouble finding the deleteFiles() method and the DeleteFilesOptions argument in the Firebase API reference. My IDE is indicating that this method requires an optional argument, but I can't seem to locate any information on this type. I ...

Capturing screenshots with Selenium in Node.js is proving to be quite sluggish

My current project involves using Selenium with Mocha in Node.js for UI testing on a website. I want to capture screenshots after each test to review and share the results visually. The challenge arises when there are AJAX calls and JavaScript animations ...

Leverage predefined JavaScript functions within an Angular template

I have been attempting to execute an eval function within my angular template in the following manner: <div *ngFor="..."> <div *ngIf="eval('...')"></div> </div> You understand what I'm trying to ...

Debouncing in AngularJS with $watch

In my code, I have an HTML search field represented by the following: <input ng-model-options="{ debounce: 500 }" type="text" ng-model="name"> Along with the JavaScript snippet: $scope.$watch('name', function(newVal, oldVal) { ...

AJAX requests sent from different origins to AWS S3 may encounter CORS errors on occasion

My current objective is to access publicly available files stored in S3. The CORS configuration for my S3 setup is as follows: <?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> < ...

Tips for adjusting the value of a textbox up and down

I am facing an issue with my booking flight form that is supposed to take input from users regarding the number of travelers. I have three textboxes for Adult, Children, and Infants respectively, along with a main textbox to display the final result. Howev ...

Changing $scope does not automatically refresh the selected option when using ng-options with <select> in AngularJS

My select element is structured like this: <select ng-model="exportParam" ng-options="item as item.lib for item in allExportParams | orderBy:'lib'" class="form-control"> </select> To save its state for later display, I sto ...

MUI Alert: Encountered an Uncaught TypeError - Unable to access properties of undefined when trying to read 'light' value

After utilizing MUI to create a login form, I am now implementing a feature to notify the user in case of unsuccessful login using a MUI Alert component. import Alert from '@mui/material/Alert'; The code snippet is as follows: <CssVarsProvide ...

Internet Explorer 11 Ajax problem

Once again, Internet Explorer is causing some issues. I have a file called validation.php where the values from a text box are sent for validation. The text box value is read and then a result indicates whether it is valid or not. This functionality work ...

Retrieve the $ionicConfigProvider within a Controller

In my controller file named ProfileController.js, I am trying to change the text of the back button. After doing some research, I found this code snippet: $ionicConfigProvider.backButton.text('Go Back').icon('ion-chevron-left'); How can ...