Employing setInterval() along with a .map function on an array and ultimately presenting it as a promise

Currently, I am facing an issue with a function that takes a list of IDs and converts them into an array of URLs. The function then uses the map method to make fetch requests to these URLs. While this approach works well, it tends to fire the requests too quickly, resulting in errors from the API provider due to hitting the API too frequently. I have tried setting intervals for the request, but haven't had much success. Any suggestions on how to address this issue?

async function getReports(reportIDs) {
    const urls = reportIDs.map(id => `https://api.data.com/api/v1/report/${id}/?include_datasets=true`);
    const requests = urls.map(url => fetch(url, {
        method: 'GET',
        headers: { 'api-key': key }
    }).then(res => res.json()));
    
    const responses = await Promise.all(requests).catch(err => console.error(err));
    return responses;
}

I have implemented promises so that I can use the await keyword to retrieve the results of the function within another function for dataset transformation. Any new ideas on how to better handle this situation?

Answer №1

“Simplicity is a valuable virtue that requires effort to achieve and knowledge to appreciate. To complicate matters further, complexity tends to sell better.”Edsger W. Dijkstra

The so-called "lightweight" solution consists of almost 20,000 lines of code and relies on CoffeeScript and Lua. What if you could replace all of that with just 50 lines of JavaScript?

Imagine having a task that needs some time to compute a result -

async function task(x) {
  // task takes time
  await pause(random(5000))
  // task computes a result
  return x * 10
}

Promise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(task))
  .then(console.log, console.error)
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]

This executes all twelve (12) tasks simultaneously. If these were requests sent to a server, some connections might be rejected due to overwhelming simultaneous traffic. By introducing a Pool of threads, we can manage the flow of parallelized tasks -

// my pool with four threads
const pool = new Pool(4)

async function queuedTask(x) {
  // wait for a pool thread
  const close = await pool.open()
  // execute the task and close the thread upon completion
  return task(x).then(close)
}

Promise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(queuedTask))
  .then(console.log, console.error)
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]

Functions should be concise and focus on accomplishing one specific goal. This approach simplifies feature development and enhances reusability, enabling the combination of simple features into more sophisticated ones. Earlier, you encountered random and pause functions -

const random = x =>
  Math.random() * x

const pause = ms =>
  new Promise(resolve => setTimeout(resolve, ms))

If you wish to apply throttling to each task, you can specialize pause to ensure a minimum runtime duration -

const throttle = (p, ms) =>
  Promise.all([ p, pause(ms) ]).then(([ value, _ ]) => value)

async function queuedTask(x) {
  const close = await pool.open()
  // ensure task runs for at least 3 seconds before releasing the thread
  return throttle(task(x), 3000).then(close)
}

Promise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(queuedTask))
  .then(console.log, console.error)
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]

We can incorporate console.log messages to monitor the process efficiency. Additionally, introducing a random pause at the beginning of each task demonstrates that tasks can queue in any order without affecting the final outcome -

async function queuedTask(x) {
  await pause(random(5000))
  console.log("in queue", x)
  const close = await pool.open()
  console.log("   sending", x)
  const result = await throttle(task(x), 3000).then(close)
  console.log("     received", result)
  return result
}

Promise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(queuedTask))
  .then(console.log, console.error)
[...]
console.log thread 1 thread 2 thread 3 thread 4
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]

In the above scenario, our pool was configured with size=4, allowing up to four tasks to run concurrently. After witnessing sending four times, a task must have completed, as evidenced by received, before the next task commences. While queueing can occur at any time, the Pool processes queued tasks using an efficient last-in-first-out (LIFO) sequence while maintaining result order.

Moving forward with our implementation, similar to other functions, we can define thread in a straightforward manner -

const effect = f => x =>
  (f(x), x)

const thread = close =>
  [new Promise(r => { close = effect(r) }), close]

function main () {
  const [t, close] = thread()
  console.log("please wait...")
  setTimeout(close, 3000)
  return t.then(_ => "some result")
}

main().then(console.log, console.error)

please wait...
(3 seconds later)
some result

You can employ thread to build more sophisticated features like Pool -

class Pool {
  constructor (size = 4) {
    Object.assign(this, { pool: new Set, stack: [], size })
  }
  open () {
    return this.pool.size < this.size
      ? this.deferNow()
      : this.deferStacked()
  }
  deferNow () {
    const [t, close] = thread()
    const p = t
      .then(_ => this.pool.delete(p))
      .then(_ => this.stack.length && this.stack.pop().close())
    this.pool.add(p)
    return close
  }
  deferStacked () {
    const [t, close] = thread()
    this.stack.push({ close })
    return t.then(_ => this.deferNow())
  }
}

Your program is now complete. In the functional demo below, I condensed the definitions for quick reference. Run the program to observe the results in your browser -

[...]
.as-console-wrapper { min-height: 100%; }

I hope you found the insights about JavaScript engaging! If you enjoyed this, consider expanding Pool capabilities. Perhaps implement a simplistic timeout function to ensure tasks finish within a designated timeframe. Or introduce a retry function that repeats a task if it encounters an error or exceeds the time limit. For another application of Pool to a different problem, check out this Q&A. If you have any queries, feel free to ask for assistance!

Answer №2

To start, modify your existing code to include a new function named fetchAfterDelay(). This function will require the URL and the specific index number of the element in the array (0, 1, 2, 3...):

async function fetchReports(reportIDs) {
    const urls = reportIDs.map(id => `https://api.data.com/api/v1/report/${id}/?include_datasets=true`);
    
    const requests = urls.map((url, i) => fetchAfterDelay(url, i));
    
    const responses = await Promise.all(requests).catch(error => console.error(error));
    return responses;
}

Next, implement a simple function that returns a promise which resolves after a delay based on the index value multiplied by 1000 milliseconds (0, 1 second, 2 seconds, 3 seconds...):

const fetchAfterDelay = async (i) => {
    const ms = i * 1000;
    return new Promise((resolve, reject) => {
        setTimeout(resolve, ms);
    });
};

Now define the fetchAfterDelay function. It should utilize the defined wait() function for waiting purposes without concerning itself with the returned value:

const fetchAfterDelay = async (url, i) => {
    await wait(i);
    const response = fetch(url, {
        method: 'GET',
        headers: { 'api-key': key }
    });
    return response.json();
};

Answer №3

In my opinion, one effective solution is to incorporate a sleep function.

async function pause() {
  return new Promise((resolve) => setTimeout(resolve, 300));
}

await pause()

Answer №4

In case you have a node, an efficient npm package named bottleneck can help in throttling requests. This package is highly effective and popular among users. Alternatively, you could explore this option or create your own solution.

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

Vue.js template is failing to properly render hyperlinks, although basic string output is functioning as expected

Whenever I print attrib.link, everything works perfectly fine, <div v-for="attrib in attributes"> {{ attrib.link }} </div> However, when I try: <div v-for="attrib in attributes"> <a target='_blank' href={{ attrib.link } ...

"I'm trying to incorporate react-datepicker into my ReScript application, but I'm struggling to

I am attempting to incorporate an external library like react-datepicker into my project. Below is the code snippet and how I am using it: module DatePicker = { @react.component @module("react-datepicker") external make: () => React.eleme ...

Error importing React Icons with the specific icon FiMoreHorizontal

Currently following a guide to create a Twitter-like application and I need to add the following imports: import { FiMoreHorizontal } from 'react-icons/fi' 2.3K (gzipped: 1K) import { VscTwitter } from 'react-icons/vsc' 3.1K (gzipped: ...

Align the center of table headers in JavaScript

I'm currently in the process of creating a table with the following code snippet: const table = document.createElement('table'); table.style.textAlign = 'center'; table.setAttribute('border', '2'); const thead ...

Changing Values of Elements in an Array in PHP

I have created a form in HTML as shown below: <form method="post" action="survey.php"> <div> <label class="prompt">Favorite CS courses:</label> <label> & ...

Animating with CSS3 triggered by JavaScript

Can you help me with an issue I'm having? When attempting to rotate the blue square using a function, it only works once. After that, the page needs to be reloaded in order for the rotation function to work again. Additionally, after rotating 120 degr ...

What is the best way to call a JavaScript function with multiple arguments from a Silverlight project?

I encountered an issue when trying to invoke a JavaScript function with multiple arguments from an HTML page. Here is what I am attempting to do: wbNavigator.Navigate(new Uri("http://localhost:56433/route.html", UriKind.Absolute)); object results = wbNavi ...

Oops! An issue occurred at ./node_modules/web3-eth-contract/node_modules/web3-providers-http/lib/index.js:26:0 where a module cannot be located. The module in question is 'http'

I have been troubleshooting an issue with Next.js The error I am encountering => error - ./node_modules/web3-eth-contract/node_modules/web3-providers-http/lib/index.js:26:0 Module not found: Can't resolve 'http' Import trace for req ...

Error: The _BLOCK_TYPE_IS_VALID(pHead->nBlockUse) is not valid

Encountered an error "_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)" at the final line of the code snippet below: pixelCoorindateAndThreePoint* tempSpace = new pixelCoorindateAndThreePoint[possibleMaxSize]; while (sscanf(temp, "%f %f", &cam1x, &cam1y)){ l ...

Unforeseen alterations in value occur in JavaScript when converting to JSON format

Having trouble generating a gantt chart from a JSON string, specifically with parsing the JSON string into a JSON object. I have a variable myString containing a JSON string that looks like this: {"c": [{"v": "496"}, {"v": "Task name 1"}, {"v": "9, "}, { ...

Refresh the page with cleared cache using window.location.reload

Is there a way to reload a page using JavaScript and still clear the cache, ensuring that the refreshed page shows the latest content from the server? It seems that other browsers are not displaying the most up-to-date versions of the content. Anyone have ...

Exploring Updates in Two Objects Using a Loop in JavaScript

Two different Objects are structured as follows: Obj1: { 0: {name:"", id: 0}, 1: {"age": "", id:0}, 2: {name:"", id: 1}, 3: {"age": "", id:1}, } After triggering a property, the structure ch ...

I wonder why serialize() is not returning a response to me

As someone who is just starting to learn jQuery, I've been experimenting with the .serialize method in jQuery. Unfortunately, I haven't had much luck getting it to work properly. Despite connecting to the latest library (version 3.4.1) and checki ...

What is the best way to individually retrieve objects from an array and automatically activate a button?

function LoginLib() { const [active, setActive] = useState('') const tabs = [ { id:"tab1", name: "Live Sports", subHeadline: "Catch your games at home or on the go. S ...

The React Fabric TextField feature switches to a read-only mode once the value property is included

I've been grappling with how to manage value changes in React Fabric TextFields. Each time I set the value property, the component goes into read-only mode. When utilizing the defaultValue property, everything functions correctly, but I require this i ...

Tips for extracting and dividing the value of an input field using jQuery

My system includes an input search field where I can enter the make and/or model of a vehicle to conduct a search based on that term. The challenge arises when attempting to distinguish between the make and model, especially for makes with multiple words ...

Conducting a directory verification process using Node.js

Is there a method to execute a server script that verifies the presence of all necessary directories in the server directory? I have explored using server.on('event') but it appears that this specific event does not exist. ...

Node.js Passport Authentication: Simplifying User Verification

I need assistance with the following code as I am encountering errors during execution. Can someone help me in setting up routes for both login and register forms that are located on the same page? When trying to submit the registration form using this cod ...

Using Typescript to change a JSON array of objects into a string array

I'm currently working with the latest version of Angular 2. My goal is to take a JSON array like this: jsonObject = [ {"name": "Bill Gates"}, {"name": "Max Payne"}, {"name": "Trump"}, {"name": "Obama"} ]; and convert it into a st ...

The dilemma between Nuxt Js global CSS and Local CSS

Currently, I am in the process of developing a Nuxt application utilizing an admin template. While I am well-versed in Vue.js, I am finding the aspect of loading assets within Nuxt.js to be a bit perplexing. I am in the process of converting the admin temp ...