-->

React - Controlling AJAX calls made to the server

2019-07-03 13:26发布

问题:

In my React application I have an array of parameters (some IDs, for example), which should be used as a parameters for an ajax call queue. The problem is that array might contain more than 1000 items and if I just recursively make the ajax call using the forEach loop, the browser page eventually stops responding before each of the requests are resolved.

Is there a library, which could allow to send ajax requests, for example, maintaining 5 requests at a time asynchronously.

This is the code which I am using for now.

async function makeBatchCalls(arrayIds, length) 
{
     //convert arrayIds to two dimensional arrays of given length [[1,2,3,4,5], [6,7,8,9,10] ....]
     let test = arrayIds.reduce(
             (rows, key, index) => (index % length == 0 
                                    ? rows.push([key]) 
                                    : rows[rows.length-1].push(key)) && rows, []);

    let Batchresults = [];

    for (calls of test) {
        Batchresults.push(await Promise.all(calls.map((call)=>fetch(`https://jsonplaceholder.typicode.com/posts/${call}`))));
    }
return Promise.all(Batchresults); //wait for all batch calls to finish
}

makeBatchCalls([1,2,3,4,5,6,7,8,9,10,12,12,13,14,15,16,17,18,19,20],5)

Problem with this code is it waits for 5 calls to complete and then send another batch of 5 calls. This is not an effective utilisation of the network. What I want is at any point of time there should be 5 requests.

Is it possible to tweak above code itself to cater the requirements?

回答1:

This is interesting problem to solve. One approach I can think is to make the 6th ajax call as soon as any one in first batch finishes. This way there will be 5 ajax requests in process at any moment. I have tried to implement something similar. Though my solution does not make ajax calls, but I am guessing you can changed the process function to make the ajax call and return the promise back.

JS Bin

/** 
  This function processes the jobs in batches, once a job is finished in batch it   then processes the next job. This can be used to make network calls.  
*/
function processBatch(queue, batchSize, processJob) {
  // remove a batch of items from the queue
  const items = queue.splice(0, batchSize);
  let count = items.length;

  // no more items?
  if (!count) {
    return Promise.resolve();
  }

  return new Promise((resolve, reject) => {
    return items.forEach((item, i) => {
      return processJob(item).then(result => {
        return processBatch(queue, 1, processJob)
          .then(_ => --count || resolve());
      });
    });
  })
}

// create queue
var queue = [];
for (var i = 1; i <= 20; i++) {
  queue.push(i);
}

// a per-item action
function process(item) {
  console.log('starting ' + item + '...');
  return new Promise((resolve, reject) => {
    // (simulating ajax)
    return setTimeout(function() {
      console.log('completed ' + item + '!');
      resolve();
    }, Math.random() * 1000);
  });
}

// start processing queue!
processBatch(queue, 5, process)
  .then(result => console.log("All jobs processed"));

I just tried to implement a generic function using promises. You can try to run same with ajax calls. I would be interested to know how this solution would work for you.

As you can see, I am recursively calling the processBatch function after successful execution of each job and the successive batchSize is hard coded to 1, but that can be changed and parameterized. Also, this function will work for only happy path case, as it does not take rejected promises into consideration.



回答2:

Interesting question, I am going to propose a different solution than the one you proposed, this one will make sure that there is always 5 requests being processed at maximum at each time.

function makeBatchCalls(arrayIds, length) {
    // determines how many queries are being sent at any given time
    let queued = 0;
    // determines the index of the query to send at any given time
    let currentIndex = 0;
    // recursive function that launches queries and makes sure the queue is respected
    let launchNext = function() {
        if (queued === length) {
            return;
        }
        fetch(`https://jsonplaceholder.typicode.com/posts/${arrayIds[currentIndex]}`).then((results) => {
            queued--;
            launchNext();
            // do something with your results here...
        });
        queued++;
        currentIndex++;
    };
    // launch the first length queries as the queue is empty at first
    for (let i = 0; i < length; i++) {
        launchNext();
    }
}

Hope this helps.



回答3:

You can use the Async library for your use case. There is a queue function that does exactly this. It maintains a queue of tasks to be executed and executes them maintaining desired concurrency at all times.

Here is how your function can be changed to use Async queue.

async function makeBatchCalls(arrayIds, length) 
{
    // create a queue object with concurrency 5(equal to batch length in your case)
    var q = async.queue(function(task, callback) {
        //Call your api for this task here
        fetch(`https://jsonplaceholder.typicode.com/posts/${call}`)
        .then(function (response) {
            //Once your task executes successfully, call the Q callback so that next pending task can be picked up. 
            //This ensures all your tasks keep running one after the other with desired concurrency
            callback();
        })
        .catch(function (err) {
            //in case of failure, either you can return, or you can move to next task by calling callback
        });
    }, 5);

    // Is called when all the tasks have completed
    q.drain = function() {
        console.log('all items have been processed');
    };

    // push all items to queue
    for(var i=0; i < arrayIds.length; i++){
        q.push(arrayIds[i]);
    }
}

makeBatchCalls([1,2,3,4,5,6,7,8,9,10,12,12,13,14,15,16,17,18,19,20],5)