1

This is what the code below does:

  • Goes to a table in a database and retrieves some search criteria I will send to Google API (the PHP file is getSearchSon.php)

  • After having the results, I want to loop around it, call the Google API (searchCriteriasFuc) and store the results in an array

  • The last part of the code is doing an update to two different tables with the results returned from Google API (updateSearchDb.php)

In my code, I am using setTimeout in a few occasions which I don't like. Instead of using setTimeout, I would like to properly use callback functions in a more efficient way (This might be the cause of my problem) What is the best way of me doing that?

$(document).ready(function() {


    $.ajax({ 

        url: 'getSearchSon.php',  

        type: 'POST',

        async: true,

        dataType: 'Text',

        /*data: { }, */

        error: function(a, b, c) { alert(a+b+c); }  

    }).done(function(data) {


    if(data != "connection")
    {
        var dataSent = data.split("|");

        var search_criterias = JSON.parse(dataSent[0]);

        var date_length = dataSent[1];

        var divison_factor = dataSent[2];

        var length = search_criterias.length;

        var arrXhr = [];

        var totalResultsArr = [];

        var helperFunc = function(arrayIndex)
        {
            return function()
            {
                var totalResults = 0;

                if (arrXhr[arrayIndex].readyState === 4 && arrXhr[arrayIndex].status == 200) 
                {
                    totalResults = JSON.parse(arrXhr[arrayIndex].responseText).queries.nextPage[0].totalResults;

                    totalResultsArr.push(totalResults);
                }
            }
        }

        var searchCriteriasFuc = function getTotalResults(searchParam, callback) 
        {   
            var searchParamLength = searchParam.length;

            var url = "";

            for(var i=0;i<searchParamLength;i++)
            {
                url = "https://www.googleapis.com/customsearch/v1?q=" + searchParam[i] + "&cx=005894674626506192190:j1zrf-as6vg&key=AIzaSyCanPMUPsyt3mXQd2GOhMZgD4l472jcDNM&dateRestrict=" + date_length;

                arrXhr[i] = new XMLHttpRequest();

                arrXhr[i].open("GET", url, true);

                arrXhr[i].send();

                arrXhr[i].onreadystatechange = helperFunc(i);
            }

            setTimeout(function()
            {       
                if (typeof callback == "function")  callback.apply(totalResultsArr);
            }, 4000);


            return searchParam;
        }   

        function callbackFunction()
        { 
            var results_arr = this.sort();

            var countResultsArr = JSON.stringify(results_arr);

            $.ajax({

                url: 'updateSearchDb.php',  

                type: 'POST',

                async: true,

                dataType: 'Text',

                data: { 'countResultsArr': countResultsArr },

                error: function(a, b, c) { alert(a+b+c); }  

            }).done(function(data) {

                var resultsDiv = document.getElementById("search");

                if(data == "NORECORD") resultsDiv.innerHTML = 'Updated failed. There was a problem with the database';

                else resultsDiv.innerHTML = 'Update was successful';

            }); //end second ajax call
        }

        //llamando funcion principal
        var arrSearchCriterias = searchCriteriasFuc(search_criterias, callbackFunction);

    }
    else
    {
        alert("Problem with MySQL connection.");
    }

    }); // end ajax 

});
Erick
  • 823
  • 16
  • 37
  • Was the answer posted incorrect? – Erick Jan 23 '16 at 16:29
  • 1
    Looks like what you've got should work just fine – adeneo Jan 23 '16 at 16:30
  • Is es7 async-await an option here? – Tamas Hegedus Jan 23 '16 at 16:32
  • If I understand correctly you want the http requests to be executed sequentially, not all of them at once, correct? – Tamas Hegedus Jan 23 '16 at 16:34
  • I made a few changes to the code when posting it here. Let me post all of it as I have it and explain what I want (I will change the post in a second) – Erick Jan 23 '16 at 16:34
  • The line you wanna pause registers an callback which is triggered on every state change - including the final state change when the request is completed. – Andreas Jan 23 '16 at 16:34
  • I updated the code and explained with as much detail as possible. Let me know what questions you guys have – Erick Jan 23 '16 at 16:42
  • 1
    Irrelevant, but is totalResults a global variable? – Tamas Hegedus Jan 23 '16 at 16:46
  • @hege_hegedus that is a good question. I forgot to declare that variable (By the way, I posted the entire code so if you don't see the declaration there, is that I made a mistake and forgot to declare it which might be part of the problem) I added the line var totalResults = 0; inside the helperFunc – Erick Jan 23 '16 at 16:48
  • I updated the post. I already know why I am getting the error I mentioned. However, I could make this code a lot more efficient. Right now I have a setTimeout for a few seconds. Instead of doing that, I would like to use callbacks – Erick Jan 23 '16 at 16:56

3 Answers3

2

How you did it in 2015

Callbacks are things of the past. Nowadays you represent result values of asynchronous tasks with Promises. Here is some untested code:

$(document).ready(function() {
  $.ajax({ 
      url: 'getSearchSon.php',  
      type: 'POST',
      async: true,
      dataType: 'text'
      /*data: { }, */
  }).then(function(data) {
    if (data == 'connection') {
      alert("Problem with MySQL connection.");
    } else {
      var dataSent = data.split("|");
      var search_criterias = JSON.parse(dataSent[0]);
      var date_length = dataSent[1];
      var divison_factor = dataSent[2];

      return Promise.all(search_criterias.map(function(criteria) {
        return $.ajax({
          url: "https://www.googleapis.com/customsearch/v1"
            + "?q=" + criteria 
            + "&cx=005894674626506192190:j1zrf-as6vg"
            + "&key=AIzaSyCanPMUPsyt3mXQd2GOhMZgD4l472jcDNM"
            + "&dateRestrict=" + date_length,
          type: 'GET'
        });
      })).then(function(totalResultsArr) {
        totalResultsArr.sort();
        var countResultsArr = JSON.stringify(totalResultsArr);

        return $.ajax({
          url: 'updateSearchDb.php',  
          type: 'POST',
          async: true,
          dataType: 'text',
          data: { 'countResultsArr': countResultsArr },
          error: function(a, b, c) { alert(a+b+c); }  
        });
      }).then(function(data) {
        var resultsDiv = document.getElementById("search");
        if(data == "NORECORD") {
          resultsDiv.innerHTML = 'Updated failed. There was a problem with the database';
        } else {
          resultsDiv.innerHTML = 'Update was successful';
        }
      });
    }
  }).then(null, function() {
    alert('Some unexpected error occured: ' + e);
  });
});

This is how you do it in 2016 (ES7)

You can just use async/await.

$(document).ready(async() => {
  try {
    var data = await $.ajax({ 
        url: 'getSearchSon.php',  
        type: 'POST',
        async: true,
        dataType: 'text'
        /*data: { }, */
    });
    if (data == 'connection') {
      alert("Problem with MySQL connection.");
    } else {
      var dataSent = data.split("|");
      var search_criterias = JSON.parse(dataSent[0]);
      var date_length = dataSent[1];
      var divison_factor = dataSent[2];

      var totalResultsArr = await Promise.all(
        search_criterias.map(criteria => $.ajax({
          url: "https://www.googleapis.com/customsearch/v1"
          + "?q=" + criteria 
          + "&cx=005894674626506192190:j1zrf-as6vg"
          + "&key=AIzaSyCanPMUPsyt3mXQd2GOhMZgD4l472jcDNM"
          + "&dateRestrict=" + date_length,
          type: 'GET'
        }))
      );

      totalResultsArr.sort();
      var countResultsArr = JSON.stringify(totalResultsArr);
      var data2 = await $.ajax({
          url: 'updateSearchDb.php',  
          type: 'POST',
          async: true,
          dataType: 'text',
          data: { 'countResultsArr': countResultsArr },
          error: function(a, b, c) { alert(a+b+c); }  
      });
      if(data2 == "NORECORD") {
        resultsDiv.innerHTML = 'Updated failed. There was a problem with the database';
      } else {
        resultsDiv.innerHTML = 'Update was successful';
      }
    }
  } catch(e) {
    alert('Some unexpected error occured: ' + e);
  }
});

UPDATE 2016

Unfortunately the async/await proposal didn't make it to the ES7 specification ultimately, so it is still non-standard.

Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
  • I will try implementing the 2016 version. Do I need to include any libraries or with JQuery is all sufficient? I mean I have never used Promise or await. – Erick Jan 23 '16 at 17:14
  • @Erick to be honest you cant just use ES7 out of the box yet. There are hardly any browsers that support async/await, but you can transpile ES7 code to ES5 using [babel](http://babeljs.io/repl/), and will have to include Facebooks regenerator runtime. The es2015 version should work fine on all modern browsers. – Tamas Hegedus Jan 23 '16 at 17:19
0

To get the callback execute after google calls are finished you could change:

    var requestCounter = 0;        

    var helperFunc = function(arrayIndex)
    {
        return function()
        {
            if (arrXhr[arrayIndex].readyState === 4 && arrXhr[arrayIndex].status == 200) 
            {
                requestCounter++;                    

                totalResults = JSON.parse(arrXhr[arrayIndex].responseText).queries.nextPage[0].totalResults;

                totalResultsArr.push(totalResults);

                if (requestCounter === search_criterias.length) {
                    callbackFunction.apply(totalResultsArr);
                }
            }
        }
    }

then remove the setTimeout on searchCreteriaFuc.

Consider using promises and Promise.all to get all much cleaner :D

Federico Baron
  • 967
  • 6
  • 15
0

You could reformat your getTotalResults function in the following matter, it would then search rather sequential, but it should also do the trick in returning your results with an extra callback.

'use strict';

function getTotalResults(searchParam, callback) {
  var url = "https://www.googleapis.com/customsearch/v1?q={param}&cx=005894674626506192190:j1zrf-as6vg&key=AIzaSyCanPMUPsyt3mXQd2GOhMZgD4l472jcDNM&dateRestrict=" + (new Date()).getTime(),
    i = 0,
    len = searchParam.length,
    results = [],
    req, nextRequest = function() {
      console.log('received results for "' + searchParam[i] + '"');
      if (++i < len) {
        completeRequest(url.replace('{param}', searchParam[i]), results, nextRequest);
      } else {
        callback(results);
      }
    };
  
  completeRequest(url.replace('{param}', searchParam[0]), results, nextRequest);
}

function completeRequest(url, resultArr, completedCallback) {
  var req = new XMLHttpRequest();

  req.open("GET", url, true);
  req.onreadystatechange = function() {
    if (this.readyState === 4 && this.status == 200) {
      var totalResults = JSON.parse(this.responseText).queries.nextPage[0].totalResults;
      resultArr.push(totalResults);
      completedCallback();
    }
  };
  req.send();
}

getTotalResults(['ford', 'volkswagen', 'citroen', 'renault', 'chrysler', 'dacia'], function(searchResults) {
  console.log(searchResults.length + ' results found!', searchResults);
});

However, since you already use JQuery in your code, you could also construct all the requests, and then use the JQuery.when functionality, as explained in this question

Wait until all jQuery Ajax requests are done?

Community
  • 1
  • 1
Icepickle
  • 12,689
  • 3
  • 34
  • 48