Angular deferred implementation only outputs last value of loop - javascript

I have a custom synchronization process where I queue up, in order, all of my sync records. When my service retrieves more than 1 sync record, it will process them, then update my last sync date for every successful record, or log my error when it fails (without updating the last sync date) and abort the sync process.
I've implemented the $q.all from AngularJS. Here's a subset of the sync loop:
var processes = [];
for (var i in data) {
if (data[i] === null || data[i].TableName == null || data[i].Query == null || data[i].Params == null) {
// Let's throw an error here...
throw new TypeError("ERROR! The data retrieved from the download sync process was of an unexpected type.");
}
var params = data[i].Params;
var paramsMassaged = params.replaceAll("[", "").replaceAll("]", "").replaceAll(", ", ",").replaceAll("'", "");
var paramsArray = paramsMassaged.split(",");
mlog.Log("Query: " + data[i].Query);
mlog.Log("Params: " + paramsArray);
if (data[i].TableName === "table1") {
var process = $table1_DBContext.ExecuteSyncItem(data[i].Query, paramsArray);
process.then(
function () {
$DBConfigurations_DBContext.UpdateLastSyncDate(data[i].CreatedDate, function (response) {
mlog.Log(response);
});
},
function (response) {
mlog.LogSync("Error syncing record: " + response, "ERROR", data[i].Id);
},
null
);
processes.push(process);
} else if (data[i].TableName === "table2") {
var process = $table2_DBContext.ExecuteSyncItem(data[i].Query, paramsArray);
process.then(
function () {
$DBConfigurations_DBContext.UpdateLastSyncDate(data[i].CreatedDate, function (response) {
mlog.Log(response);
});
},
function (response) {
mlog.LogSync("Error syncing record: " + response, "ERROR", data[i].Id);
},
null
);
processes.push(process);
} else {
mlog.LogSync("WARNING! This table is not included in the sync process. You have an outdated version of the application. Table: " + data[i].TableName);
}
}
$q.all(processes)
.then(function (result) {
mlog.LogSync("---Finished syncing all records");
}, function (response) {
mlog.LogSync("Sync Failure - " + response, "ERROR");
});
Example ExecuteSyncItem function:
ExecuteSyncItem: function (script, params) {
window.logger.logIt("In the table1 ExecuteSyncItem function...");
var primaryKey = params[params.length - 1];
var deferred = $q.defer();
$DBService.ExecuteQuery(script, params,
function (insertId, rowsAffected, rows) {
window.logger.logIt("rowsAffected: " + rowsAffected.rowsAffected);
if (rowsAffected.rowsAffected <= 1) {
deferred.resolve();
} else {
deferred.resolve(errorMessage);
}
},
function (tx, error) {
deferred.reject("Failed to sync table1 record with primary key: " + primaryKey + "; Error: " + error.message);
}
);
return deferred.promise;
}
The problem I'm running into is, if there are more than 1 sync records that fail, then this line displays the same value for all records that failed (not sure if it's the first failure record, or the last).
mlog.LogSync("Error syncing record: " + response, "ERROR", data[i].Id);
How do I get it to display the information for the specific record that failed, instead of the same message "x" times?

As mentioned by comradburk wrapping your processes in a closure within a loop is a good solution, but there is an angular way in solving this problem. Instead of using the native for-in loop, you can do it via angular.forEach() and loop through all the data elements.
var processes = [];
angular.forEach(data, function(item) {
if (item === null || item.TableName == null || item.Query == null || item.Params == null) {
// Let's throw an error here...
throw new TypeError("ERROR! The data retrieved from the download sync process was of an unexpected type.");
}
var params = item.Params;
var paramsMassaged = params.replaceAll("[", "").replaceAll("]", "").replaceAll(", ", ",").replaceAll("'", "");
var paramsArray = paramsMassaged.split(",");
mlog.Log("Query: " + item.Query);
mlog.Log("Params: " + paramsArray);
if (item.TableName === "table1") {
var process = $table1_DBContext.ExecuteSyncItem(item.Query, paramsArray);
process.then(
function () {
$DBConfigurations_DBContext.UpdateLastSyncDate(item.CreatedDate, function (response) {
mlog.Log(response);
});
},
function (response) {
mlog.LogSync("Error syncing record: " + response, "ERROR", item.Id);
},
null
);
processes.push(process);
} else if (item.TableName === "table2") {
var process = $table2_DBContext.ExecuteSyncItem(item.Query, paramsArray);
process.then(
function () {
$DBConfigurations_DBContext.UpdateLastSyncDate(item.CreatedDate, function (response) {
mlog.Log(response);
});
},
function (response) {
mlog.LogSync("Error syncing record: " + response, "ERROR", item.Id);
},
null
);
processes.push(process);
} else {
mlog.LogSync("WARNING! This table is not included in the sync process. You have an outdated version of the application. Table: " + item.TableName);
}
});
$q.all(processes)
.then(function (result) {
mlog.LogSync("---Finished syncing all records");
}, function (response) {
mlog.LogSync("Sync Failure - " + response, "ERROR");
});

The problem is due the closure you have on i. When the callback function executes, the value of i will be the last value in the for loop. You need to bind that value i to a separate, unchanging value. The easiest way to do that is with a self invoking function.
for (var i in data) {
(function(item) {
// Put your logic in here and use item instead of i, for example
mlog.LogSync("Error syncing record: " + response, "ERROR", data[item].Id
})(i);
}
Here's a good read for why closures cause this (it's a pretty common problem):
Javascript infamous Loop issue?

Related

Recursive promise not returning expected value

With a slight modification, I am attempting to use the code provided by Bergi in jQuery Recursive AJAX Call Promise. In my case I make an AJAX call to test if a username is already used. If it is already in use then compose a new username and test that one. Once we have a username that is not in use then we are done and return that unused username. However, I am not getting the expected return value. The return value I get is undefined. The console log statement:
console.log("Return => " + username);
just before the return from the requestUsername function shows that I am returning a good value, but it is not making it to the:
requestUnused().done(function(unused_uname)
statement. Here is my code:
$(document).ready(function() {
function request(query_val) {
// return the AJAX promise
return $.ajax({
url: "/php/is_dup_ad_json.php",
method: 'GET',
dataType: 'json',
data: {
query: query_val, sid: Math.random()
},
});
}
function requestUsername(username) {
console.log("Initial => " + username);
return request(username).then(function(ajax_json){
$.each(ajax_json, function(key, value) {
$.each(value, function(k, v) {
if ((k == "duplicate") && (v > 0)) {
// try again with a different username
var first_initial = fname.substr(0,1);
var surname = lname.substr(0,6);
var idx = v + 1;
var tmpUname = surname + first_initial + idx;
console.log("Temp => " + tmpUname);
return requestUsername(tmpUname);
}
else {
console.log("Return => " + username);
return username;
}
});
});
});
}
function requestUnused(){
var fname = "bugs";
var lname = "bunny";
var first_initial = fname.substr(0,1);
var surname = lname.substr(0,7);
var init_uname = surname + first_initial;
return requestUsername(init_uname);
}
$("#test").on('click', function() {
requestUnused().done(function(unused_uname) {
console.log("Done => " + unused_uname);
});
});
});
Without debugging tools at hand, I would guess that the return value from "requestUnused()," which is a ".then" returned from "requestUsername" is competing with the ".done". I believe ".done" and ".then" serve a similar purpose. If you want to keep a modular approach, separating the functions as it were, you could define the function in the ".then" externally and remove "requestUsername" entirely. Then (no pun intended) call "request" directly in "requestUnused," applying the ".then" functionality extracted previously in the ".click" function instead of ".done."
Alternatively, you could simply call "requestUnused()" in the click function without a ".done".

How to chain two ajax requests with promises

I am having trouble with ajax/promises. I have two ajax requests total, with the second ajax call relying data to be returned by the first ajax call.
My first ajax call finds Latitude, Longitude, and country code of the value of #search.
My second ajax call finds the weather of that city, but the API URL is dependent on the Latitude, Longitude and country code that my first ajax call returns. So the second ajax call can't be started until the first one is finished.
My logic here is that var ajax1 is assigned a promise, and var ajax2 starts after ajax1.then() checks that ajax1's promise is resolved. Then ajax2 runs and returns another promise. Finally ajax2.done starts after it checks that ajax2's promise is resolved, and then starting my successWeatherFunction().
My problem is that ajax2.done is not working, as the console.log("test") is not showing up on the console. The two earlier console.logs, console.log(info) and console.log(weatherApiUrl) are working.
Thanks!
$("#search").keypress(function(event) {
if (event.which === 13) {
var searchCity = $("#search").val();
var jsonURL = "http://autocomplete.wunderground.com/aq?query=" + searchCity + "&cb=?"
var ajax1 = $.getJSON(jsonURL);
var ajax2 = ajax1.then(function(data) {
var info = [];
info.push(data["RESULTS"][0]["name"]);
info.push(data["RESULTS"][0]["c"]);
info.push(data["RESULTS"][0]["lat"]);
info.push(data["RESULTS"][0]["lon"]);
console.log(info);
var searchLat = info[2];
var searchLng = info[3];
var countryCode = info[1];
if (countryCode === "US") {
var weatherApiUrl = "https://api.forecast.io/forecast/{APIKEY}/" + searchLat + "," + searchLng + "?exclude=minutely" + "&callback=?";
} else {
var weatherApiUrl = "https://api.forecast.io/forecast/{APIKEY}/" + searchLat + "," + searchLng + "?exclude=minutely" + "?units=si" + "&callback=?";
console.log(weatherApiUrl);
}
return $.getJSON(weatherApiUrl);
});
ajax2.done(function(data){
console.log("test");
successCityWeather(data);
});
Your code use then and done. done is the old promises jQuery syntax so you should use only then.
The following code works for me :
$(function() {
$.get('/test').then(function() {
console.log('First request end');
return $.get('/test');
}).then(function() {
console.log('second request end');
});
});
But in your case, maybe a one of your request fail. Give a second parameter to then to log the error :
$.getJSON('...').then(function(data) {
console.log('success', data);
}, function(data) {
console.log('fail', data);
});
If not sure, always use always() handler. That way you will know if the request actually finished with error or not at all.
$.ajax( ...params... )
.always(function(jqXHR, textStatus) {
if (textStatus != "success") {
alert("Error: " + jqXHR.statusText); //error is always called .statusText
} else {
alert("Success: " + jqXHR.response); //might not always be named .response
}});
$.post(jsonURL)
.then(function (data) {
var info = [];
// some actions
return $.getJSON(weatherApiUrl);
})
.then(function(data, status, promise) {
// some actions
successCityWeather(data);
})

why callback variable is undefined in this function?

I have a function which handle an Ajax call in my status.js:
window.APPLICATION = window.APPLICATION || {};
window.APPLICATION = {
getStatus: function(callingScope) {
var _this = this;
var getStatus = $.ajax({
type: "POST",
url: "/php/status.php",
data: {
"action": "mystatus"
}
});
getStatus.done(function(Response) {
if (Response.data != undefined) {
_this.cache.statusdata = {
userstatus: Response.data
};
} else if (Response.error != undefined) {
console.log('status data request error...');
}
callingScope.ajaxLoaded++;
}),
getStatus.fail(function(jqXHR, textStatus) {
console.log("user save form fail - an error occurred: (" + textStatus + ").");
});
},
}
in my user.js, I call it:
window.APPLICATION.USER.ajaxLoaded = 0;
window.APPLICATION.getStatus(window.APPLICATION.USER);
I need this ajaxLoaded variable counter as I have other Ajax calls. Need it to determine whether all calls finished.
However, I got following errors in console:
how to solve it?
window.APPLICATION.USER.ajaxLoaded = 0;
this line is giving error.
It's because, USER is undefined.
You need to declare USER as an object first before defining properties.
so
window.APPLICATION.USER = {};
then
window.APPLICATION.USER.ajaxLoaded = 0; // this will work
Inside the callback you are trying to access window.APPLICATION.USER object which was undefined so callingScope is giving error that it's undefined

Trying to get an async DB request to work in Angular, "Cannot call method then of undefined"

I'm building a PhoneGap app using AngularJS + an SQLite database. I am having a classic "How does asynchronous work" Angular problem with a database query, getting error "Cannot call method then of undefined". I am hoping someone can help me to see the error of my ways.
Here's my query function. Every alert() in here returns meaningful data indicating that the transaction itself is successful:
.factory('SQLService', ['$q', '$rootScope', 'phonegapReady',
function ($q, $rootScope, phonegapReady) {
function search(query) {
alert("Search running with " + query);
var promise = db.transaction(function(transaction) {
var str = "SELECT category, id, chapter, header, snippet(guidelines, '<b>', '</b>', '...', '-1', '-24' ) AS snip FROM guidelines WHERE content MATCH '" + query + "*';";
transaction.executeSql(str,[], function(transaction, result) {
var resultObj = {},
responses = [];
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
resultObj = result.rows.item(i);
alert(resultObj.category); //gives a meaningful value from the DB
responses.push(resultObj);
}
} else {
//default content
}
},defaultNullHandler,defaultErrorHandler);
alert("End of transaction");
});
// Attempting to return the promise to the controller
alert("Return promise"); //this alert happens
return promise;
}
return {
openDB : openDB,
search: search
};
}]);
And in my controller, which gives the "Cannot call method then of undefined" error:
$scope.search = function(query) {
SQLService.search(query).then(function(d) {
console.log("Search THEN"); //never runs
$scope.responses = d; //is never defined
});
}
Thanks to the accepted answer, here is the full working code.
Service
function search(query) {
var deferred = $q.defer();
db.transaction(function(transaction) {
var str = "SELECT category, id, chapter, header, snippet(guidelines, '<b>', '</b>', '...', '-1', '-24' ) AS snip FROM guidelines WHERE content MATCH '" + query + "*';";
transaction.executeSql(str,[], function(transaction, result) {
var resultObj = {},
responses = [];
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
resultObj = result.rows.item(i);
responses.push(resultObj);
}
} else {
resultObj.snip = "No results for " + query;
responses.push(resultObj)
}
deferred.resolve(responses); //at the end of processing the responses
},defaultNullHandler,defaultErrorHandler);
});
// Return the promise to the controller
return deferred.promise;
}
Controller
$scope.search = function(query) {
SQLService.search(query).then(function(d) {
$scope.responses = d;
});
}
I can then access the responses in the template using $scope.responses.
The question here is: what does db.transaction return.
From the way you're using it, I'm guessing it's some 3rd-party code that doesn't return a promise.
Assuming that you're using it correctly (your alert shows the right results), you need to actualy use $q to get the promise working.
Something like this:
function search(query) {
// Set up the $q deferred object.
var deferred = $q.defer();
db.transaction(function(transaction) {
transaction.executeSql(str, [], function(transaction, result) {
// do whatever you need to do to the result
var results = parseDataFrom(result);
// resolve the promise with the results
deferred.resolve(results);
}, nullHandler, errorHandler);
});
// Return the deferred's promise.
return deferred.promise;
}
Now, in your controller, the SQLService.search method will return a promise that should get resolved with the results of your DB call.
You can resolve multiple promises. Pass the array of queries as args
function methodThatChainsPromises(args,tx){
var deferred = $q.defer();
var chain = args.map(function(arg){
var innerDeferred = $q.defer();
tx.executeSql(arg,[],
function(){
console.log("Success Query");
innerDeferred.resolve(true);
},function(){
console.log("Error Query");
innerDeferred.reject();
}
);
return innerDeferred.promise;
});
$q.all(chain)
.then(
function(results) {
deferred.resolve(true)
console.log("deffered resollve"+JSON.stringify(results));
},
function(errors) {
deferred.reject(errors);
console.log("deffered rejected");
});
return deferred.promise;
}

jQuery get sent data back

how to get data back when using jQuery.get method ?
function send_data(pgId)
{
for(var i = 0; i < pgId.length; i++)
{
// $.get(url, data, success(data, textStatus, jqXHR))
$.get('index.php?page=' + pgId[i], pgId[i], function(respTxt, status, xhr)
{
if(status === "success")
{
alert("Data received: " + respTxt + "\n");
alert("Data sent: " + pgId[i]); //<-- ???
}
});
}
}
The parameter what I'm sending is optional, server doesn't accept that parameter, the only thing I want is that to pass that parameter to callback function when succeed to work with. pg_array is array of DIV ids.
I need to get sent data to process when ajax succeeds or at least pass that argument to custom callback when it succeeds.
I'm also new on web development so, apologies. I was searching a lot but I can't understand any of samples it showed.
Regards.
You can do it with a closure that will keep the value of the ID:
function send_data(pgId) {
var callbackWithId = function (pgId) {
//This will keep the pgId for the returned function
return function(respTxt, status, xhr) {
if(status === "success") {
alert("Data received: " + respTxt + "\n");
alert("Data sent: " + pgId);
}
}
}
for(var i = 0; i < pgId.length; i++) {
$.get('index.php?page=' + pgId[i], pgId[i], callbackWithId(pgId[i]));
}
}

Categories