Simultaneous independent AJAX requests - javascript

I have a search suggestion script that pulls results from two Google APIs, orders the results by an integer value and then displays it to a user. The script ensures it only returns the five most relevant responses for each query based on the rel attribute of each.
JSFiddle: http://jsfiddle.net/22LN5/
However, with this current set-up there is no fault tolerance; for example one API being on available or exceeding the query limit on one API so no results are returned.
How can this be resolved?
My jQuery code is:
var combined=[];
$(document).ready(function(){
$("#search").keyup(function(e){
$(this).html("");
$.getJSON("http://suggestqueries.google.com/complete/search?q="+$("#search").val()+"&client=chrome&callback=?",function(data1){
$.getJSON("https://www.googleapis.com/freebase/v1/search?query="+$("#search").val()+"&limit=3&encode=html&callback=?",function(data2){
for(var key in data1[1]){
if(data1[4]["google:suggesttype"][key]=="NAVIGATION"){
combined.push("<li rel='"+data1[4]["google:suggestrelevance"][key]+"'><a href='"+data1[1][key]+"'>"+data1[2][key]+"</a></li>");
}else{
combined.push("<li rel='"+data1[4]["google:suggestrelevance"][key]+"'>"+data1[1][key]+"</li>");
}
}
for(var key in data2.result){
combined.push("<li rel='"+Math.round(data2.result[key].score*5)+"'> Freebase: "+data2.result[key].name+"</li>");
}
combined.sort(function(a,b){
return +$(b).attr("rel")-+$(a).attr("rel");
});
$("#suggest").html(combined.slice(0,5).join(""));
combined=[];
});
});
});
});

I've updated your fiddle: http://jsfiddle.net/22LN5/2/
I whipped up $.whenFaultTolerant, which will always return the promise in the event of completion or failure. Pass in a hash of deferreds, and out comes a hash of results! This is not a complete solution but I hope it is a good step in the right direction.
$.whenFaultTolerant = function(things) {
var remaining = Object.keys(things).length;
var outputs = {};
var dfd = $.Deferred();
$.each(things, function(key, thing) {
thing.always(function(data) {
outputs[key] = data;
--remaining || dfd.resolve(outputs);
});
});
return dfd.promise();
};
Usage:
$.whenFaultTolerant({
suggestqueries: $.getJSON("http://suggestqueries.google.com/complete/search?q="+$("#search").val()+"&client=chrome&callback=?"),
googleapis: $.getJSON("https://www.googleapis.com/freebase/v1/search?query="+$("#search").val()+"&limit=3&encode=html&callback=?")
}).done(function(data){
console.log(data);
});
Outputs:
{
"suggestqueries": <the results from suggestqueries.google.com>,
"googleapis": <the results from www.googleapis.com>
}

Related

Google Cloud SQL not updating with script

I have a long script which is designed to edit a specific row in the Cloud SQL table. The code is long so i will shorten it.
Client Side:
function build_profile(){
var cbid = sessionStorage.getItem("client_id");
var self = this;
var createSuccess = function(data){
var statuse = ["Active", "Wiating", "Discharged"];
if(data !== false){
data = data.split(",");
var dec = app.pages.Profile.descendants;
dec.fname.text = data[1];
dec.sname.text = data[3];
sessionStorage.setItem("school_id", data[9]);
app.popups.Loading.visible = false;
}
};
var init = function() {google.script.run.withSuccessHandler(createSuccess).get_user_data(cbid);};
app.popups.Loading.visible = true;
init();
}
function save_profile() {
var createSuccess = function(data){
var dec = app.pages.Profile.descendants;
console.log(data);
if(data !== -1){
var ds = app.datasources.Clients;
ds.load(function(){
ds.selectIndex(data);
console.log("editing:"+ds.item.CBID);
ds.item.fname = dec.fname_edit.value;
ds.item.sname = dec.sname_edit.value;
ds.load(function(){build_profile();});
});
}
}};
var init = function() {google.script.run.withSuccessHandler(createSuccess).update_client(sessionStorage.getItem("client_id"));};
init();
}
Server Side:
function get_user_data(cbid){
try{
var query = app.models.Clients.newQuery();
query.filters.CBID._equals = parseInt(cbid);
var results = query.run();
if(results.length > 0){
var arr = [
results[0].Id, //0
results[0].fname, //1
results[0].sname //3
];
return arr.join(",");
}else{
return false;
}
}catch(e){
console.error(e);
console.log("function get_user_data");
return false;
}
}
function update_client(cbid) {
try{
var ds = app.models.Clients;
var query = ds.newQuery();
query.filters.CBID._equals = parseInt(cbid);
var results = query.run();
if(results.length > 0){
var id = results[0]._key;
return id+1;
}else{
return -1;
}
}catch(e){
console.error(e);
return -1;
}
}
This gets the Clients table and updates the row for the selected client, then rebuilds the profile with the new information.
EDIT: I have managed to get to a point where its telling me that i cannot run the query (ds.load()) while processing its results. There does not seem to be a manual check to see if it has processed?
Note: datasource.saveChanges() does not work as it saves automatically.
You error is being produced by the client side function save_profile() and it is exactly in this block:
ds.load(function(){
ds.selectIndex(data);
console.log("editing:"+ds.item.CBID);
ds.item.fname = dec.fname_edit.value;
ds.item.sname = dec.sname_edit.value;
ds.load(function(){build_profile();});
});
So what you are doing is reloading the datasource almost immediately before it finishes loading hence you are getting that error
cannot run the query (ds.load()) while processing its results
This is just a matter of timing. A setTimeout can take of the issue. Just do the following:
ds.load(function(){
ds.selectIndex(data);
console.log("editing:"+ds.item.CBID);
ds.item.fname = dec.fname_edit.value;
ds.item.sname = dec.sname_edit.value;
setTimeout(function(){
ds.load(function(){build_profile();});
},1000);
});
I have manage to find a solution to this particular issue. It requires Manual Saving but it saves a lot of hassle as one of the inbuilt solutions can be used rather than relying on dealing with errors or timeouts.
function client_query_and_result(){
var createSuccess = function(data){ //callback function
console.log(data);
};
app.datasources.SomeTable.saveChanges(function(){//ensures all changes have been saved
app.datasources.SomeTable.load(function(){//makes sure to reload the datasource
google.script.run.withSuccessHandler(createSuccess).server_query_and_result(); //at this point All data has been saved and reloaded
});
});
}
The Server side code is the exact same methods. To enable manual saving you can select the table in App Maker -> Datasources -> check "Manual save mode".
Hope this can be useful to someone else.

Next iteration of $.each when received AJAX-content

The question has been asked before, but it is almost four years ago and maybe there is a better solution.
I have a $.each-loop where sometimes additional data is being fetched via ajax.
I am bulding an object with the fetched data, after the loop there is a function that generates HTML from the object. The problem is that the loop finishes before the ajax data arrives. If I place an alert in the HTML-generating-function the content is loading properly.
I am searching for a solution that calls the HTML-generator-function only when the loop and all ajax calls are finished. Maybe it is a solution to count the started Ajax requests and wait if all of them are finished?
I believe jQuery deferred is the right solution for me but I do find only examples where everything stays inside the loop. Can someone help?
I have stripped down my code to the most important things:
//goes through each testplace -->main loop
$.each(jsobject, function(key, value)
{
//build object together...
for (var i = 0, numComputer = jenkinsComputer.contents.computer.length; i < numComputer; i++)
{
//If the testplace is in both objects then fire AJAX request
if (jenkinsComputer.contents.computer[i].displayName == key) //<<<This can happen only once per $.each loop, but it does not happen every time
{
//next $.each-iteration should only happen when received the JSON
var testplaceurl = jenkinsComputer.contents.computer[i].executors[0].currentExecutable.url;
$.when($.getJSON("php/ba-simple-proxy.php?url=" + encodeURI(testplaceurl) + "api/json?depth=1&pretty=1")).done(function(jenkinsUser)
{
//build object together...
});
}
}
}); //End of main Loop ($.each)
generateHTML(builtObject);
It would be great if someone could give me an advice how to do it.
I would do something like this:
var thingstodo = $(jsobject).length;
var notfired = true;
$.each(jsobject, function(key, value)
{
//build object together...
for (var i = 0, numComputer = jenkinsComputer.contents.computer.length; i < numComputer; i++)
{
//If the testplace is in both objects then fire AJAX request
if (jenkinsComputer.contents.computer[i].displayName == key) //<<<This can happen only once per $.each loop, but it does not happen every time
{
//next $.each-iteration should only happen when received the JSON
var testplaceurl = jenkinsComputer.contents.computer[i].executors[0].currentExecutable.url;
$.when($.getJSON("php/ba-simple-proxy.php?url=" + encodeURI(testplaceurl) + "api/json?depth=1&pretty=1")).done(function(jenkinsUser)
{
//build object together...
thingstodo--;
if(thingstodo === 0 && notfired){
notfired = false;
generateHTML(buildObject);
}
});
}else{
thingstodo--;
}
}
}); //End of main Loop ($.each)
if(thingstodo === 0 && notfired){
generateHTML(buildObject);
}
This is short untested example about the solution. I hope this to give you idea.
// I guess that jsobject is array ..
// if it is not object you can use something like:
// var keys = Object.getOwnPropertyNames(jsobject)
(function () {
var dfd = $.Deferred();
function is_not_finished() {
return jsobject.length > 0 && jenkinsComputer.contents.computer.length > 0;
}
(function _handleObject() {
var key = jsobject.shift();
var displayName = jenkinsComputer.contents.computer.shift().displayName;
if (displayName == key) //<<<This can happen only once per $.each loop, but it does not happen every time
{
//next $.each-iteration should only happen when received the JSON
var testplaceurl = jenkinsComputer.contents.computer[i].executors[0].currentExecutable.url;
$.getJSON("php/ba-simple-proxy.php?url=" + encodeURI(testplaceurl) + "api/json?depth=1&pretty=1").done(function(jenkinsUser)
{
//build object together...
if(is_not_finished()) {
setTimeout(_handleObject,0);
} else {
dfd.resolve();
}
});
} else if (is_not_finished()) {
setTimeout(_handleObject,0);
} else {
dfd.resolve();
}
}());
return dfd.promise();
}()).done(function () {
generateHTML(builtObject);
});

Parse.com Javascript Asyn Call within Loop

I have a list of company and would like to calculate a total amount of invoices issued to each company. The following is the code that I wrote. (Actual logic is more complicated within the loop but took them out here)
Basically I want to alert the message once the business logic within the loop is complete (Again, it will do something more complex here). I got a feeling that I can resolve this issue by using Promises but am not quite sure how to use it. I didn't quite follow Parse.com's document. I have been stuck with this for a few hours. Please help!
function calculate(companies) {
companies.forEach(function(company) {
var total = 0;
var invoice = Parse.Object.extend('Invoice');
var query = new Parse.Query(invoice);
query.equalTo('invoiceCompany', company);
query.find().then(function(invoices) {
invoices.forEach(function(invoice) {
total += parseFloat(invoice.get('amount'));
});
});
});
alert("Calculated Finished");
}
You can use promises in paralell:
https://parse.com/docs/js/guide#promises-promises-in-parallel
It would be something like this:
function calculate(companies) {
var promises = [];
companies.forEach(function(company) {
var total = 0;
var invoice = Parse.Object.extend('Invoice');
var query = new Parse.Query(invoice);
query.equalTo('invoiceCompany', company);
var queryPromise = query.find().then(function(invoices) {
invoices.forEach(function(invoice) {
total += parseFloat(invoice.get('amount'));
});
});
promises.push(queryPromise);
});
return Parse.Promise.when(promises);
}
calculate(companies).then(function() {
alert("Calculated Finished");
});

Simultaneous AJAX request with jQuery

I have a search suggestion script that pulls results from two Google APIs, orders the results by an integer value and then displays it to a user.
However, currently the script doesn't appear to return results from the second API until the user has pressed enter or return. Why could this be?
JSFiddle: http://jsfiddle.net/m8Kfx/
My code is:
var combined = [];
$(document).ready(function(){
$("#search").keyup(function(){
$("#suggest").html("");
$.getJSON("http://suggestqueries.google.com/complete/search?q="+$("#search").val()+"&client=chrome&callback=?",function(data){
for(var key in data[1]){
if(data[4]["google:suggesttype"][key]=="NAVIGATION"){
combined.push("<li rel='"+data[4]["google:suggestrelevance"][key]+"'><a href='"+data[1][key]+"'>"+data[2][key]+"</a></li>");
}else{
combined.push("<li rel='"+data[4]["google:suggestrelevance"][key]+"'>"+data[1][key]+"</li>");
}
}
});
$.getJSON("https://www.googleapis.com/freebase/v1/search?query="+$("#search").val()+"&limit=3&encode=html&callback=?",function(data){
for(var key in data.result){
combined.push("<li rel='"+Math.round(data.result[key].score*5)+"'> Freebase: "+data.result[key].name+"</li>");
}
});
combined.sort(function(a,b){
return +$(b).attr("rel") - +$(a).attr("rel");
});
$("#suggest").html(combined.slice(0, 5).join(""));
combined = [];
});
});
Actually, it does return values, but you have a timing issue here. You fill your list with results, before the requests have actually been finished. Try something like this instead:
http://jsfiddle.net/jDvVL/1/
Also, since you're appending the result of your second request to your array, they will never show up due to your .slice(0,5), so I removed that.
You can do something like this
var allData = []
$.getJSON("/values/1", function(data) {
allData.push(data);
if(data.length == 2){
processData(allData) // where process data processes all the data
}
});
$.getJSON("/values/2", function(data) {
allData.push(data);
if(data.length == 2){
processData(allData) // where process data processes all the data
}
});
var processData = function(data){
var sum = data[0] + data[1]
$('#mynode').html(sum);
}
Wrap the secong getJSON in the first, like this. Nice code BTW.
var combined = [];
$(document).ready(function(){
$("#search").keyup(function(){
$("#suggest").html("");
$.getJSON("http://suggestqueries.google.com/complete/search?q="+$("#search").val()+"&client=chrome&callback=?",function(data){
for(var key in data[1]){
if(data[4]["google:suggesttype"][key]=="NAVIGATION"){
combined.push("<li rel='"+data[4]["google:suggestrelevance"][key]+"'><a href='"+data[1][key]+"'>"+data[2][key]+"</a></li>");
}else{
combined.push("<li rel='"+data[4]["google:suggestrelevance"][key]+"'>"+data[1][key]+"</li>");
}
}
$.getJSON("https://www.googleapis.com/freebase/v1/search?query="+$("#search").val()+"&limit=3&encode=html&callback=?",function(data){
for(var key in data.result){
combined.push("<li rel='"+Math.round(data.result[key].score*5)+"'> Freebase: "+data.result[key].name+"</li>");
}
});
});
combined.sort(function(a,b){
return +$(b).attr("rel") - +$(a).attr("rel");
});
$("#suggest").html(combined.slice(0, 5).join(""));
combined = [];
});
});
I noticed two things about the code.
The sorting and appending should be called in the callbacks of the ajax functions, this can be achieved by making another function that handles sorting and display. Then call this function in the success callback.
Second, the freemarker results are showing up, however they are always sent to the bottom of the list. If you view 200 of your results they are at the bottom.
var combined = [];
$(document).ready(function(){
$("#search").keyup(function(){
$("#suggest").html("");
$.getJSON("http://suggestqueries.google.com/complete/search?q="+$("#search").val()+"&client=chrome&callback=?",function(data){
for(var key in data[1]){
if(data[4]["google:suggesttype"][key]=="NAVIGATION"){
combined.push("<li rel='"+data[4]["google:suggestrelevance"][key]+"'><a href='"+data[1][key]+"'>"+data[2][key]+"</a></li>");
}else{
combined.push("<li rel='"+data[4]["google:suggestrelevance"][key]+"'>"+data[1][key]+"</li>");
}
}
sortAndDisplay(combined);
});
$.getJSON("https://www.googleapis.com/freebase/v1/search?query="+$("#search").val()+"&limit=3&encode=html&callback=?",function(data){
for(var key in data.result){
combined.push("<li rel='"+Math.round(data.result[key].score*5)+"'> Freebase: "+data.result[key].name+"</li>");
}
sortAndDisplay(combined);
});
});
});
function sortAndDisplay(combined){
combined.sort(function(a,b){
return +$(b).attr("rel") - +$(a).attr("rel");
});
$("#suggest").html(combined.slice(0, 200).join(""));
combined = [];
}
Working Example http://jsfiddle.net/m8Kfx/4/

how to return results to the calling function (development of a firefox extension)

In my firefox extension I have a sqlite-database with some tables. Using executeAsync(), I updated the tables, inserted some new data and selected some values. The select-case cause me some problems.
Within the handleCompletion()-function I can pass the retrieved data from the table to another function (and can alerting the results, for example). But I would like to pass the result back to the calling function. I searched the net for an answer to my problem, but I can't find a solution.
What I found:
retrieveData: function() {
var rows = new Array();
if (this.dbConn.connectionReady){
statement = this.dbConn.createAsyncStatement("SELECT * " +
"FROM 'domains' " +
";");
statement.executeAsync ({
handleResult: function(aResultSet) {
var i = 0;
for (let row = aResultSet.getNextRow(); row; row = aResultSet.getNextRow()) {
rows[i] = row;
++i;
}
},
handleError: function(anError) {
alert("error");
},
handleCompletion: function(aReason) {
if (aReason != Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
// something went wrong
alert("error2");
}
}
});
}
return rows;
}
This code does not return the expected results. The statement is executed after the method returned the Array "rows". So, my calling function can never receive the data from the table...
How can I solve this problem? Is there something like a timeout for returning the datarows of the table?
Thanks a lot for help!
You should ideally be dealing in callbacks in the above example. Thats how the SQLite API developers intended the API to be used. :)
retrieveData: function(_callback) {
...
statement.executeAsync ({
...
handleCompletion: function(aReason) {
...
_callback(rows);
}
});
}
And where you call the function:
retrieveData(function(rows) {
// do stuff with rows here
});

Categories

Resources