Callback called when a task finish OR already finished - javascript

I have a simple code that involves asynchronous tasks:
// The NewsFeed Class
function NewsFeed() {
this.loadFeed = function() {
$.ajax({
url: "http://www.example.com",
success: function() {
// doSomething here, and call onload.
}
});
}
// Need to implement onload here somehow
this.onload = ?;
this.loadFeed();
return this;
}
NewsFeed.constructor = NewsFeed;
// In main JS file
var newsFeed = new NewsFeed();
$(function() {
// do something
newsFeed.onload = function() { // do something when news feed is loaded };
}
My requirement is that, onload of NewsFeed needed to be executed in both case:
If the loadFeed's ajax is finished, run it immediately.
If the loadFeed's ajax is not done yet, run after it.

There's really no need to use new or constructor when you don't need new instances, all you really need is to run a simple ajax function that gets the result from cache if it hasn't changed.
function newsFeed() {
return $.ajax({
url : "http://www.example.com",
cache : true // let the browser handle caching for you
});
}
// In main JS file
$(function() {
newsFeed().then(function() {
// do something when news feed is loaded
});
});

The new pattern instead of callback is using Promises
see:
https://github.com/kriskowal/q
With jquery you can use:
https://api.jquery.com/category/deferred-object/
now the code:
function NewsFeed() {
function loadFeed() {
var deferred = $.Deferred();
$.ajax({
url: "http://www.example.com",
success: function(data) {
deferred.resolve(data);
},
error: function(data) {
deferred.reject(data);
}
});
return deferred.promise();
}
this.loadFeed = loadFeed;
return this;
}
NewsFeed.constructor = NewsFeed;
// In main JS file
var newsFeed = new NewsFeed();
newsFeed.loadFeed().done(function(data){
//data loaded successfully
})
.fail(function(data){
//ajax request failed
})
.always(function(){
//finally:
});

Related

In jQuery, how to deal with parallel script loading?

I am stuck with this in an app development and don't know what to try. I have the following function:
// Function to load Scripts on the fly
$.loadScript = function(url, arg1, arg2) {
var cache = false,
callback = null;
//arg1 and arg2 can be interchangable
if ($.isFunction(arg1)) {
callback = arg1;
cache = arg2 || cache;
} else {
cache = arg1 || cache;
callback = arg2 || callback;
}
var that = this;
var load = true;
var deferred = jQuery.Deferred();
if (jQuery.isFunction(callback)) {
deferred.done(function(){
callback.call(that);
});
};
if( url.constructor === Array ){
function loadScript(i) {
if (i < url.length) {
var el = url[i];
//check all existing script tags in the page for the url
if (window.loadedScripts.indexOf(el) === -1) {
window.loadedScripts.push(el);
//didn't find it in the page, so load it
$.ajax({
url: el,
success: function(e){
__info('Loaded script. url: '+el, 'verbose');
loadScript(i + 1);
},
complete: function(e){
if(typeof e.done !== 'function'){
$(function () {
$('<script>')
.attr('type', 'text/javascript')
.text(e.responseText)
.prop('defer',true)
.appendTo('head');
})
}
},
dataType: 'script',
cache: cache
});
}
//-----------------
} else {
deferred.resolve();
}
}
loadScript(0);
return deferred;
} else {
//check all existing script tags in the page for the url
if (window.loadedScripts.indexOf(url) === -1) {
window.loadedScripts.push(url);
//didn't find it in the page, so load it
$.ajax({
url: url,
success: function(e){
__info('Loaded script. url: '+url, 'verbose');
deferred.resolve();
},
complete: function(e){
if(typeof e.done !== 'function'){
$(function () {
$('<script>')
.attr('type', 'text/javascript')
.text(e.responseText)
.prop('defer',true)
.appendTo('head');
})
}
},
dataType: 'script',
cache: cache
});
} else {
deferred.resolve();
};
}
};
This function works similar to $.getScript, but loads several scripts (or only one), fires a callback at end and introduces cache parameter to deal with custom cache of this app.
It already works fine, except in the case when several scripts are requesting loading in parallel. When this occurs, second set of scripts enter the function, watches that first script (common for the blocks) are loading and don`t load it (but will need to wait for it). But, in this case, in first block the first script is not loaded already and in the second block, this script is needed but is skipped.
Example of use:
<script type="text/javascript">
$.loadScript([
"//code.highcharts.com/highcharts.src.js",
"//code.highcharts.com/modules/data.js",
"//code.highcharts.com/modules/treemap.js"
], true, function(){
$.ajax({
type: 'POST',
url: '/skip-process/charts/graph1',
success: function(data){
console.log($.parseJSON(data));
var chart = new Highcharts.Chart($.parseJSON(data));
}
});
});
</script>
How to deal with several scripts requesting load at the same time the same script and they are not waiting for it? Thanks a lot.
I would approach this using $.getScript and using the promise it returns and using then() instead of passing a callback into the function.
Following is not well tested but should be very close
$.loadScript = function (url) {
// create loadedScripts array if doesn't already exist
const cache = window.loadedScripts = window.loadedScripts || [];
// always use array even when string passed in, reduces duplicate code
const normalizeUrl = (url) => (Array.isArray(url) ? url : [url]);
let urlArr = normalizeUrl(url).filter((u) => {
if (cache.includes(u)) {
return false;
}
cache.push(u);
return true;
});
// if all cached just return a resolved promise
if (!urlArr.length) {
return Promise.resolve();
}
// create initial getScript promise
const reqs = $.getScript(urlArr.shift());
while (urlArr.length) {
const u = urlArr.shift();
// chain a new then() for each url
reqs.then(() => $.getScript(u));
}
//return getScript promise chain
return reqs;
};
$.loadScript([
'remote-script-1.js',
'remote-script-2.js'])
.then(() => {
console.log('All Loaded');
});

Jasmine Jquery Spy AJAX complete function

I am trying to write unit test to cover method A complete() block. I am able to mock ajax request using Deferred. But Deferred does not support complete() so I am getting below error
TypeError: _this.methodB(...).complete is not a function. Please help me to cover methodB(..).complete() block.
methodB: function(xURL, container) {
var _this = this;
return $.ajax({
type: 'GET',
url: xURL,
async: false,
dataType: 'html',
timeout: _this.ajaxTimeOut
})
.fail(function(resp) {
_this.doSomethingOnFail();
})
.done(function(resp, textStatus, jqXHR) {
if (jqXHR.status === 200 && resp !== '') {
_this.doSomethingOnDone();
}
});
},
methodA: function(e) {
var _this = this,
_this.methodB(_this.refineURL, _this.$el)
.complete(function(resp) {
**if (resp.responseText !== undefined &&
resp.responseText.indexOf("PRICE_DATA_AVLBL = 'false'") > -1) {
var params1 = _this._getFilterURLParameters(_this.refineURL);
var params2 = _this._getFilterURLParameters(_this.SUCC_URL);
if (params1.lowerbound !== params2.lowerbound ||
$(e.currentTarget).hasClass('js-txt-min')) {
$txtMin.addClass('border-danger');
} else {
$txtMin.val(params2.lowerbound);
}
} else {
_this._pageSubmit();
}**
});
}
Unit Test Code :
it('validate ajax complete', function ajaxComplete(){
spyOn($, 'ajax').and.callFake( function fake() {
XMLHttpRequest = jasmine.createSpy('XMLHttpRequest');
var jqXHR = new XMLHttpRequest();
jqXHR.status = 200;
var dea = new $.Deferred();
dea.resolve('{property:value}',' ', jqXHR);
return dea;
});
f.methodA();
});
Mock dependencies
It is important to remember that when testing a function, you mock dependencies of that function. You don't want to actually invoke those dependent functions in your tests, because you aren't testing those functions. You should be testing those functions elsewhere, and mocking their dependencies, etc.
Your code
With this in mind when testing methodA, you shouldn't care that methodB makes an ajax request. All you care about is that it returns some object that has a complete function, and that you wire up a callback correctly, etc.
Tests
The following (untested) code should roughly work for you, or give you a decent starting point.
describe('.methodA()', function() {
var methodBResult;
beforeEach(function() {
methodBResult = jasmine.createSpyObj('result', ['complete']);
spyOn(f, 'methodB').and.returnValue(methodBResult);
});
it('should call .methodB()', function() {
f.refineURL = 'something for the test';
f.$el = 'something else for the test';
f.methodA();
expect(f.methodB.calls.count()).toBe(1);
expect(f.methodB).toHaveBeenCalledWith(f.refineURL, f.$el);
});
it('should register a callback on complete', function() {
f.methodA();
expect(methodBResult.complete.calls.count()).toBe(1);
expect(methodBResult.complete).toHaveBeenCalledWith(jasmine.any(Function));
});
it('should call .doSomethingOnComplete() when the callback is invoked', function() {
spyOn(f, 'doSomethingOnComplete');
f.methodA();
var callback = methodBResult.complete.calls.argsFor(1)[0];
callback();
expect(f.doSomethingOnComplete.calls.count()).toBe(1);
expect(f.doSomethingOnComplete).toHaveBeenCalledWith();
});
});

Creating a module on JS that have to get data from a RESTFUL API

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*/})

jQuery AJAX listener

If I have object myApi with execute function
var api = new myApi();
api.execute();
Inside execute I have (*that is myApi instance)
function execute() {
$.ajax({
type: this.getRequestMethod(),
data: this.getDataParams(),
complete: function(xmlHttp){
that.setResult(jQuery.parseJSON(xmlHttp.responseText));
that.setHttpStatus(xmlHttp.status);
},
url: this.getUrl(),
beforeSend: setHeader
});
}
How can I make callback/listener so I can do this
var api = new myApi();
api.execute();
var result = api.getResult();
var statusCode = api.getStatusCode();
switch(statusCode) {...};
if I leave it just this way, these bottom two lines are executed before ajax call is finished (complete isn't called yet) so I have undefined variables.
You can't do it that way, unless you would force the AJAX requests to be syncronous (which probably is a bad idea). You need need to attach somekind of callback method, you can also use some jQuery Deferred magic.
Therefore, return the jqXHR object which encapsulates a Deferred:
function execute() {
return $.ajax({
type: this.getRequestMethod(),
data: this.getDataParams(),
complete: function(xmlHttp){
that.setResult(jQuery.parseJSON(xmlHttp.responseText));
that.setHttpStatus(xmlHttp.status);
},
url: this.getUrl(),
beforeSend: setHeader
});
}
and then use it like
var api = new myApi();
var req = api.execute();
req.done(function( data ) {
});
req.fail(function( xhr ) {
var statusCode = xhr.status; // etc.
});

Programming pattern to flatten deeply nested ajax callbacks?

I've inherited JavaScript code where the success callback of an Ajax handler initiates another Ajax call where the success callback may or may not initiate another Ajax call. This leads to deeply nested anonymous functions. Maybe there is a clever programming pattern that avoids the deep-nesting and is more DRY. Also, there is the problem of inner variables myVar1 and myVar2 that are used throughout the functions.
jQuery.extend(Application.Model.prototype, {
process: function() {
var myVar1;
// processing using myVar1;
jQuery.ajax({
url:myurl1,
dataType:'json',
success:function(data) {
var myVar2;
// process data using myVar1, set state of myVar2,
// then send it back
jQuery.ajax({
url:myurl2,
dataType:'json',
success:function(data) {
// do stuff with myVar1 and myVar2
if(!data.ok) {
jQuery.ajax({
url:myurl2,
dataType:'json',
success:mycallback
});
}
else {
mycallback(data);
}
}
});
}
});
}
});
There's no need for all the callbacks to be anonymous and defined inline, you can declare them elsewhere and just use the function name when specifying the callback.
Thanks to the chaining hint and this comment, I have come to the following solution. I have tested it and it works. There are probably some scope issues and you could refactor a general ChainAjax class out of it. But for the time being, this is ok.
jQuery.extend(MyApplication.Model.prototype, {
process: function() {
// private class for executing the Ajax calls
var myAjaxCalls = function(options) {
this.options = options;
this.myVar1 = null;
this.myVar2 =null;
}
jQuery.extend(myAjaxCalls.prototype, {
process1:function(data) {
// processsing using this.myVar1
this.myVar1 = 5;
return true;
},
process2:function(data) {
this.myVar2 = 6;
if(data.ok) {
mycallback(data);
}
else {
return true;
}
},
process3:function(data) {
// Process this.myVar1 and this.myVar
mycallback(data);
return false;
},
chainAjax:function() {
if(this.options.length > 0) {
var opt = this.options.shift();
var that = this;
jQuery.ajax({
url:opt.url,
success:function(data) {
if(that[opt.callback](data)) {
that.chainAjax();
}
}
});
}
}
});
// End private class
var calls = new myAjaxCalls([
{url:'http://localhost/', callback:'process1'},
{url:'http://localhost/', callback:'process2'},
{url:'http://localhost/', callback:'process3'}
]);
calls.chainAjax();
}
});
Update: I found this nice presentation that also deals with useful programming patterns and best practices.
Update 2012: In the meantime there are several libraries for simulating a synchronous flow with asynchronous functions: q, stratified.js and streamline.js
I would suggest creating a little tool called "chain ajax". You give it what you want to happen in what order, and then fire. It will chain ajax on success until all the logic runs out. It will help you stop repeating yourself and just represent the logical model of what you want done vs grunt-coding.
You could do this with Frame.js like this:
jQuery.extend(Application.Model.prototype, {
process: function() {
var myVar1;
// processing using myVar1;
Frame(function(done){
jQuery.ajax({
url:myurl1,
dataType:'json',
success: done
});
});
Frame(function(done, data) {
var myVar2;
// process data using myVar1, set state of myVar2,
// then send it back
jQuery.ajax({
url:myurl2,
dataType:'json',
success: done
});
});
Frame(function(done, data) {
// do stuff with myVar1 and myVar2
if(!data.ok) {
jQuery.ajax({
url:myurl2,
dataType:'json',
success:done
});
}
else {
done(data);
}
});
Frame(function(done, data){
mycallback(data);
});
Frame.start();
}
});

Categories