1

What causes ajax to send more than one POST request at the same time? It is hard to reproduce since it happens about 2% of the time. It seems to happen on bad/mobile networks. We are using Chrome on Android.

How Form Works

  • .keyup event listener waits for N characters and then sends the data from the form after some minor validation via an ajax call.
  • The form is immediately cleared so that another request can be sent.
  • onSuccess returns and updates a totals table.

Problem The system saves multiple records when it should only save one.

What I've found/tried

  • Only one call is returned to the UI. The onSuccess is only called once when this happens. The UI has a total that gets out of sync whenever this occurs.
  • Double scan or double click? No, I've created a check to prevent duplicate data of the same value to be sent back to back.
  • The server access logs show duplicate requests at the exact same time.
  • I thought it was due to HTTP/1.1 RFC 8.2.4: which says POST requests can be retried, so I made change to controller to check for a timestamp (p) but since the request is at the exact same time, my code does not catch that the same p is coming through. https://stackoverflow.com/a/14345476/2537574

[22/Aug/2016:07:14:12 -0700] "POST /api/save?p=14718752538085 HTTP/1.1" 200 749
[22/Aug/2016:07:14:12 -0700] "POST /api/save?p=14718752538085 HTTP/1.1" 200 736

Attempt 6 - Did not work

unbind/bind is the old way of doing it. Now trying to use .off and .on and added e.preventDefault(). (https://stackoverflow.com/a/14469041/2537574)

$(document).off('keyup', "#field").on('keyup',"#field" ,function(e) {
    e.preventDefault();
        ... 

Attempt 7 - Did not work Added return false to end of keyup event listener.

$(document).off('keyup', "#field").on('keyup',"#field" ,function(e) {
    e.preventDefault();
    // checks for 6 chars and submits
    return false;
)};

Attempt 8 - Debounce

$(document).off('keyup', "#field").on('keyup',"#field" ,debounce(function(e) {
    // checks for 6 chars and submits
},50));

Attempt 9 - Updated web server Tomcat Updated to latest tomcat 7.70 but that did not fix the issue.

Current Code

$(document).off('keyup', "#field").on('keyup',"#field" ,debounce(function(e) {

        e.preventDefault(); // attempt 6
        if(e.which != 13){
            if($(this).val().length ==6){                   
                submitForm();                   
                $(this).val('');
            }
        }
        return false; // attempt 7
    },50)); 

    function submitForm(){
        // Does some validation and if it passes calls:
        persist();
    }

    function persist(){
        var data = $("#Form").serialize();
        var persistNum = Date.now()+''+(Math.floor(Math.random() * 9)); // used to prevent duplicate insertion on POST retry

        $.ajax({
            url: "/api/save?pNum="+persistNum,
            context:document.body,
            cache: false,
            type: "POST",
            data: data,
            success:function(data, textStatus, jqXHR){},
            error: function(jqXHR, textStatus, errorThrown) 
            {}
        });     
      }
Community
  • 1
  • 1
Rio
  • 21
  • 4
  • could it be shift/ctrl/alt/meta keys causing you issues? – Jaromanda X Aug 23 '16 at 04:28
  • Dont think so, they are using a tablet. – Rio Aug 23 '16 at 04:32
  • Ajax call on every keypress? might want to debounce it – epascarello Aug 26 '16 at 14:38
  • @epascarello It only submits after 6 characters, but I will look into "debounce"-ing. Thank you! – Rio Aug 26 '16 at 14:41
  • @epascarello debounce did not fix the problem but I will keep it as it is an improvement, thank you. – Rio Aug 30 '16 at 14:18
  • Have you ever figured out the cause? I also see the same problem today, where I would see intermittent duplicate POST requests with the exact same timestamp. I also noticed that they seem to be coming from Android Chrome devices. I only caught the issue because my app would randomly log data integrity exceptions as it attempts to insert data to a DB table with unique constraints. The duplicate requests bypasses any condition checks I put in to catch duplicate inserts. – georaldc Apr 23 '18 at 23:33

1 Answers1

0

I cant be certain this is your issue, but you definitely want to use a timer with keyup when using it to call ajax so that it only triggers when the user stops typing. Also, I would keep track of the submission state and only send if not already sending. This is what I use, and it may solve your problem:

var ajaxProcessing = false; // keep track of submission state

$('#field').unbind('keyup').keyup(function(e) {
  if (ajaxProcessing) return; // if processing, ignore keyup
  var $this = $(this);
  keyupDelay(function() { // keyup timer, see below
    if (e.which != 13) {
      if ($this.val().trim().length == 6) { // good UX to trim the value here ;)
        submitForm();
      }
    }
  }, 500); // this sets it to go off .5 seconds after you stop typing. Change as needed

});

function submitForm() {
  // Does some validation and if it passes calls:
  persist();
}

function persist() {
  ajaxProcessing = true; // set is proccessing
  var data = $("#Form").serialize();
  $.ajax({
    url: "/api/save",
    context: document.body,
    cache: false,
    type: "POST",
    data: data,
    success: function(data, textStatus, jqXHR) {
      ajaxProcessing = false;
      $("#Form").find("input[type=text], textarea").val(""); // reset form here
    },
    error: function(jqXHR, textStatus, errorThrown) {
      ajaxProcessing = false;
      alert('Form was submitted. It failed though because ajax wont work from SO');
    }
  });
}



// keyup timer funtion
var keyupDelay = (function() {
  var timer = 0;
  return function(callback, ms) {
    clearTimeout(timer);
    timer = setTimeout(callback, ms);
  };
})();
input {
  width: 400px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form action="" id="Form">
  <input type="text" maxlength="6" placeholder="type 6 chars here then stop" id="field">
</form>
Wesley Smith
  • 17,890
  • 15
  • 70
  • 121
  • I dont think this is will help based on the fact they scan a 6 char barcode and im getting all 6 chars at the server, but I do like the keyupDelay function and I'll be using it for handling bad scans. Thank you. – Rio Aug 23 '16 at 05:44
  • @Rio, Im not sure I understand what you're saying. Your code is currently using `$('#field').unbind('keyup').keyup(function (e) {` to send the ajax...right? – Wesley Smith Aug 23 '16 at 05:47
  • Yes, once it sees 6 characters it sends the ajax. The user is actually scanning in a barcode into the input field instead of typing. In other words the scanner simulates a human typing the numbers. It will always stop at 6 chars since the barcode is only 6 chars. So, as soon as i see str.length = 6, I call submitForm(). Thank you for your help. – Rio Aug 23 '16 at 06:18
  • @Rio Fair enough. The check on `ajaxProcessing` in the above would still stop any duplicate submissions coming from the client side. If that doesnt stop your issue, the issue is almost certainly in your server side code – Wesley Smith Aug 23 '16 at 06:25