Testing for a function that contains asynchronous code - javascript

With qUnit, I understand how to use asyncTest() if you have asynchronous code within your tests, but what if you have a function that contains asynchronous code?
In other words, the asynchronous request is not within the test, but is simply part of the code that is being tested.
Take this code for example:
function makeAjaxCall(){
$.get('/mypage', {}, function(data){
// Do something with `data`
},'json');
}
How can I call makeAjaxCall() within a test and then run tests on the data that is returned from the ajax request?

You could use the jQuery Global Ajax Event Handlers in this situation. Bind it before calling, unbind it once you finish the test, possibly via the module method in Qunit.
Something like (untested):
asyncTest(function() {
expect(1);
$(document).ajaxSuccess(function(e, xhr, settings) {
var jsonRep = JSON.parse(xhr.responseText);
// assert on jsonRep
//ok(true);
});
$(document).ajaxError(function(e) {
ok(false);
});
$(document).ajaxComplete(function() {
start();
$(document).unbind('ajaxSuccess ajaxError ajaxComplete');
});
makeAjaxCall();
});
Note that the global handlers do not have access to the parsed contents, and you have to reparse them yourself using JSON.parse. Not ideal, but I don't know of another way.

It seems that nobody cares about mockAjax, so I found something better these days: sinonJS !
Do you know the feature autoResponse of this wonderful tool named Fiddler?
sinonJS allows you to do the same on the client side, it's a kind of ajax by-pass proxy. if you are interested to see an example, let me know... so sinonJS can respond to any ajax request without accessing the server, that's kind of magic if you want to mock your client, if you want to unit test your javascript code.

Related

Understanding jQuery.Deferred in the context of jQuey.AJAX (again)

I concede that, despite hours of reading and attempting, I am fundamentally unable to grasp something about Deferred promises and asynchrony in general.
The goal on my end is real, real simple: send some data to the server, and react to the contents of the response conditionally.
The response will always be a JSON object with save and error keys:
{ "save": true, "error":false}
// or
{ "save" : false,
"error" : "The server has run off again; authorities have been notifed."}
I have tried dozens and dozens of variations from the jQuery API, from other stackexchange answers, from tutorials, etc.. The examples all seem concerned with local asynchronous activity. When I need is some ability to be made aware when the AJAX request has either finished and returned a response I can inspect and make decisions about, or else to know that it's failed. Below, I've used comments to explain what I think is happening so someone can show me where I'm failing.
I know this is a repost; I am, apprently, worse than on average at grasping this.
var postData = {"id":7, "answer":"Ever since I went to Disneyland..."};
/* when(), as I understand it, should fire an event to be
responded to by then() when it's contents have run their course */
var result = $.when(
/* here I believe I'm supposed to assert what must complete
before the when() event has fired and before any chained
functions are subsequently called */
/* this should return a jqXHR object to then(), which is,
I'd thought, a queue of functions to call, in order,
UPON COMPLETION of the asynchronous bit */
$.post("my/restful/url", postData))
.then( function() {
/* since "this" is the jqXHR object generated in the $.post()
call above, and since it's supposed to be completed by now,
it's data key should be populated by the server's response—right? */
return this.data;
});
// alas, it isn't
console.log(result.data);
// >> undefined
Most examples I can find discuss a timeout function; but this seems, as I understand, to be a failsafe put in place to arbitrarily decide when the asynchronous part is said to have failed, rather than a means of stalling for time so the request can complete. Indeed, if all we can do is just wait it out, how's that any different from a synchronous request?
I'll even take links to a new read-mes, tutorials, etc. if they cover the material in a different way, use something other than modified examples from the jQuery API, or otherwise help this drooling idiot through the asynchronous mirk; here's where I've been reading to date:
jQuery API: Deferred
JQuery Fundamentals
jQuery Deferreds promises asynchronous bliss (blog)
StackOverflow: timeout for function (jQuery)
Update
This is in response to #Kevin B below:
I tried this:
var moduleA = {
var moduleB = {
postData: {"id":7, "answer":"Ever since I went to Disneyland..."};
save: function() {
return $.post("path/to/service", postData, null, "JSON");
}
};
var result = this.moduleB.save();
result.done(function(resp) {
if (resp.saved == true) {
// never reached before completion
console.log("yahoo");
} else {
console.log("Error: " + resp.error);
// >> undefined
}
});
}
You are over-complicating your code. You cannot get the data to outside of the callback, no matter how many deferred/promises you create/use (your sample creates 3 different deferred objects!)
Use the done callback.
var postData = {"id":7, "answer":"Ever since I went to Disneyland..."};
$.post("my/restful/url", postData).done(function (result) {
console.log(result.save, result.error);
});
You seem to have a misunderstanding of both asynchronous requests, the Promise pattern, and Javascripts mechanism of passing functions as an argument.
To understand what's really happening in your code I suggest you use a debugger and set some breakpoints in the code. Or, alternatively, add some console.logs in your code. This way you can see the flow of the program and might understand it better. Also be sure to log the arguments of the function you pass as an argument in the then()-method, so you understand what is passed.
ok you got it half right. the problem is that when you execute the console.log the promised is not yet fulfilled the asynchronous nature of the promises allows the code to execute before that ajax operation is done. also result is a deferred not a value, you need to handle your promised with .done instead of .then if you wish to return a value otherwise you'll continue passing promises.
so that said
var result={};
$.when(
$.post("my/restful/url", postData))
.done( function(data) {
result.data=data;
});
// here result is an object and data is a undefined since the promised has no yet been resolve.
console.log(result.data);

Syntax for javascript object implementing an AJAX GET and custom event

I've got a couple of questions about this small snippett adapted from a tutorial I found here.
var loader = (function ($, host) {
return {
loadTemplate: function (path) {
var tmplLoader = $.get(path)
.success(function (result) {
$("body").append(result);
})
.error(function (result) {
alert("Error Loading Template");
}) // --> (1) SEMICOLON?
// (2) How does this wire up an event to the previous
// jQuery AJAX GET? Didn't it already happen?
tmplLoader.complete(function () {
$(host).trigger("TemplateLoaded", [path]);
});
}
};
})(jQuery, document);
Is there supposed to be a semicolon there?
It seems like the AJAX GET is happening and then an event is getting wired to it - what am I missing here?
Is there supposed to be a semicolon there?
It's optional, but recommended.
It seems like the AJAX GET is happening and then an event is getting wired to it - what am I missing here?
AJAX is asynchronous, so it's very unlikely the request will be already completed right after sending it. So, there's time to add another callback. And even if there weren't, it would work anyway, since jQuery implements those callbacks with promises. See example here.
With javascript, and ajax in particular it is important to understand how the browser goes about executing your code. When you make the request for remote data via an ajax GET, the rest of your code is still executing. Imagine if as soon as you made a request for some JSON to a busy server, lets say it takes a couple seconds, and everything on your page stops working during that time period. It would be very difficult to write code that wasn't difficult for the user to interact with. Luckily ajax is async, meaning it makes the request and an carries on as usual until the complete event (or equivalent) is fired. This is what executes your code pertinent to the data you just received. So when you specify that callback at the bottom of your snippit, you are telling the browser, "go do your thing for now but when you hear back from the server, do all of these things".
Oh yeah, and semicolons are optional, but as a best practice, most people use them.
They are assigning the $.get to a variable and then adding a complete handler to it.
It's the same as doing this:
$.get('/path'), function(){
//success callback
}).error(function(e){
//errors
}).complete(function(){
//always run
});
Just an unusual way of doing it.

Possible Javascript scope issue

I'm sort of a noob with this so please forgive me :)
I can't get this one part of the function to update the variable. Could anyone possibly take a look a see what I'm doing wrong?
http://pastie.org/private/zfnv8v2astglabluo89ta
From line 142 thru 172 I'm not getting any results in the end. I've tested inside that function to make sure it is actually returning data, but the "body" variable is passing back up after line 172. So if I look at my generated HTML on the page, it simply looks the function skips from 140 to 174.
Thanks for any feedback!!
Your $.get is asynchronous. That means it will finish sometime AFTER the rest of the code, thus you won't see it's effect on the body variable inside that function. Instead, it's success callback function will be called long after this function has already finished.
To chain multiple asynchronous ajax calls like you have here, you can't just use normal sequential programming because asynchronous ajax calls aren't sequential. The network request is sent, then your javascript continues executing and SOMETIME LATER when the response arrives, the success handler is called and is executed.
To run sequential ajax calls like you have, you have to nest the work inside the success handler so that the ONLY code that uses the response is actually in the success handler. In pseudo-code, it looks like this:
$.get(..., function(data) {
// operate on the results only in here
// a second ajax function that uses the data from the first
// or adds onto the data from the first
$.get(..., function(data) {
// now finally, you have all the data
// so you can continue on with your logic here
});
// DO NOT PUT ANYTHING HERE that uses the responses from the ajax calls
// because that data will not yet be available here
});
You cannot do what you're doing which is like this:
var myVariable;
$.get(..., function(data) {
// add something to myVariable
});
$.get(..., function(data) {
// add something to myVariable
});
$.get(..., function(data) {
// add something to myVariable
});
// do something with myVariable
None of those ajax calls will have completed before the end of your function. You have to follow a design pattern like in my first example.
For more advanced tools, one can always use jQuery deferreds which are just a different way of defining code to run after an ajax call is done. It looks a little more like sequential programming even though it's really just scheduling code to run the same way my first code example does.
Function 8 will be invoke after line 174-180. You must put code from 174-180 line to the end of function

Extract Data from Anonymous Function Scope

Because of the complexity of this application, I have a need to wrap Facebook API calls, like so.
//In main file, read is always undefined
var read = fb_connect.readStream();
// In fb_wrapper.js
function readStream () {
var stream;
FB.api('/me/feed', {limit:10000}, function (response) {
stream = response.data;
});
return stream;
}
I know that due to the asynchronous nature of the call, the rest of the readStream() function will return stream (which has no value). I am having trouble finding a way of getting the data out of the callback function scope and back up to a higher scope. The FB API call is returning fine (I have debugged it a hundred times), but getting that response data has been the battle thus far.
If anyone has any suggestions, it would be much appreciated. I searched for Facebook jQuery plug-ins (as a pre-made wrapper, perhaps) with little luck.
Judging from your question, it seems that you are looking for a synchronous call. Which means that you'd want to use the data returned from the api call right after calling it. In that case, you'll need to check whether FB.api supports synchronous calls (mostly doesn't).
Otherwise, you'll need to understand that you are making an async call here. Which means that you should put your handling code INSIDE the callback function that you pass to FB.api. This is called the "continuation" style of writing code and is the standard way to use async calls.
FB.api('/me/feed', {limit:10000}, function (response) {
var stream = response.data;
// Do your processing here, not outside!!!
});
Or:
function handlerFunction(response) {
// Do your processing here
}
FB.api('/me/feed', {limit:10000}, handlerFunction);

Variables set during $.getJSON function only accessible within function

This may be more of a scoping question. I'm trying to set a JSON object within a $.getJSON function, but I need to be able to use that object outside of the callback.
var jsonIssues = {}; // declare json variable
$.getJSON("url", function(data) {
jsonIssues = data.Issues;
});
// jsonIssues not accessible here
A similar question like this one was asked in another post, and the consensus was that anything I need to do with the JSON objects needs to be done within the callback function, and cannot be accessed anywhere else. Is there really no way that I can continue to access/manipulate that JSON object outside of the $.getJSON callback? What about returning the variable, or setting a global?
I'd appreciate any help. This just doesn't seem right...
UPDATE:
Tried setting the $.ajax() async setting to false, and running through the same code, with no luck. Code I tried is below:
var jsonIssues = {}; // declare json variable
$.ajax({ async: false });
$.getJSON("url", function(data) {
jsonIssues = data.Issues;
});
// jsonIssues still not accessible here
Also, I've had a couple responses that a global variable should work fine. I should clarify that all of this code is within $(document).ready(function() {. To set a global variable, should I just declare it before the document.ready? As such:
var jsonIssues = {};
$(document).ready(function() {
var jsonIssues = {}; // declare json variable
$.getJSON("url", function(data) {
jsonIssues = data.Issues;
});
// now accessible?
}
I was under the impression that that a variable declared within document.ready should be "globally" accessible and modifiable within any part of document.ready, including subfunctions like the $.getJSON callback function. I may need to read up on javascript variable scoping, but there doesn't seem to be an easy to achieve what I'm going for. Thanks for all the responses.
UPDATE #2:
Per comments given to answers below, I did use $.ajax instead of .getJSON, and achieved the results I wanted. Code is below:
var jsonIssues = {};
$.ajax({
url: "url",
async: false,
dataType: 'json',
success: function(data) {
jsonIssues = data.Issues;
}
});
// jsonIssues accessible here -- good!!
Couple follow-up comments to my answers (and I appreciate them all). My purpose in doing this is to load a JSON object initially with a list of Issues that the user can then remove from, and save off. But this is done via subsequent interactions on the page, and I cannot foresee what the user will want to do with the JSON object within the callback. Hence the need to make it accessible once the callback complete. Does anyone see a flaw in my logic here? Seriously, because there may be something I'm not seeing...
Also, I was reading through the .ajax() jQuery documentation, and it says that setting async to false "Loads data synchronously. Blocks the browser while the requests is active. It is better to block user interaction by other means when synchronization is necessary."
Does anyone have an idea how I should be blocking user interaction while this is going on? Why is it such a concern? Thanks again for all the responses.
$.getJSON is asynchronous. That is, the code after the call is executed while $.getJSON fetches and parses the data and calls your callback.
So, given this:
a();
$.getJSON("url", function() {
b();
});
c();
The order of the calls of a, b, and c may be either a b c (what you want, in this case) or a c b (more likely to actually happen).
The solution?
Synchronous XHR requests
Make the request synchronous instead of asynchronous:
a();
$.ajax({
async: false,
url: "url",
success: function() {
b();
}
});
c();
Restructure code
Move the call to c after the call to b:
a();
$.getJSON("url", function() {
b();
c();
});
Remember that when you supply a callback function, the point of that is to defer the execution of that callback until later and immediately continue execution of whatever is next. This is necessary because of the single-threaded execution model of JavaScript in the browser. Forcing synchronous execution is possible, but it hangs the browser for the entire duration of the operation. In the case of something like $.getJSON, that is a prohibitively long time for the browser to stop responding.
In other words, you're trying to find a way to use this procedural paradigm:
var foo = {};
$.getJSON("url", function(data) {
foo = data.property;
});
// Use foo here.
When you need to refactor your code so that it flows more like this:
$.getJSON("url", function(data) {
// Do something with data.property here.
});
"Do something" could be a call to another function if you want to keep the callback function simple. The important part is that you're waiting until $.getJSON finishes before executing the code.
You could even use custom events so that the code you had placed after $.getJSON subscribes to an IssuesReceived event and you raise that event in the $.getJSON callback:
$(document).ready(function() {
$(document).bind('IssuesReceived', IssuesReceived)
$.getJSON("url", function(data) {
$(document).trigger('IssuesReceived', data);
});
});
function IssuesReceived(evt, data) {
// Do something with data here.
}
Update:
Or, you could store the data globally and just use the custom event for notification that the data had been received and the global variable updated.
$(document).ready(function() {
$(document).bind('IssuesReceived', IssuesReceived)
$.getJSON("url", function(data) {
// I prefer the window.data syntax so that it's obvious
// that the variable is global.
window.data = data;
$(document).trigger('IssuesReceived');
});
});
function IssuesReceived(evt) {
// Do something with window.data here.
// (e.g. create the drag 'n drop interface)
}
// Wired up as the "drop" callback handler on
// your drag 'n drop UI.
function OnDrop(evt) {
// Modify window.data accordingly.
}
// Maybe wired up as the click handler for a
// "Save changes" button.
function SaveChanges() {
$.post("SaveUrl", window.data);
}
Update 2:
In response to this:
Does anyone have an idea how I should be blocking user interaction while this is going on? Why is it such a concern? Thanks again for all the responses.
The reason that you should avoid blocking the browser with synchronous AJAX calls is that a blocked JavaScript thread blocks everything else in the browser too, including other tabs and even other windows. That means no scrolling, no navigation, no nothing. For all intents and purposes, it appears as though the browser has crashed. As you can imagine, a page that behaves this way is a significant nuisance to its users.
maybe this work, works to me.. :)
$variable= new array();
$.getJSON("url", function(data){
asignVariable(data);
}
function asignVariable(data){
$variable = data;
}
console.log($variable);
Hope it help you..
:)
You could approach this with promises:
var jsonPromise = $.getJSON("url")
jsonPromise.done(function(data) {
// success
// do stuff with data
});
jsonPromise.fail(function(reason) {
// it failed... handle it
});
// other stuff ....
jsonPromise.then(function(data) {
// do moar stuff with data
// will perhaps fire instantly, since the deferred may already be resolved.
});
It is pretty straight forward and a viable way to make async code feel more imperative.
"But this is done via subsequent interactions on the page, and I cannot foresee what the user will want to do with the JSON object within the callback."
The callback is your opportunity to set the screen up for the user's interaction with the data.
You can create or reveal HTML for the user, and set up more callbacks.
Most of the time, none of your code will be running. Programming an Ajax page is all about thinking about which events might happen when.
There's a reason it's "Ajax" and not "Sjax." There's a reason it's a pain to change from async to sync. It's expected you'll do the page async.
Event-driven programming can be frustrating at first.
I've done computationally intensive financial algorithms in JS. Even then, it's the same thing--you break it up into little parts, and the events are timeouts.
Animation in JavaScript is also event driven. In fact, the browser won't even show the movement unless your script relinquishes control repeatedly.
You are just running into scoping issues.
Short answer:
window.jsonIssues = {}; // or tack on to some other accessible var
$.getJSON("url", function(data) {
window.jsonIssues = data.Issues;
});
// see results here
alert(window.jsonIssues);
Long answers:
Scoping issue in Javascript
Javascript closure scoping issue

Categories

Resources