Melissa_Y
Melissa_Y

Reputation: 11

async await with multiple setIntervals

I have two API requests, one that gets called every 5000ms and one that gets called every 30000ms. I want to ensure that each call is completed before issuing a new request to the server. I don't want either of the requests to overlap one another. For example, if func1 API hasn't completed then I don't want to issue func2 API call until that one completes.

This is what I've tried so far.

async function vals() {
            try {
                await func1()
                    .then(response => response.json())
                    .then(result => display_result(result))
                    .catch(error => console.error('Error: ', error));
                await func2()
                    .then(response => response.json())
                    .then(result => display_result(result))
                    .catch(error => console.error('Error: ', error));
            }
            catch(error) {
                return error;
            }
}

vals();

Here are func1 and func2.

function func1() {
        return setInterval(() => fetch(url, { method: 'GET' }), 5000);
}

function func2() {
        return setInterval(() => fetch(url, { method: 'GET' }), 30000);
}

I would expect this to run func1() first, wait for it to resolve, and then run func2(). Instead func1() gets called twice and never gets to func2(). Should the setIntervals be set inside the vals() function instead? Any guidance to make this work would be much appreciated.

Upvotes: 0

Views: 563

Answers (1)

Gershom Maes
Gershom Maes

Reputation: 8131

Ok this is a bit tricky! You have two different intervals spawning tasks (http requests) which take a non-trivial amount of time, and you want to make sure that the tasks don't coincide with each other.

I recommend that instead of immediately activating your requests in the timeout, instead add the requests to a queue of work-to-be-done. That queue will process a number of tasks in serial as quickly as it can.

// In your example your "long-running-tasks" are http requests.
// In this example I'll use a timeout.
let genLongRunningTask1 = async () => {
  console.log('Task 1 start');
  await new Promise(r => setTimeout(r, 1500));
  console.log('Task 1 end');
};

let genLongRunningTask2 = async () => {
  console.log('Task 2 start');
  await new Promise(r => setTimeout(r, 1600));
  console.log('Task 2 end');
};

// The tail of the promise-queue. If it resolves we're ready
// to begin a new long-running-task. It's initially resolved.
let queueTail = Promise.resolve();
let queueNewTask = async genLongRunningTask => {
  await queueTail;
  await genLongRunningTask();
};

// Now setup our intervals. We don't directly generate any
// long-running-tasks here - instead we "queue" them, and
// then point the tail of the queue to their completion.
console.log('Starting...');
setInterval(() => {
  queueTail = queueNewTask(genLongRunningTask1);
}, 3000);
setInterval(() => {
  queueTail = queueNewTask(genLongRunningTask2);
}, 6000);

In my example the two intervals are at 3000ms and 6000ms, so they should run simultaneously every 6000ms - but you'll see that the queueing logic keeps them nice and separate! You'll never see a new task start before the previous task has ended.

In your case you should only need to edit genLongRunningTask1 and genLongRunningTask2 so that they await and process your request. Something like the following:

let genLongRunningTask1 = async () => {
  try {
    // Assuming `func1` returns a "response object":
    let response = await func1();
    
    /*
    Assuming the "response object" has a `json` method,
    and `display_result` is an async method for showing
    the json data.
    NOTE: this use of `await` ensures requests will remain queued
    until the previous request is done processing *and* rendering.
    To begin sending the next request after the previous request
    has returned, but overlapping with the period in which that
    request is still *rendering*, omit `async` here.
    */
    await display_result(response.json());
  } catch(err) {
    console.error('Error:', err);
  }
};

Warning: Be careful that you aren't queuing tasks more quickly than the tasks can complete!

Upvotes: 1

Related Questions