1

I am making a Javascript page that requests data on user events

A lot of data needs to be retrieved per event, so I get a small amount of the data with a single AJAX request so I can show updates. The callback recursively requests more data via AJAX until there's no data left to get. Within this "chain", everything goes well.

Problems, however, arise when an user fires more events when a different chain is already running. Both chains use the same data object, so data gets lost. What is the proper way to queue these user events so they don't run concurrently? I've added a mutex (https://github.com/kirill-konshin/mutex-promise) but that doesn't take into account the AJAX request.

General code structure:

function userEvent(){
    exclusiveGetData()
}

var dataCache = [];

var getDataMutex = new MutexPromise('ThisIsAnUniqueEnoughKey');

function exclusiveGetData(){
    return getDataMutex.promise()
        .then(function(){
            getDataMutex.lock();
            getData();
        })
        .then(function(res){
            getDataMutex.unlock();
            return res;
        })
        .catch(function(e){
            getDataMutex.unlock();
            throw e;
        });
}


function getData(){
    $.get("url", function(data){
        dataCache.handleData();
        if(thereIsMoreData)
            getData();
    })
}

This of course doesn't work because we don't wait for $.get() to finish before releasing the lock. I'm pretty sure I need promises, but I've been fiddling around with those for about 4 hours and I haven't gotten it to work yet. Any advice?

Sopwafel
  • 43
  • 8
  • Duplicate of https://stackoverflow.com/questions/133310/how-can-i-get-jquery-to-perform-a-synchronous-rather-than-asynchronous-ajax-re?rq=1 – Sopwafel Jun 17 '20 at 15:22
  • If you want to use AJAX instead of promises, maybe you can pause user ability to send new data (like change button to reload icon) until you get the response, and then change back to the button, in that case you won't get any new events from the user in the middle. – Alon Joshua Jun 17 '20 at 15:27
  • NVM the solution in the link locks the entire page for the duration – Sopwafel Jun 17 '20 at 15:36
  • @Bizzaros Responsiveness is important so that isn't an option. Tomorrow, I'm going to try to abort all running requests when a new user event is fired. Not pretty, but should be more simple to implement. – Sopwafel Jun 17 '20 at 15:40
  • 1
    _"What is the proper way to queue these user events"_ — the proper way to queue the user events is to _**queue** the user events_ -- add them to a work queue rather then act on them immediately, as Yousaf does in his answer. – Stephen P Jun 17 '20 at 20:40

2 Answers2

2

When an event is triggered, add the request to be made in a list and check if you can make the request by checking if a boolean flag variable, which is initially true, is true. If you can make the request, set the boolean flag variable to false and go ahead with the request otherwise no action is needed.

After the request is complete, check if there are any pending requests in the list, if there are, then make those requests one by one. If no requests are pending then set the boolean flag variable to true indicating that all the pending requests have been completed.

Example:

See the following example which fetches random jokes from chuck-norris jokes api. When the fetch button is clicked for the first time, first request is made after 2 seconds. If you press the button multiple times before first request is made, all those request will be added in the pendingRequests array and after first request is complete, then next requests will be made if there are any pending requests.

P.S. setTimeout is added just to show that when button is clicked multiple times, requests are added to the pendingRequests array. You can remove that in your code if you follow this example.

const btn = document.querySelector('button');
const baseURL = 'https://api.chucknorris.io/jokes/random';
const pendingRequests = [];
let canMakeRequest = true;

btn.addEventListener('click', getData);

function getData() {
  pendingRequests.push(baseURL);
  
  if (canMakeRequest) {
    canMakeRequest = false;
    setTimeout(() => {
       makeRequest(pendingRequests.shift());
    }, 2000);
  }
}

function makeRequest(url) {
  fetch(url)
    .then(res => res.json())
    .then(data => {
       displayData([data]);
    
       const nextURL = pendingRequests.shift();
    
       if (nextURL) {
         makeRequest(nextURL);
       }
       else {
         canMakeRequest = true;
       }
    })
    .catch(error => console.log(error));
}

function displayData(data) {
  const div = document.querySelector('div');
  let html = '';
  data.forEach(d => html += `<span>${d.value}</span>`);
  div.innerHTML += html;
}
div {
  display: flex;
  flex-direction: column;
  align-items: center;
}

span {
  background: #f8f8f8;
  box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
  padding: 15px;
  margin: 5px;
  width: 100%;
  text-align: center;
}
<button>Fetch Data</button>
<div id="container"></div>
Yousaf
  • 27,861
  • 6
  • 44
  • 69
1

you can try using simply count check of number of times, the user trigger the event, pendingAjaxCount will store the user event trigger, based on each trigger completion, it will helps to reduce the count and trigger the next ajax call

var pendingAjaxCount = 0;
var dataCache = [];

var userEvent = () => {
  pendingAjaxCount++;
  if(pendingAjaxCount === 1) {
    getData();
 }
}

var unlockedData = () => {
    if(pendingAjaxCount !== 0) {
      getData();
    }
};
 
var getData = () => {
    $.get("url", function(data){
        dataCache.handleData();
        if(thereIsMoreData) {
            getData();
        }
        else {
            pendingAjaxCount--;
            unlockedData()
        }
    })
}
Raj Kumar
  • 839
  • 8
  • 13