I have a function that accepts a callback function where I pass the data back in. Can this converted to a deferred object for better practice?
Here is what I got:
var chapters;
var getChapters = function (fnLoad) {
//CACHE DATA IF APPLICABLE
if (!chapters) {
//CALL JSON DATA VIA AJAX
$.getJSON('/chapters.txt')
.done(function (json) {
//STORE DATA IN LOCAL STORAGE
chapters = Lawnchair(function () {
this.save(json, function (data) {
//CALL CALLBACK ON DATA
fnLoad(data);
});
});
});
} else {
//RETURN ALREADY CREATED LOCAL STORAGE
chapters.all(function (data) {
//CALL CALLBACK ON DATA
fnLoad(data);
});
}
};
Then I simply use it like this:
this.getChapters(function (data) {
console.log(data);
});
How can I use it like a promise though while maintaining the cache approach?
this.getChapters().done(function (data) {
console.log(data);
});
var chapters;
var getChapters = function (fnLoad) {
var d = new $.Deferred();
//CACHE DATA IF APPLICABLE
if (!chapters) {
//CALL JSON DATA VIA AJAX
$.getJSON('/chapters.txt')
.done(function (json) {
//STORE DATA IN LOCAL STORAGE
chapters = Lawnchair(function () {
this.save(json, function (data) {
//CALL CALLBACK ON DATA
d.resolve(data);
});
});
})
.fail(function() { d.reject(); });
} else {
//RETURN ALREADY CREATED LOCAL STORAGE
chapters.all(function (data) {
//CALL CALLBACK ON DATA
d.resolve(data);
});
}
return d.promise();
};
Relevant example
I see you have already accepted an answer, however if you take a large mental leap and store a promise of chapters instead of the chapters themselves, then the code will simplify significantly.
These days, this is probably the more generally adopted approach for a "fetch/cache" situation.
var chapters_promise;
var getChapters = function () {
//Cache data if applicable and return promise of data
if (!chapters_promise)
chapters_promise = $.getJSON('/chapters.txt').then(Lawnchair).then(this.save);
return chapters_promise;
};
What is actually promised (the chapters) will be determined by the value(s) returned by the functions Lawnchair and this.save, so you still have some work to do.
getChapters() will always return a promise, regardless of whether the data needs to be fetched or is already cached. Therefore, getChapters() can only be used with promise methods .then(), .done(), .fail() or .always(), for example :
getChapters().then(fnLoad);
You have no other way to access the chapters but that is reasonable since at any call of getChapters(), you don't know whether it will follow the $.getJSON() branch or the simple return branch, both of which return an identical promise.
Related
Function 1: Get JSON Data & Store
I am creating a script where an array of twitch channels will go through the JSON function loop to be processed and then stored using "localStorage.setItem" as temporary storage. I'm saving them in name,viewer and url.
Function 2: Sort Data
Stored data can later be used to display the information without having to use function 1 again.
Problem
The sortdata function keeps on firing before function 1 is complete. Resorting in error because the data is undefined. This error popped before the console displays all the information stored from function 1.
My code:
$(window).load(function(){
$.when(getData()).promise().done(function(){
getStoredObj();
});
});
function getData(){
var streamArray=[];
jQuery.each (channels, function (i, channel) {
channelId = channel.id;
channelUrl = channel.url;
var promise = $.ajax({
dataType: "jsonp",
url: twitchApi + channelId,
success: 1,
}).done(function ( data ) {
if (data.stream == null) {
} else {
var displayName = data.stream.channel.display_name;
var viewerCount = data.stream.viewers;
streamArray.push({name: displayName, views: viewerCount, url: channelUrl});
localStorage.setItem("storedStreamArray", JSON.stringify(streamArray));
console.log(JSON.stringify(streamArray));
}
});
});
}
function getStoredObj () {
var retrievedObject = localStorage.getItem('storedStreamArray');
var parsedObject = JSON.parse(retrievedObject);
<sorting codes here>
}
Some help here really appreciated. :)
You're calling $.when with the result of getData, but getData doesn't return anything, let alone a deferred that when can use. As a result, there's nothing to wait for and your done callback calls getStoredObj immediately.
In getData, you need to collect all the deferreds returned by your ajax calls and pass them back to the caller. That would look like:
function getData(){
return jQuery.map (channels, function (i, channel) {
return $.ajax(...).done(function ( data ) {
// Do work
});
});
}
Each iteration returns its ajax deferred, which are aggregated by map and returned to the caller. Then you can run when on the result and wait for loading to finish before you sort anything.
I want to return result directly (Normally we try to put to data and access that data yeah. but now how I want is directly). Like when we call hello() function I want to return result variable.
I try like the following, but it doesn't return yeah. How do I try?
hello: function() {
var result = "";
this.$http.get(window.location.href).success(function(data) {
result = data;
}).error(function (data, status, request) {
});
return result;
}
Looks like it doesn't return the data because the request is async. Your method sets up the request and then returns immediately, synchronously, before the request had a chance to assign any data to the result variable.
You can try to change your code slightly:
hello: function() {
var result = {};
this.$http.get(window.location.href).success(function(data) {
result.data = data;
result.ready = true;
}).error(function (data, status, request) {
});
return result;
}
That should enable you to access the data elsewhere (as result.data), but only after the async query has succeeded. The result.ready flag would tell you if and when that has happened.
In my opinion, it would definitely be better to work with promises, though, e.g. using jQuery Deferreds and promises, or ES6 promises along with a polyfill.
I am facing the following synchronization issue. I wouldn't be surprised if it has a simple solution/workaround. The BuildMenu() function is called from another block of code and it calls the CreateMenuData() which makes a request to a service which return some data. The problem is that since it is an async call to the service when the data variable is being used it is undefined. I have provided the js log that also shows my point.
BuildMenu: function () {
console.log("before call");
var data=this.CreateMenuData();
console.log("after call");
//Doing more stuff with data that fail.
}
CreateMenuData: function () {
console.log("func starts");
data = [];
dojo.forEach(config.layerlist, function (collection, colindex) {
var layersRequest = esriRequest({
url: collection.url,
handleAs: "json",
});
layersRequest.then(
function (response) {
dojo.forEach(response.records, function (value, key) {
console.log(key);
data.push(key);
});
}, function (error) {
});
});
console.log("func ends");
return data;
}
Console log writes:
before call
func starts
func ends
after call
0
1
2
3
4
FYI: using anything "dojo." is deprecated. Make sure you are pulling all the modules you need in "require".
Ken has pointed you the right direction, go through the link and get familiarized with the asynchronous requests.
However, I'd like to point out that you are not handling only one async request, but potentionally there might be more of them of which you are trying to fill the "data" with. To make sure you handle the results only when all of the requests are finished, you should use "dojo/promise/all".
CreateMenuData: function (callback) {
console.log("func starts");
requests = [];
data = [];
var scope = this;
require(["dojo/_base/lang", "dojo/base/array", "dojo/promise/all"], function(lang, array, all){
array.forEach(config.layerlist, function (collection, colindex) {
var promise = esriRequest({
url: collection.url,
handleAs: "json",
});
requests.push(promise);
});
// Now use the dojo/promise/all object
all(requests).then(function(responses){
// Check for all the responses and add whatever you need to the data object.
...
// once it's all done, apply the callback. watch the scope!
if (typeof callback == "function")
callback.apply(scope, data);
});
});
}
so now you have that method ready, call it
BuildMenu: function () {
console.log("before call");
var dataCallback = function(data){
// do whatever you need to do with the data or call other function that handles them.
}
this.CreateMenuData(dataCallback);
}
when a function returns a promise, I can call some other function after the first one did it's work:
do_stuff().then(function(){
alert('yoooo');
});
and do_stuff() looks like this:
function do_stuff(){
if(!got_the_data){
var d = $.Deferred();
$.ajax({
success: function(html){
$('#box').append(html);
$('#box').addClass('visible');
$('#box').on('transitionend webkitTransitionEnd', function(){
got_the_data = true;
d.resolve();
});
}
});
return d.promise();
}else{
// got the data, what now?
}
}
but what do I return if I already did the ajax request (result is cached) and I don't have to wait for anything? I can't return d.resolve() because the function that was attached to then() won't fire :/
and I can't return d.promise because I have to resolve the "d" somewhere
You can choose between two approaches; caching data or caching promises.
Here's two examples, both of which key on url, though any other key may be used, as appropriate - as long as it uniquely identifies each individual case.
Cache data
var dataCache = {};
function do_stuff_1(url) {
if(dataCache[url] === undefined) {
return $.ajax({
url: url
}).then(function(data) {
dataCache[url] = data;
return data;
});
} else {
return $.when(dataCache[url]);
}
}
Cache promises
var promiseCache = {};
function do_stuff_2(url) {
if(!promiseCache[url]) {
promiseCache[url] = $.ajax({
url: url
});
}
return promiseCache[url];
}
In both approaches, the function will (barring an uncaught error) return a promise, either by executing $.ajax() or by retrieving data/promise from the cache.
In most applications, there's virtually nothing to distinguish one approach from the other.
In an application where the cache is likely to grow to be large, then cache the data and avoid the overhead of caching promise wrappers.
If necessary, the cache can be pre-loaded, thus avoiding the need to fetch known data :
var dataCache = {
'/path/to/data/a': 'A',
'/path/to/data/b': 'B'
}
or
var promiseCache = {
'/path/to/data/a': $.when('A'),
'/path/to/data/b': $.when('B')
}
The simplest solution is to just return an empty already-resolved promise in the else clause:
return $.Deferred().resolve();
To avoid the Promise anti-pattern your code might be better structured thus:
function show_stuff(html) {
return $.Deferred(function(def) {
$('#box').append(html);
$('#box').addClass('visible');
$('#box').on('transitionend webkitTransitionEnd', def.resolve);
});
}
function do_stuff() {
if (got_the_data) {
return $.Deferred().resolve();
} else {
return $.ajax(...).then(show_stuff);
}
}
Note that there's no line (yet) setting got_the_data = true - you should consider whether it's really appropriate to wait until the data has been displayed to set this flag, otherwise there's nothing to prevent multiple invocations of do_stuff all resulting in new stuff getting added to #box. IMHO you would be better with a getting_the_data flag.
I have a rather simple getUser method that I'm having some trouble with. I am not deeply familiar with scopes and such in JS so this is giving me a head ache. Basically I want to fetch an object from the database and return it to the calling method:
function getUser(uid)
{
var result = null;
var userTable = tables.getTable('Users');
userTable.where({
userId: uid
}).read({
success: function (results) {
if (results.length > 0) {
result = results[0];
console.log('userid'+result.id);
}
}
});
console.log('userid-'+result.id); // undefined!!
return result;
}
Also, returning from inside the success doesn't return from getUser, but just the function defined inside. I tried "result = function(results)" as well but it stores the defined function and not the return value.
How am I supposed to do this?
I found a solution to this elsewhere. In practice (to the best of my understanding), it is not possible to do this within a JavaScript with asynchronous functions. What you need to do is create a recursion instead from inside the success handler.
Because the call to the database is asynchronous, your last two lines are executed (and hence result is undefined) before the call the database actually finishes. So you need to handle everything inside your success callback. Or, if your getUser() func is a helper, you could structure your code (without recursion) like this with a callback:
function insertOrWhateverCallingMethod()
{
var uid = 'blah';
getUser(uid,function(user) {
// Do something with the user object
});
}
function getUser(uid,callback)
{
var result = null;
var userTable = tables.getTable('Users');
userTable.where({
userId: uid
}).read({
success: function (results) {
if (results.length > 0) {
result = results[0];
console.log('userid'+result.id);
callback(result);
}
}
});
callback(null);
}
The code above assumes you're in a table script, where the tables object is available - if it's not you can pass it as a parameter to getUser().