I'm writing a library to access data from a server, and return formatted data to the consumer of my functions.
Here is an example of what I'd like to have:
// my code
var model = function () {
return $.ajax(myRequest).done(function (rawData) {
return treatment(data);
});
}
// client code
var useModel = function () {
var modelPromise = model();
modelPromise.done(function (formattedData) { // consume this result })
}
where formattedData is the result of my first done callback and not the rawData.
Do you have any ideas?
Thanks
R.
Regis,
jQuery's documention for .then() says :
As of jQuery 1.8, the deferred.then() method returns a new promise
that can filter the status and values of a deferred through a
function, replacing the now-deprecated deferred.pipe() method.
The second example for .then() is similar to what you want (though not involving ajax).
As far as I can tell, the necessary changes to your code are very minimal :
// my code
var model = function () {
return $.ajax(myRequest).then(function (rawData) {
return treatment(rawData);
});
}
// client code
var useModel = function () {
var modelPromise = model();
modelPromise.done(function (formattedData) { // consume this result })
}
Regis,
I like Beetroot's answer a lot. Here's an example I made while trying to understand this concept for myself: Multiple asynchronous requests with jQuery .
Source from jsFiddle:
var logIt = function (msg) {
console.log(((new Date()).toLocaleTimeString()) + ": " + msg);
}, pauseBrowser = function (ms) {
ms += new Date().getTime();
while (new Date() < ms) {}
}, dataForService1 = {
json: JSON.stringify({
serviceNumber: 1,
description: "Service #1's data",
pauseAfterward: 3 // for pausing the client-side
}),
delay: 0 // delay on the server-side
}, dataForService2 = {
json: JSON.stringify({
serviceNumber: 2,
description: "Service #2's data",
pauseAfterward: 1
}),
delay: 0 // delay on the server-side
};
function getAjaxConfiguration() {
return {
type: 'POST',
url: '/echo/json/',
success: function (data) {
var msg = "Handling service #" + data.serviceNumber + "'s success";
logIt(msg);
logIt(JSON.stringify(data));
}
};
}
var async2 = function () {
var ajaxConfig = $.extend(getAjaxConfiguration(), {
data: dataForService2
});
return $.ajax(ajaxConfig);
};
var async1 = function () {
var ajaxConfig = $.extend(getAjaxConfiguration(), {
data: dataForService1
});
return $.ajax(ajaxConfig);
};
var do2AsynchronousFunctions = function () {
var dfd = new $.Deferred();
async1()
.then(function (async1ResponseData) {
logIt("async1's then() method called, waiting " + async1ResponseData.pauseAfterward + " seconds");
pauseBrowser(async1ResponseData.pauseAfterward * 1000);
})
.done(function (a1d) {
logIt("async1's done() method was called");
return async2()
.then(function (async2ResponseData) {
logIt("async2's then() method called, waiting " + async2ResponseData.pauseAfterward + " seconds");
pauseBrowser(async2ResponseData.pauseAfterward * 1000);
})
.done(function (a2d) {
logIt("async2's done() method was called");
dfd.resolve("final return value");
});
});
return dfd.promise();
};
$.when(do2AsynchronousFunctions()).done(function (retVal) {
logIt('Everything is now done! Final return value: ' + JSON.stringify(retVal));
});
Related
I have a jQuery file which also uses unserscore.js. It controls the selections of dates and different venues. For one of the pages it also controls which visuals are displayed depending on the type of venue. I can successfully, using ajax, get the type of page, but I have been unable to pass that value to a public variable in the script. It is based on which WiFi spot the data is coming from. If the data is from a local spot the page should display a d3 bubble chart. If it's from a remote spot it should display a map of the venue. Currently I have the functionality working with hard coding based on the id of the venue which is far from ideal.In order to make the decision based on which spot the venue is using I created an ajax call that gets the "spot". With console.log I can see that I am getting the correct result from the ajax call, but I'm missing something in terms of passing that information to a variable so I can use it.
This is the complete jQuery files:
define([
"ui/selects",
], function (SelectsUiClass) {
var global = this;
var MainControlsClass = function () {
// Private vars
var _this = this,
_xhr = null,
_selects = new SelectsUiClass(),
_dateRangeSelect,
_venueSelect,
_floorSelect,
_zoneSelect;
// Public vars
this.Selects = null;
this.spotName = null;
// Private Methods
var _construct = function () {
_dateRangeSelect = _selects.InitSelect('#mainControls-dateRange', _onSelectChange);
_venueSelect = _selects.InitSelect('#mainControls-venue', _onSelectChange);
_floorSelect = _selects.InitSelect('#mainControls-floor', _onSelectChange);
_zoneSelect = _selects.InitSelect('#mainControls-zone', _onSelectChange);
var value = _this.GetVenue();
_getChartDisplayDiv(value);
};
var _getChartDisplayDiv = function (venueId) {
var path = window.location.pathname,
pathArray = path.split("/"),
page = pathArray[pathArray.length - 1];
console.log('controlsjs 36, navigation page: ' , page);
console.log('controlsjs 37, venue value: ' , venueId);
_this.Load(venueId);
console.log('Controls 40, sPot Name = ', _this.spotName);
if (page === 'heatmap') {
if (venueId === 8 || venueId === 354) {
//make the bubble div visible
$("#heatmap-bubble").show();
//make the map div invisible
$("#heatmap-map").hide();
} else {
//make the map div visible
$("#heatmap-map").show();
//make the bubble div invisible
$("#heatmap-bubble").hide();
}
}
}
this.Load = function (venueId) {
console.log("Controls 66, Venue Id sent = ", venueId);
if (_xhr) {
_xhr.abort();
_xhr = null;
}
_this.SetLoading(true);
_xhr = $.ajax({
url: $("meta[name='root']").attr("content") + '/app/heatmap/spot',
type: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
venue_id: venueId
},
dataType: 'JSON',
async: true,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
_this.SetLoading(false);
},
success: function (response) {
_this.SetLoading(false);
console.log("Controls 90, Response of ajax call = ", response);
_this.Update(response);
}
});
};
// Public functions
this.SetLoading = function (option) {
if (_.isUndefined(option)) { option = false; }
if (this.spotName) { this.spotName.SetLoading(option); }
};
this.Update = function (data) {
if (_.isUndefined(data) || _.isNull(data)) {
console.log('Controls 106: Spot Name: ', data)
this.spotName = data;
}
};
var _getVenueData = function (venueId) {
for (var i = 0; i < venuesData.length; i++) {
if (venuesData[i].id === venueId) {
if (!_.isUndefined(venuesData[i].spot_data)) {
return venuesData[i].spot_data;
}
}
}
};
var _onVenueChange = function () {
var value = _this.GetVenue();
if (_.isNull(value)) {
return;
}
_getChartDisplayDiv(value);
//_setSelectValue(_venueSelect, value);
var venueData = _getVenueData(value);
console.log('Venue data received: ', venueData);
if (!_.isUndefined(venueData) && !_.isUndefined(venueData.floors)) {
_selects.UpdateSelect(_floorSelect, venueData.floors);
_onFloorChange();
}
};
var _onFloorChange = function () {
var value = _this.GetFloor(),
zones = [];
if (_.isNull(value)) {
return;
}
//_setSelectValue(_floorSelect, value);
if (_.isNumber(value)) {
var venueData = _getVenueData(_this.GetVenue()),
floors = venueData.floors;
for (var i = 0; i < floors.length; i++) {
if (floors[i].id === value) {
zones = floors[i].zones;
}
}
}
_selects.UpdateSelect(_zoneSelect, zones);
};
var _onZoneChange = function () {
var value = _this.GetZone();
if (_.isNull(value)) {
return;
}
//_setSelectValue(_zoneSelect, value);
};
var _onSelectChange = function (e) {
var t = $(e.target),
id = t.attr('id');
if (_venueSelect && _venueSelect.attr('id') === id) {
_onVenueChange();
} else if (_floorSelect && _floorSelect.attr('id') === id) {
_onFloorChange();
} else if (_zoneSelect && _zoneSelect.attr('id') === id) {
_onZoneChange();
}
EventDispatcher.Dispatch('Main.Controls.Change', _this, {
caller: id
});
};
// Public Methods
this.GetDateRange = function () {
return _selects.GetSelectValue(_dateRangeSelect);
};
this.GetDateRangeKey = function () {
if (_dateRangeSelect) {
var selected = _dateRangeSelect.find('option:selected');
if (selected.length) {
return selected.attr("data-key") || "";
}
}
return "";
};
this.GetVenue = function () {
return _selects.GetSelectValue(_venueSelect);
};
this.SetVenue = function (value) {
_selects.SetSelectValue(_venueSelect, value);
}
this.GetFloor = function () {
return _selects.GetSelectValue(_floorSelect);
};
this.SetFloor = function (value) {
_selects.SetSelectValue(_floorSelect, value);
}
this.GetZone = function () {
return _selects.GetSelectValue(_zoneSelect);
};
this.SetZone = function (value) {
_selects.SetSelectValue(_zoneSelect, value);
}
this.GetData = function () {
return {
dateRange: {
date: this.GetDateRange(),
key: this.GetDateRangeKey()
},
venue: this.GetVenue(),
floor: this.GetFloor(),
zone: this.GetZone()
};
};
// Init
_construct();
};
return MainControlsClass;
});
The function that determines which visual to display is close to the top: _getChartDisplayDiv:
var _getChartDisplayDiv = function (venueId) {
var path = window.location.pathname,
pathArray = path.split("/"),
page = pathArray[pathArray.length - 1];
_this.Load(venueId);
console.log('Controls 40, sPot Name = ', _this.spotName);
if (page === 'heatmap') {
if (venueId === 8 || venueId === 354) {
//make the bubble div visible
$("#heatmap-bubble").show();
//make the map div invisible
$("#heatmap-map").hide();
} else {
//make the map div visible
$("#heatmap-map").show();
//make the bubble div invisible
$("#heatmap-bubble").hide();
}
}
}
When I am able to pass the "spot" information to it or a variable that it uses, it should look like this:
var _getChartDisplayDiv = function (venueId) {
var path = window.location.pathname,
pathArray = path.split("/"),
page = pathArray[pathArray.length - 1];
_this.Load(venueId);
console.log('Controls 40, sPot Name = ', _this.spotName);
if (page === 'heatmap') {
if (_this.spotName === 'local' ) {
//make the bubble div visible
$("#heatmap-bubble").show();
//make the map div invisible
$("#heatmap-map").hide();
} else {
//make the map div visible
$("#heatmap-map").show();
//make the bubble div invisible
$("#heatmap-bubble").hide();
}
}
}
My ajax call is here:
this.Load = function (venueId) {
console.log("Controls 66, Venue Id sent = ", venueId);
if (_xhr) {
_xhr.abort();
_xhr = null;
}
_this.SetLoading(true);
_xhr = $.ajax({
url: $("meta[name='root']").attr("content") + '/app/heatmap/spot',
type: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
venue_id: venueId
},
dataType: 'JSON',
async: true,
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
_this.SetLoading(false);
},
success: function (response) {
_this.SetLoading(false);
console.log("Controls 90, Response of ajax call = ", response);
_this.Update(response);
}
});
};
This successfully gets the right spot, but I have been unable to pass it to a variable I can use. I think I am getting mixed up between private and public variables. I tried to use the 'this.Update' function to pass the setting to the public 'this.spotName' variable, but that comes up null. I have also tried to simply return the result of the ajax call, but I get a "not a function" error. How can I make the result of the ajax call available to my '_getChartDisplayDiv' function?
Your problem is that you are trying to read the value of _this.spotName before it is assigned. Let us walk through the steps that happen.
When you call _getChartDisplayDiv(value), the _getChartDisplayDiv function first calls _this.Load(venueId). Load, in turn, submits an ajax request with a success callback, reproduced in abbreviated form below:
this.Load = function (venueId) {
// ...
_this.SetLoading(true);
_xhr = $.ajax({
...
success: function (response) {
_this.SetLoading(false);
console.log("Controls 90, Response of ajax call = ", response);
_this.Update(response);
}
});
};
When the response arrives, the success callback will be invoked, which in turn will call _this.Update, which will set the variable you are after. The syntax you used for this purpose is correct. However!
"When the response arrives" happens to be an unpredictable event in the future. It might be after 10 milliseconds, it might take 2 seconds, or the request might time out altogether. Even 10 milliseconds is already an eternity, compared to the time it takes your browser to execute all other code in your script. You can be quite sure that by the time $.ajax returns, the success callback has not run yet.
When you pass a callback (success) to a function ($.ajax) and the callback is not run before the function returns, this is called an asynchronous callback, "async" for short. When a callback might be invoked async, it is important for the function to guarantee that it always runs async, because this type of situation needs to be handled in an entirely different way from when the callback is invoked synchronously (i.e., before the function returns). You can read more about the technicalities in this blogpost. So this is exactly what $.ajax guarantees: it will never invoke the success (or error) callback before it returns, even in the hypothetical situation that the response would arrive fast enough.
Right after $.ajax returns, your Load function returns, at which point your _getChartDisplayDiv function continues to execute. Almost immediately after that, you intend to read _this.spotName. $.ajax has already returned, so you might hope that at this point, the success callback has already been invoked.
Unfortunately for you, async callbacks are more stubborn than that. Not only does an async callback not run until the function to which you pass it returns; it does not run until any currently executing function returns. Besides $.ajax, Load needs to return, _getChartDisplayDiv needs to return, any function that was calling _getChartDisplayDiv needs to return, and so forth. The entire call stack needs to unwind. Only then (and when the response actually arrives, which is likely to be many milliseconds later) will the success callback be invoked. This game rule is called the event loop in JavaScript.
The solution is simpler than you might expect: you just need to invert the order of control. Rather than trying to force the data out of a request when you want to update the chart, you can update the chart when the response arrives, and rather than trying to update the chart directly, you can just trigger the request. Specifically in your case, you just need to make three changes:
In the places where you currently call _getChartDisplayDiv, call _this.Load instead.
Remove the line that calls _this.Load inside the _getChartDisplayDiv function.
At the end of the success handler, add a line that calls _getChartDisplayDiv.
Incidentally, using a proper application framework will make it much easier to manage this kind of thing. In your case, I recommend trying Backbone; it builds on top of Underscore and jQuery and it is unopinionated, so you can gradually adopt it without having to radically change the way you work.
I am not familiar with underscore.js. For jQuery you have two options, which you can use as an inspiration for your case. Untested code:
1. Callback function
You provide a callback function:
$('.mydiv').myPlugin({ // Pass options Object to plugin
venuId: '123',
getType: function(type) {
console.log(type); // Example accessing internal data
}
});
Your plugin code:
(function( $ ) {
$.fn.myPlugin = function(opt) {
this.filter('div').each(function() {
const settings = $.extend({
namespace: 'myPlugin',
type: 'local'
getType: function() {},
// otherSettings: 'as needed',
}, opt);
// plugin code here...
if(typeof settings.getType === 'function') {
settings.getType(settings.type);
}
});
return this;
};
}( jQuery ));
2. Plugin method
You define plugin method(s) that can be called:
$('#mydiv').myPlugin({ // Pass options Object to plugin
venuId: '123'
});
console.log($('#mydiv').myPlaugin('getType'));
Your plugin code:
(function( $ ) {
$.fn.myPlugin = function(opt) {
this.filter('div').each(function() {
const settings = $.extend({
namespace: 'myPlugin',
type: 'local',
// otherSettings: 'as needed',
}, opt);
this.getType = function() {
return settings.type;
}
let firstArg = arguments[0];
if(typeof firstArg === 'string') {
let func = this[firstArg];
if(typeof func === 'function') {
var args = [];
for(var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
return func.apply(this, args);
}
} else {
// plugin init code here...
}
});
return this;
};
}( jQuery ));
I have struggled on this topic for a quite a bit of time and it just won't click!
I need to pass the object to the &.post (WordPress processed AJAX) request, but I cannot figure out how to do it correctly using regular variables; instead, I'm forced to call $(document) and iterate over it's DOM elements (very ugly and slow).
How would I correct this code so I'm able to pass the title variable all the way to the post data, instead using $(document).find('#sections_title').val() request?
Please explain how to do this properly.
(function ($) {
var title = $('#sections_title');
var timeout = 2000;
var delay = (function () {
var timer = 0;
return function (callback, ms) {
clearTimeout(timer);
timer = setTimeout(callback, ms);
};
})();
title.keyup(function () {
// i would like to have variable here, that grabs the $(this).val()
// and use this variable to pass to the data
// var value = .......
delay(function () {
$.post(
ajaxurl,
{
'action': 'add_foobar',
'data': $(document).find('#sections_title').val()
// instead I would like:
// 'data': value
},
function(response){
alert('The server responded: ' + response);
}
);
}, timeout);
})();
})(jQuery);
This should be easiest way for your case.
Bind the val as object to callback. Then cast this to string when you need to use it.
delay(function () {
$.post(
ajaxurl,
{
'action': 'add_foobar',
'data': String(this)
// instead I would like:
// 'data': value
},
function(response){
alert('The server responded: ' + response);
}
);
}.bind(Object($(this).val())), timeout);
Or here is the complete code
(function ($) {
var title = $('#sections_title');
var timeout = 2000;
var delay = (function () {
var timer = 0;
return function (callback, ms) {
clearTimeout(timer);
timer = setTimeout(callback, ms);
};
})();
title.keyup(function () {
// i would like to have variable here, that grabs the $(this).val()
// and use this variable to pass to the data
// var value = .......
delay(function () {
$.post(
ajaxurl,
{
'action': 'add_foobar',
'data': String(this)
// instead I would like:
// 'data': value
},
function(response){
alert('The server responded: ' + response);
}
);
}.bind(Object($(this).val())), timeout);
})();
})(jQuery);
You can set it explicitly on the window object. In the browser, the global object is the same as the window object, except for certain environments like node.js.
(function($) {
window.title = $('#sections_title');
var timeout = 2000;
var delay = (function() {
var timer = 0;
return function(callback, ms) {
clearTimeout(timer);
timer = setTimeout(callback, ms);
};
})();
title.keyup(function() {
delay(function() {
$.post(
ajaxurl, {
'action': 'add_foobar',
'data': window.title
},
function(response) {
alert('The server responded: ' + response);
}
);
}, timeout);
})();
})(jQuery);
Have a NodeJS process that reaches out to a webservice for something called Kudos. These kudos are sent from one person to another person/or group of people. What I'm trying to do is create one message that has the following:
Kudos from {poster} to {receiver/s}
{Kudos Message}
Currently I have the process working correctly for poster to one receiver. I am struggling with making it work with getting the multiple names of the receivers.
The problem stems from the fact that the section where it returns the users receiving the kudos, it only provides the user id. So I need to make another call to obtain the user's name. I can easily get the promises to work for the one user, but I can seem to get the multiple user properly.
The JSON data that contains the multiple users looks something like this:
"notes_user": [
{
"id": "1060",
"note_id": "795",
"user_id": "411"
},
{
"id": "1061",
"note_id": "795",
"user_id": "250"
},
{
"id": "1062",
"note_id": "795",
"user_id": "321"
}
],
Here is the function that does the majority of the work:
getMaxId returns a database index of that highest kudos currently processed, and getKudos just returns the json dataset of "kudos".
function promisifiedKudos() {
var maxid;
var newmaxid;
Promise.all([getMaxId(), getKudos()])
.then(function(results) {
maxid = results[0];
var kudos = results[1];
newmaxid = kudos[0].id;
return kudos.filter(function(kudo) {
if (maxid < kudo.id) {
return kudo;
}
})
})
.each(function(kudo) {
return getTribeUserName(kudo);
})
.then(function(results) {
return results.map(function(kudo) {
var message = "Kudos from " + kudo.poster.full_name + " to " + kudo.kudo_receiver_full_name + "\r\n";
message += "\r\n";
return message += entities.decode(striptags(kudo.note));
})
})
.each(function(message) {
return postStatus(message);
})
.then(function() {
var tribehr = db.get('tribehr');
console.log(new Date().toString() + ":Max ID:" + newmaxid);
tribehr.update({ endpoint: "kudos" }, { $set: { id: newmaxid } });
})
.done(function(errors) {
console.log("Run Complete!");
return "Done";
});
}
The helper function getTribeUserName()
function getTribeUserName(kudo) {
return new Promise(function(fulfill, reject) {
var id = kudo.notes_user[0].user_id;
var options = {
url: "https://APIURL.com/users/" + id + ".json",
method: "GET",
headers: {
"Authorization": "Basic " + new Buffer("AUTHCODE" + AUTHKEY).toString('base64')
}
}
request.getAsync(options).then(function(response) {
if (response) {
var data = JSON.parse(response.body)
kudo.kudo_receiver_full_name = data.User.full_name;
fulfill(kudo);
} else {
reject("Get Tribe User Name Failed");
}
});
});
}
I've tried adding a helper function that calls the getTribeUserName() that looks like this:
function getTribeUsers(kudo) {
return new Promise(function(fulfill, reject) {
kudo.notes_user.map(function(user) {
//Make calls to a getTribeUserName
})
});
}
But the outcome is that the user names are undefined when the finalized message is put together.
Any pointers in how to use promises better would be extremely helpful. This is really my first stab with them and I hope I'm heading in the right direction. I know I need to add the error checking in, but currently I'm just trying to get the process working for multiple users.
if you need to use the result of a promise passed as parameter of the resolve function then you can catch it in the then onFulfilled callback.
If you need to pass some data obtained within a then method of a chain to another then you just have to return it and catch it through the onFulfilled callback of the following then.
object.somePromise().then(function(param){
var data = someFunction();
return data;
}).then(function(param){
//param holds the value of data returned by the previous then
console.log(param);
});
If it's a matter of getting multiple TribeUserNames asynchronously, then you need somehow to aggregate promises returned by multiple calls to getTribeUserNames().
You could write Promise.all(array.map(mapper)) but Bluebird provides the more convenient Promise.map(array, mapper).
Bluebird's .spread() is also convenient, for referencing maxid and kudos.
Here it is in as simple a form as I can manage :
function promisifiedKudos() {
return Promise.all([getMaxId(), getKudos()])
.spread(function(maxid, kudos) {
var newmaxid = kudos[0].id;
// The following line filters (synchronously), adds TribeUserNames (asynchronously), and delivers an array of processed kudos to the next .then().
return Promise.map(kudos.filter((kudo) => kudo.id > maxid), getTribeUserName)
.then(function(filteredKudosWithTribeUserNames) { // in practice, shorten arg name to eg `kudos_`
return Promise.map(filteredKudosWithTribeUserNames, function(kudo) {
return postStatus("Kudos from " + kudo.poster.full_name + " to " + kudo.kudo_receiver_full_name + "\r\n\r\n" + entities.decode(striptags(kudo.note)));
});
})
.then(function() {
var tribehr = db.get('tribehr');
console.log(new Date().toString() + ":Max ID:" + newmaxid);
return tribehr.update({ endpoint: 'kudos' }, { $set: { 'id': newmaxid } });
});
})
.then(function() {
console.log('Run Complete!');
}).catch(function(error) {
console.log(error);
throw error;
});
}
getTribeUserName() needs to return a promise, and can be written as follows :
function getTribeUserName(kudo) {
var options = {
'url': 'https://APIURL.com/users/' + kudo.notes_user[0].user_id + '.json',
'method': 'GET',
'headers': {
'Authorization': 'Basic ' + new Buffer('AUTHCODE' + AUTHKEY).toString('base64')
}
}
return request.getAsync(options).then(function(response) {
// ^^^^^^
if(response) {
kudo.kudo_receiver_full_name = JSON.parse(response.body).User.full_name;
} else {
throw new Error(); // to be caught immediately below.
}
return kudo;
}).catch(function(error) { // error resistance
kudo.kudo_receiver_full_name = 'unknown';
return kudo;
});
}
Further notes:
By nesting Promise.map(...).then(...).then(...) in the .spread() callback, newmaxid remains available through closure, avoiding the need for an ugly outer var.
Promise.map() is used a second time on the assumption that postStatus() is asynchronous. If that's not so, the code will still work, though it could be written slightly differently.
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 7 years ago.
with reference to this answer How do I return the response from an asynchronous call? how could i implement this one in my secanrio.
I am trying to do caching with use of sqlite using cordova sqlite plugin but my problem is my controller executes before my factory completes its execution. I am pasting my controller code and my factory code. I had put alert for knowing the sequence of execution. My ideal alert sequence is 1,2,3,4,5,6 but when i am executing the code i am getting alert sequence like 1,5,6,2,3,4. I am pasting my controller and factory code below.
angular.module('foo').controller('PriceListController',["$scope","$http","$stateParams","CURD","$q","DB", function($scope,$http,$stateParams,CURD,$q,DB) {
$scope.brand_id=Number($stateParams.id);
$scope.process=true;
$scope.pricelists=[];
$scope.getBrandDocs= function(){
var parameters=new Array(2);
parameters[0]=$scope.brand_id
parameters[1]=1;
CURD.exc_query("select * from brand_docs where brand_id=? AND type=?",parameters)
.then(function(price_lists) {
alert("2");
$scope.inter_pricelist=price_lists;
console.log("Records from query call:"+JSON.stringify( $scope.inter_pricelist));
$scope.deferred = $q.defer();
if ($scope.inter_pricelist){
console.log("Found data inside cache", JSON.stringify($scope.inter_pricelist));
$scope.deferred.resolve($scope.inter_pricelist);
// alert(JSON.stringify( $scope.inter_pricelist));
alert("3");
}
else{
$http.get('http://foo.com?brand='+ $scope.brand_id +'&type=price_list')
.success(function(data) {
//alert("http call");
console.log("Received data via HTTP",JSON.stringify(data));
angular.forEach(data.data.info, function(value, key) {
var sql = "INSERT OR REPLACE INTO brand_docs(id, brand_id, name, file_url,type) VALUES (?, ?, ?, ?, ?)";
var parameters=new Array(5);
parameters[0]=value.id;
parameters[1]=value.brand_id;
parameters[2]=value.name;
parameters[3]=value.file_url;
parameters[4]=value.type;
var result=DB.query(sql,parameters);
});
$scope.deferred.resolve(data.data.info);
})
.error(function() {
console.log("Error while making HTTP call.");
$scope.deferred.reject();
});
}
return ($scope.deferred.promise).then(function(pricelists){
alert("4");
return pricelists;
},function(){});
},function(){});
alert("5");
};
$scope.pricelists=$scope.getBrandDocs();
alert("6");
}]);
// This is factory code i am pasting
angular.module('foo').factory('DB', function($q, DB_CONFIG,$cordovaSQLite) {
var self = this;
self.db = null;
self.init = function() {
try{
self.db = window.sqlitePlugin.openDatabase(DB_CONFIG.name, '1.0', 'database', -1);
angular.forEach(DB_CONFIG.tables, function(table) {
var columns = [];
angular.forEach(table.columns, function(column) {
columns.push(column.name + ' ' + column.type);
});
var query = 'CREATE TABLE IF NOT EXISTS ' + table.name + ' (' + columns.join(',') + ')';
self.query(query);
console.log('Table ' + table.name + ' initialized');
});
}
catch(err){
}
};
self.query = function(query, bindings) {
bindings = typeof bindings !== 'undefined' ? bindings : [];
console.log("Query:"+query+" bindings:"+ bindings);
var deferred = $q.defer();
self.db.transaction(function(transaction) {
transaction.executeSql(query, bindings, function(transaction, result) {
console.log("Query sucessfull :"+ query);
console.log("Result of Query:"+ JSON.stringify(result));
deferred.resolve(result);
}, function(transaction, error) {
console.log("Error:"+ JSON.stringify(error));
deferred.reject(error);
});
});
return deferred.promise;
};
self.fetchAll = function(result) {
var output = [];
for (var i = 0; i < result.rows.length; i++) {
output.push(result.rows.item(i));
}
//console.log("RECORDS:" +JSON.stringify(output));
return output;
};
self.fetch = function(result) {
return result.rows.item(0);
};
return self;
})
.factory('CURD', function(DB) {
var self = this;
self.all = function(table_name) {
return DB.query('SELECT * FROM '+table_name)
.then(function(result){
return DB.fetchAll(result);
});
};
self.exc_query = function(query,parameters) {
return DB.query(query,parameters)
.then(function(result){
return DB.fetchAll(result);
});
};
self.getById = function(id,table_name) {
return DB.query('SELECT * FROM '+table_name +' WHERE id = ?', [id])
.then(function(result){
return DB.fetch(result);
});
};
return self;
});
The query completes asynchronously. By that I mean that when you call CURD.exec_query(), the query is queued and as soon as it is queued, your method continues to execute at "return ($scope.deferred.promise).then(function(pricelists){". That's why 4, 5, 6 show up before 2 and 3. Once the query completes, again, asynchronously, the ".then()" method is called, which is when 2 and 3 are alerted.
Note that getBrandDocs() is going to return BEFORE then .then() method is called. What you could do is have your .then() method emit an event that is asynchronously picked up in the calling code which would in turn execute the 4, 5, and 6 steps.
Hi I was creating a JS module that I want to use in the way:
var dataCollection = new dataCollectionSetup();
var collectedData = dataCollection.getMeasures(2);
My issue is that I want to get data from a Restfull Api and that will be asynchronously. I create this module but I am stuck on how create the promise inside the function or something that allow me call the getMeausre in the way that I showed or like this dataCollection.getMeasures(2).then(UpdatecoolectedData(res)).
var dataCollectionSetup = function () {
var getMeasuresByTrainingDomainId = function (tdId)
{
var jsonResponse;
fnSuccess = function (data, status, request) {
jsonResponse = data;
return jsonResponse
};
fnError = function () {
alert("Error getting Maasures by Training Domain");
}
$.ajax({
type: "GET",
url: "/datacollection/Measures/" + timelineId,
complete: fnSuccess,
error: fnError
});
}
var getMetrics = function () {
var result = CallApiForMetrics()
return result;
}
return {
getMeasures: getMeasuresByTrainingDomainId,
getNetric: getMetrics
}
}
Thank you
Just make the following change to your function getMeasuresByTrainingDomainId
return $.ajax({
type: "GET",
url: "/datacollection/Measures/" + timelineId,
complete: fnSuccess,
error: fnError
});
If you look the jquery documentation for the ajax method, implement the Promise interface, and you can use it like this:
var dataCollection = dataCollectionSetup();
dataCollection.getMeasures(2).then(function(){/*your coode*/})