0

I've landed on a new messy code base without any testing available and I'm struggling for quite some time with this issue. I can't use any ES6 code, just plain old vanilla JS and jQuery.

The context: on a web page there are several pairs of inputs, the first input from the pair triggers the firstFunction() on blur and the second input the secondFunction() also on the blur event. As you can see from the sample code bellow, each function then calls another function, ajaxFunction() which performs an AJAX request.

The goal: I want to be able to invoke the ajaxFunction() only if there isn't another AJAX request in progress from a previous function invokement. The thing is that when it's run for the first time on a pair of inputs, a new row is created in the database and the request returns the ID of the row. So when it's called the second time, it will be an UPDATE in the database instead of an INSERT because of the ID. If the second call from the pair runs before the first one finished, I end up having two distinct rows in the database, which is not desired.

Example: if the user enters some text in the first input, then the firstFunction() is triggered on blur, then he quickly moves to the second input and enters something so the secondFunction() is triggered on blur, but it may be the case that the AJAX request from the firstFunction() call hasn't been completed yet. So the AJAX call from the secondFunction() should start only if the one from the firstFunction() has been completed. The same logic applies for the case when the user starts typing in the second input and the in the first one.

Note: this aren't the only AJAX requests on the page, there are several others as well.

I've done some research and I feel like $.when() is what I need, but it beats me when it comes to integrating it in this logic. I also thought about $.active but it doesn't really help.

function firstFunction() {
    // Some logic goes here then we call the AJAX function. 
    ajaxFunction(1, 2, 3);
}

function secondFunction() {
    // Some logic goes here then we call the AJAX function. 
    ajaxFunction(4, 5, 6);
}

function ajaxFunction(param1, param2, param3) {
    $.ajax({
        method: "POST",
        url: "https://website.localhost",
        data: { 
            param1: param1
            param2: param2
            param3: param3
        }
    }).done(function() {
        console.log('Done!');
    });
}
Cosmin
  • 864
  • 3
  • 16
  • 34
  • So set a variable if it is active – epascarello Oct 04 '19 at 16:45
  • Not really sure why this is a problem. AJAX is meant to be asynchronous. What is happening when the two calls run at nearly the same time? – BA_Webimax Oct 04 '19 at 16:45
  • Well the thing is that when it's run for the first time on a pair of inputs, a new row is created in the database and the request returns the ID of the row. So when it's called the second time, it will be an UPDATE in the database instead of an INSERT because of the ID. If the second call from the pair runs before the first one finished, I end up having two distinct rows in the database, which is not desired. – Cosmin Oct 04 '19 at 16:48
  • @epascarello, I thought about that too, but then I would need a way to run the very same request again when the variable gets false or inactive. – Cosmin Oct 04 '19 at 16:50
  • So sounds like to me is you want a queue, not just not run the ajax call – epascarello Oct 04 '19 at 16:51
  • Something like this, yes, but how using only vanilla JS and jQuery? :) – Cosmin Oct 04 '19 at 16:53
  • 1
    @Cosmin Perhaps your best bet is to do UI blocking until the call returns. While it will be mildly inconvenient for the user to have to wait to enter the next field, it is better than what you have now. – BA_Webimax Oct 04 '19 at 16:53
  • I think I found something useful about queues here https://stackoverflow.com/questions/3034874/sequencing-ajax-requests. – Cosmin Oct 04 '19 at 17:00

4 Answers4

2

As you have jQuery available on your scope, I would use the queue feature to cover this use case. The basic idea behind it is to leverage this available feature and organize your XHR requests into a FIFO line.

It was very nicely explained by @gnarf on this answer.

Hudson Tavares
  • 151
  • 1
  • 2
1

This is a quick way of making a queue, there are ways to make it cleaner, but it is the basic concept

function firstFunction() {
  addToQueue({
    param1: 1,
    param1: 2,
    param1: 3
  });
}

function secondFunction() {
  addToQueue({
    param1: 1,
    param1: 2,
    param1: 3
  });
}


var activeQueue = []
var activeRequest = false

function addToQueue(data) {
  // Add item to the queue of things needing to run
  activeQueue.push(data);
  // if the request is not active than run it
  if (!activeRequest) {
    executeQueue()
  }
}

function executeQueue() {
  // if nothing to run, exit
  if (!activeQueue.length) return
  // set the request is active
  activeRequest = true
  // get the data for this request
  var data = activeQueue.shift()
  // make the call
  $.ajax({
    method: "POST",
    url: "https://website.localhost",
    data: data
  }).done(function() {
    console.log('Done!');
    // mark call is done
    activeRequest = activeQueue.length > 0
    // run it again
    executeQueue()
  });
}
epascarello
  • 204,599
  • 20
  • 195
  • 236
0

If you don't want to allow to make another ajax, if one is already in progress and haven't commpleted yet (with error or success) then just make a global bool variable, like:

var ajaxInProgress = false.

You can set it to true in ajaxFunction and set it to false on ajax completion. Then you place your ajax call in a if(!ajaxInProgress) {...}.

This way you won't make a second ajax call if one is already being made.

I suspect that you want to put your ajax calls in a queue, so that if you do something like:

firstFunction();
secondFunction();

Your second function will send the ajax, but only after the first finished. To make this happen I would write a simple queue for your code:

var queue = [];
var ajaxInProgress = false;

function ajaxFunction(param1, param2, param3) {
  if (!ajaxInProgress) {
    ajaxInProgress = true;
    $.ajax({
      method: "POST",
      url: "https://website.localhost",
      data: {
        param1: param1,
        param2: param2,
        param3: param3
      }
    }).done(function () {
      console.log('Done!');
      ajaxInProgress = false;
      executeNextAjaxInQueue();
    });
  } else {
    ajaxInProgress.push([param1, param2, param3]);
  }
}

function executeNextAjaxInQueue(ajaxParams) {
  if (queue.length === 0) {
    return;
  }

  var params = queue.shift();
  ajaxFunction(params[0], params[1], params[2]);
}
Angeal
  • 1
  • 2
0

Depends on exactly what you want.

  1. If all ajax requests are to be completely ignored when a ajax request is already in-flight, then:
var ajaxFuntion = (function() {
    var inFlight = null;
    return function(param1, param2, param3) {
        if(!inFlight) {
            inFlight = $.ajax({
                'method': "POST",
                'url': "https://website.localhost",
                'data': { 
                    'param1': param1,
                    'param2': param2,
                    'param3': param3
                }
            }).done(function(val) {
                inFlight = null;
            });
        };
        return inFlight;
    };
})();
  1. If ajax requests are to be queued to any that were previously queued (any of which may be in-flight), then:
var ajaxFuntion = (function() {
    var inFlight = jQuery.when(); // dummy starter promise.
    return function(param1, param2, param3) {
        inFlight = inflight.then(function() {
            return $.ajax({
                'method': "POST",
                'url': "https://website.localhost",
                'data': { 
                    'param1': param1,
                    'param2': param2,
                    'param3': param3
                }
            });
        };
        return inFlight;
    });
})();

As you can see, both versions involve an IIFE which returns a Function.

By using an IIFE, the inFlight variable is "self-contained", avoiding the need for another member in the same scope as ajaxFuntion or higher.

In the second version, note that queuing is really simple, because jQuery.ajax() returns a promise allowing a queue to be formed by chaining .then().then().then() as many times as you like.

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44