Async Javascript functions, not returning values - javascript

I'm having an issue that is burning my head. Basically this is the scenario:
I have a callController() function, which just simple use the jQuery.load() method to load a controller, after loaded, I can play with the returning parameters.
Now... in one of my controllers there is a validation rule I need to check in order to to allow the user to execute certain functionality, however, I create a function like this:
function myValRule() {
var val = callController('blablabla'),
response = "";
val.done(function(data) {
//my business logic
response = something;
}
return response;
}
As you imagine, response, even if It has a value, it returns undefined, so I set a timeout and console.log() it and now it has a value, but I cannot make to return this value even if I put the return into the setTimeout(). So basically when the method call this function to validate it finds it empty.
Can you point me on some direction to solve this issue?

Remember this is asynchronous! The return response; is long gone by the time .done() is actually called.
if you need the value returned in this fashion, look at either supplying a callback function in your myValRule function (that would be called from within .done()) or maybe look at using the $.Deferred api so you can mimic what callController(...) is doing.
Here's an example of both scenarios (with example call to myValRule):
Callback argument
function myValRule(callback){
var val = callController('blablabla'),
val.done(function(data){
var response = /* something */;
callback(response);
});
}
myValRule(function(response){
// here you have response
});
Using $.Deferred
(I assume it's jQuery since there's a .done call)
function myValRule(){
var df = $.Deferred(),
val = callController('blablabla');
val.done(function(data){
var response = /*something */;
df.resolve(response);
}).fail(df.reject);
return df.promise();
}
myValRule().done(function(response){
// here you have response
});

Related

How to block calling function while multiple asynchronous calls are executed, then contiune processing the collected data?

I would like to process an XML file in JavaScript, read every sheet asynchronously (because oviously it would be faster), and return the final result. I have the parser functions, I got stuck on the part when I would like to wait for every async call to finish, then return the result. A callback isn't the best practice for me in this case, because this function would be called at the beggining of another module, where I wouldn't like to put the rest of the code (let's say for example 500 lines) in a callback.
I have tried many approaches, the last one looks like the one below. But it seems to result in an infinite loop. All the other attempts returned undefined value, because the caller function ended before async calls would finish, and the accumulator haven't got any value.
1. To collect every async call's result, I used promise.
function getAllSheets(callback) {
accumulator = {};
promise.all([
processOneSheetAsync(workbook, sheetname1, ..., callback(data) {
accumulator.one = data;
}),
processOneSheetAsync(workbook, sheetname2, ..., callback(data) {
acc.two = data;
}),
/* some more function calls */
]).then(function(result) {
callback(acc);
});
}
Note: processOneSheetAsync() is function that returns a new Promise and calls the parser(), as expected.
2. Then I tried to wait for it's callback in a function like this:
function getResult() {
var result;
var callback = false;
getAllSheets(function(data) {
result = data;
callback = true;
});
while(!callback){};
return result;
}
I thought this would block the getResult() function until the variable callback is set to true. However, it seems that the while loop never detects the change of callback's.
Is there any good approach to solve this problem?
You should really use a callback. I'd probably do something like:
var myResults=[];
var waiting=0;
data.forEach(function(datum){
waiting++;
asyncCall(datum, callback);
}
function callback(result){
myResults.push(result);
waiting--;
if (waiting == 0){
doStuffOn(myResults);
}
}
However, if you're still against everything that is good in the world, you should check this, found here

Populating an empty object from data needed inside a function?

Just wanting to populate myObj with data from within the get request. According to my console.log(myObj) everythings seems to be their but can't access. I'm sure their is a simple answer to this. Thanks.
function getInfo() {
var myObj = {};
$.get("http://ipinfo.io", function(response) {
myObj.city = response.city;
myObj.region = response.region;
}, "jsonp");
console.log(myObj.city); //undefined
return myObj;
}
var myStuff = getInfo(); //Object {} just as I expected obviously.
console.log(myStuff) // Object shows all my key value pairs but -->
console.log(myStuff.city); // undefined why?
It is more suitable to use promises. You can try something like this:
function getInfo() {
var dfd = $.Deferred();
$.get("http://ipinfo.io", function(response) {
myObj.city = response.city;
myObj.region = response.region;
dfd.resolve(myObj);
}, "jsonp");
// Return the Promise so caller can't change the Deferred
return dfd.promise();
}
getInfo().done(function(myStuff) {
console.log(myStuff);
console.log(myStuff.city);
});
With promises the code is one idea elegant.
$.get is an asynchronous operation which means that it returns immediately (without running the code in your callback function first). The callback will only be executed at some point in the future when the GET request is finished. In the meantime the code following the $.get call will be executed.
What you're seeing is a result of a specific execution order in which the GET request happened not to finish before the following code was executed. In other words you have a race condition.
In theory , if the GET request finished almost instantly, it would be possible for the callback to execute before your log statements and therefore not print undefined. This is highly unlikely, however, as the time it takes to execute log instructions are much(!) less than what it takes to execute the GET request.
To fix the issue you need to ensure that you're log statements are always run only after the GET request is finished. The idiomatic way to do this in JS is with callbacks - e.g. pass a function which contains the code to execute and call it from inside the response handler:
function getInfo(callback) {
var myObj = {};
$.get("http://ipinfo.io", function(response) {
myObj.city = response.city;
myObj.region = response.region;
callback(myObj);
}, "jsonp");
console.log(myObj.city); //undefined
}
getInfo(function(myStuff) {
console.log(myStuff)
console.log(myStuff.city);
});
It return undefine because the request didn't response yet then the function returned the value. Your request was asynchronous meaning your function wont wait for your get request. it would return what ever myObj is.
You can try something like
function getInfo(callback){
$.get(..., function(res){
var myObj = …;
callback(myObj);
}
}
when ever your request is finished, it would call the callback function and pass in the myObj. so you need to pass in a anonymous function that expect 1 argument to pass in the myObj from the response
getInfo(function(data){
//data should be myObj
});

JavaScript behavior and code check

I created this plunker.
The gist is this code:
var myObj = function (){
return {
getThis: function (callback) {
return callback("this");
}
}
}
var myDidSome = function () {
return {
DoSome: function (text) {
return text + " is cool";
}
};
};
var subObj = function (objG, didSome) {
var txt = objG.getThis(function (text) {
return didSome.DoSome(text);
});
console.log(txt);
}
new subObj(new myObj, new myDidSome);
So to deconstruct this code, view myObj and myDidSome as services (angular ones). I'm most interested in the myObj.getThis, this is supposed to mimick some $http response. So when its finished call my callback function passing in the parameters obtained from $http.
The thing I keep thinking about though is $http is async. Will my txt property in the subOj (think of this as a controller) class be populated correctly when the data gets back, I assume it will be undefined till then?
In my scenario this is completely synchronous, which obviously works.
So, I would like to know whether async will be an issue here? And whether doing this practice of propagating the return of an object is a good one, or doesn't matter, or is bad and I shouldn't do it?
You cannot use return like that with an asynchronous method. You have to do everything within the callback. In your example, the result of didSome.DoSome(text) will never be assigned to txt, not even after the callback has run.
For a simple case like this where you're logging the response, doing it in the callback is an acceptable approach. If you need to do something more complex, however, you should look into promises via $q. These provide a much nicer way to rewrite a long chain of callbacks into a more linear flow.

What to do when passing a parameter into a callback function conflicts with the callback's caller

I am using an ID3 reader script to retrieve data from audio files. The basic usage is:
ID3.loadTags(file,function()
{
var attr = ID3.getAllTags(file).attribute;
});
where the anonymous function is a callback function. This is just to provide context, however, I'm not at all sure the problem I'm having is specific to that particular script.
Typically, inside the callback function, you can extract the info you need and then use the DOM to set the innerHTML attribute of whatever element to equal the info you extracted.
Sometimes you're extracting a bunch of info and connecting all together in a long string though, and I'm trying to compartmentalize it a bit more so that my calling function will be a little cleaner. What I want to do is this:
function callingFunction()
{
var file = "whatever.mp3";
var info = getInfo(file);
}
function calledFunction(file)
{
var info = {data: 0};
ID3.loadTags(file, function(passedVar)
{
var dataobj = ID3.getAllTags(file);
passedVar.data = dataobj.title+etc+dataobj.album+....(it can get long);
}(info));
return info;
}
An object with an attribute is created because it's one of the only ways to simulate passing by reference in JS - pass the object into the callback function, assign the appropriate data to the attribute in the object, and then at the end of calledFunction, return the object to callingFunction.
It doesn't work though. Now, in that code above, if I said passedVar.data = "teststring" instead of assigning it data from dataobj, that would work, so the passing of the object into the callback function is working correctly. But if the object is assigned data from the data object that the ID3 function returns, it doesn't work. It comes back undefined, and furthermore, Chrome's JS debugger says that the object the ID3 function returns is null. This is further confirmed when I do this:
function calledFunction(file)
{
var info = {data: 0};
ID3.loadTags(file, function(passedVar)
{
alert(ID3.getAllTags(file).(any attribute));
}(info));
return info;
}
and no alert box comes up at all. But if remove the parameter being passed into the callback function in the code above, and leave everything else the same, that alert box comes up like it's supposed to.
So, to sum up, when I pass a parameter into the callback function, for some reason, another function of the object that's calling the callback function ceases to work correctly.
Is it possible that passing a parameter into the callback function is somehow conflicting with, or overriding, whatever the ID3.loadTags function is passing into the callback function, and that's why the getAllTags function is failing? Because for some reason when a parameter is passed into the callback function, the getAllTags function is no longer getting all of the information it needs to run properly? That's the only explanation I can think of.
If that's the case, is there a way to work around it? And if that's not what's going on, what is going on?
I have figured one solution, but I feel like it's hacky. I basically create a third function that gets called from the callback function(which itself receives no parameters), that takes as a parameter the object that the getAllTags method returns, extracts data from that object, and assigns it to global variables that other functions can access. So, this:
var globalVar;
function calledFunction(file)
{
//var info = {data: 0};
ID3.loadTags(file, function()
{
thirdFunction(ID3.getAllTags(file));
});
//return info;
}
function thirdFunction(dataobj)
{
globalVar = dataobj.title+etc;
}
But I don't really like that solution, I feel like it goes against the spirit of compartmentalization that I was after in the first place with this.
I'd appreciate any help.
The reason this doesn't work:
function calledFunction(file)
{
var info = {data: 0};
ID3.loadTags(file, function(passedVar)
{
var dataobj = ID3.getAllTags(file);
passedVar.data = dataobj.title+etc+dataobj.album+....(it can get long);
} (info));
// ^^^^^^ --- calls the function immediately
return info;
}
...is that you're calling your anonymous function and passing the result of that call (undefined) into ID3.loadTags. You're not passing a function into it anymore at all.
But the fundamental problem is that you're trying to use the data object before loadTags calls its callback and puts the data on the object.
I suggest that, since its output depends on an asynchronous operation, rather than relying on function return values, you change calledFunction to take a callback function. Here's what it should look like:
function callingFunction() {
getInfo('whatever.mp3', function(info) { // pass a callback function
// info.data is here now
});
}
function getInfo(file, cb) { // accept a callback function as the 2nd param
ID3.loadTags(file, function() {
var tags = ID3.getAllTags(file);
// once your async operation is done, call cb and pass back the return value
cb({
data: tags.title+etc+tags.album+....(it can get long);
});
});
}
This approach avoids problems you were trying to solve by using an object you could pass by reference, and it ensures you only move on once your asynchronous operation (ID3.getAllTags) is complete.

Join 2 'threads' in javascript

If I have an ajax call off fetching (with a callback) and then some other code running in the meantime. How can I have a third function that will be called when both of the first 2 are done. I'm sure it is easy with polling (setTimeout and then check some variables) but I'd rather a callback.
Is it possible?
You could just give the same callback to both your AJAX call and your other code running in the meantime, use a variable to track their combined progress, then link them to a callback like below:
// Each time you start a call, increment this by one
var counter = 0;
var callback = function() {
counter--;
if (counter == 0) {
// Execute code you wanted to do once both threads are finished.
}
}
Daniel's solution is the proper one. I took it and added some extra code so you don't have to think too much ;)
function createNotifier() {
var counter = 2;
return function() {
if (--counter == 0) {
// do stuff
}
};
}
var notify = createNotifier();
var later = function() {
var done = false;
// do stuff and set done to true if you're done
if (done) {
notify();
}
};
function doAjaxCall(notify) {
var ajaxCallback = function() {
// Respond to the AJAX callback here
// Notify that the Ajax callback is done
notify();
};
// Here you perform the AJAX call action
}
setInterval(later, 200);
doAjaxCall(notify);
The best approach to this is to take advantage of the fact that functions are first-order objects in JavaScript. Therefore you can assign them to variables and invoke them through the variable, changing the function that the variable refers to as needed.
For example:
function firstCallback() {
// the first thing has happened
// so when the next thing happens, we want to do stuff
callback = secondCallback;
}
function secondCallback() {
// do stuff now both things have happened
}
var callback = firstCallback;
If both your pieces of code now use the variable to call the function:
callback();
then whichever one executes first will call the firstCallback, which changes the variable to point to the secondCallback, and so that will be called by whichever executes second.
However your phrasing of the question implies that this may all be unnecessary, as it sounds like you are making an Ajax request and then continuing processing. As JavaScript interpreters are single-threaded, the Ajax callback will never be executed until the main body of code that made the request has finished executing anyway, even if that is long after the response has been received.
In case that isn't your situation, I've created a working example on my site; view the source to see the code (just before the </body> tag). It makes a request which is delayed by the server for a couple of seconds, then a request which receives an immediate response. The second request's response is handled by one function, and the first request's response is later handled by a different function, as the request that received a response first has changed the callback variable to refer to the second function.
You are talking about a thing called deferred in javascript as #Chris Conway mentioned above. Similarly jQuery also has Deferred since v1.5.
Check these Deferred.when() or deferred.done()
Don't forget to check jQuery doc.
But to give you some idea here is what I am copying from that site.
$.when($.ajax("/page1.php"), $.ajax("/page2.php")).done(function(a1, a2){
/* a1 and a2 are arguments resolved for the
page1 and page2 ajax requests, respectively */
var jqXHR = a1[2]; /* arguments are [ "success", statusText, jqXHR ] */
if ( /Whip It/.test(jqXHR.responseText) ) {
alert("First page has 'Whip It' somewhere.");
}
});
//Using deferred.then()
$.when($.ajax("/page1.php"), $.ajax("/page2.php"))
.then(myFunc, myFailure);
Something like this (schematic):
registerThread() {
counter++;
}
unregisterThread() {
if (--counter == 0) fireEvent('some_user_event');
}
eventHandler_for_some_user_event() {
do_stuff();
}
You can do this easily with Google's Closure library, specifically goog.async.Deferred:
// Deferred is a container for an incomplete computation.
var ajaxFinished = goog.async.Deferred();
// ajaxCall is the asynchronous function we're calling.
ajaxCall( //args...,
function() { // callback
// Process the results...
ajaxFinished.callback(); // Signal completion
}
);
// Do other stuff...
// Wait for the callback completion before proceeding
goog.async.when(ajaxFinished, function() {
// Do the rest of the stuff...
});
You can join multiple asynchronous computations using awaitDeferred, chainDeferred, or goog.async.DeferredList.

Categories

Resources