In every authenticated requests (GET, POST, etc) of my Backbone/Marionette application I must to attach an accessToken.
I store this accessToken and expireDate in the localStorage.
To check if the accessToken is expired I call this method: user.checkToken().
If is expired, the method renew the accessToken with a POST request to my backend.
Where should I put this check? I mean, in which part of the application?
Should I rewrite my on Backbone.sync method or use ajax.setup "beforeSend" ?
Thanks in advance for your advices/idea.
Backbone uses jQuery (see the note for a solution that may work with Zepto) for ajax requests, so you can use (as suggested by Edward) jQuery.ajaxPrefilter.
I did a little test for this task, let me know if there's any problem:
function tokenIsExpired() {
return true;
}
function createPromiseFunction(method, jqXHRsource, jqXHR) {
return function() {
jqXHRsource[method] = function(f) {
if (f) {
jqXHR[method] = function() {
f.apply(this, arguments);
};
}
return this;
};
}
}
function updateToken() {
return $.ajax({
url: '',
method: 'GET',
data: {some:'data', here:'yes'},
success: function() {
// update the token sir
console.log('token call done')
},
skipTokenCheck: true // required
});
}
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
/*
* check if token is expired every time a new ajax request is made
* if it is expired, aborts the current requests, updated the token
* and eventually does the original request again.
*/
if (!options.skipTokenCheck && tokenIsExpired()) {
// at this point no callback should have be added to the promise object
var methodsNames = [
'done',
'always',
'fail',
'progress',
'then'
];
var methods = {};
// copy the callbacks when they're added to the old request
for (var i = 0; i < methodsNames.length; i++) {
var name = methodsNames[i];
createPromiseFunction(name, jqXHR, methods)();
};
jqXHR.abort();
// TODO: error checks
updateToken().done(function() {
console.log('done');
var newReq = $.ajax($.extend(originalOptions, {skipTokenCheck: true}));
for (var i = 0; i < methodsNames.length; i++) {
var name = methodsNames[i];
var f = methods[name];
if (f) {
newReq[name](f);
}
};
});
}
});
var p = $.get('.');
p.done(function() { console.log(arguments); }).fail(function() {
console.log('fail');
});
Looks like that ajaxPrefilter doesn't work with Zepto. Alternatively you can use the ajaxBeforeSend event.
Returning false in the beforeSend function will cancel the request.
Should be easy to adapt the code I posted above.
Overwrite your model's sync() function and do whatever you need to do.. Something like:
var MyModel = Backbone.Model.extend({
sync: function() {
// Put your code here
Backbone.Model.prototype.sync.apply(this, arguments);
}
});
Edit #1:
Not sure where you get user (as well as other variables) from but here it is:
var MyModel = Backbone.Model.extend({
sync: function() {
user.checkToken().done(_.bind(function(){
Backbone.Model.prototype.sync.apply(this, [ method, model, options ]);
});
}, this);
});
Related
I want to make a convenience method for my Ajax calls as it is used extensively in the project.
As of now a typical call in the project looks like this.
$.post(
"url",
{
param1: value1,
param2: value2
},
function (data) {}
);
This call is repeated multiple times in the project with different parameters so I would like to be able to create a function to which I can pass the parameters and it will handle the entire Ajax call without me having to write the code every time.
Expected Output:
var data= {firstName:"John", lastName:"Doe", age:46};
do_ajax_request(data);
The function do_ajax_request in turn contains the actual Ajax code which makes the actual request and handles the result.
If possible I would also like for it to return a callback in case I need to perform any extra operations, would a promise work for that?
This would be a global function so I can access it from any JavaScript file.
So many complicated answers for something jQuery supports out of the box. Turning my comment to an answer.
You are basically just coding a wrapper for a wrapper so you do no have to recode some basic lines. No harm in that since it is easy to make the change in one place vs many.
So defined your function and just return the Ajax object that jQuery has. You can than use the done, fail, always methods.
function do_ajax_request (data) {
return $.post("url", data);
}
do_ajax_request({"foo":"bar"})
.done( function(){})
.fail(function(){})
do_ajax_request({"foo":"bar"})
.done( function(){})
.fail(function(){})
If you want to have common code inside, you can do that too, basic idea for an error handler...
function do_ajax_request (data) {
var xhr = $.post("url", data);
xhr.fail(function () {
console.log(arguments)
});
return xhr;
}
I have written several jQuery plug-ins for use in my projects, and have brought along my ajax call method in nearly everyone. Here is a snippet of it from one of my projects. Enjoy!
Method Signature:
obj = An object you want to pass to the ajax call in the data parameter. Pass null if not needed.
method = ajax methods: POST, GET, PUT, DELETE, etc. Default is GET.
endPoint = Url to call.
returnType = html, json, text, etc.
success = callback method when the call is successful.
beforesend = method to call before the send. This is useful when you need to set headers before a call.
failure = callback method when the call is unsuccessul.
var _api = {
call: function (obj, method, endPoint, returnType, success, beforesend, failure) {
obj = obj === null || undefined ? {} : obj;
$.ajax({
method: method || 'GET',
data: !$.isEmptyObject(obj) ? JSON.stringify(obj) : null,
contentType: function () {
switch (returnType) {
case 'json':
return 'application/json';
case 'text':
return 'text/plain';
case 'buffer':
return 'arraybuffer';
case 'html':
default:
return 'text/html';
}
}(returnType === 'json' ? 'application/json; charset=utf-8' : ''),
url: endPoint,
dataType: returnType,
beforeSend: function (xhr, obj) {
if (beforesend) {
beforesend(xhr, obj);
} else {
_api.showLoader();
}
}
}).done(function (data, textStatus, xhr) {
if (success) success(data)
}).fail(function (data, textStatus, xhr) {
if (failure) failure()
}).always(function () {
// Implement code here that you want to run whenever the call is complete regardless of success or failure.
});
}
}
You could create a prototype to with a constructor to handle the input - make the request and handle the response:
ajax.requests = function ( data ) {
this.data = data;
return this.doRequest();
};
ajax.requests.prototype = {
doRequest : function () {
var _this = this;
$.ajax({
data: _this.data
}).done(function(data) {
Handle response and return!
});
}
};
// USAGE
var response = new ajax.requests( yourData );
By returning the $.post, you can use a callback like .done(), chain them together with .then(), etc.
function do_ajax_request(data) {
return $.post( ... ); //RETURN the object
}
var myData = { ... };
do_ajax_request(myData).done(function(result) {
console.log("AJAX complete: " + result);
});
Just another take on this that maybe you hadn't considered. Rather than trying to wrap what is essentially already a wrapper, consider encapsulating your common functionality, like handling errors and dealing with results and using this when executing an ajax request with the existing jQuery ajax wrapper(s)
function handleError(e){
// your common error handling
}
function handleResult(result){
// your common result handling
}
// then every time you execute a request, use your shared functionality
$.post(url, data)
.fail(handleError)
.done(handleResult);
Using code below, you'd need to import config object or declare on top of the functions.
I made two versions for POST and GET respectively
function getJSON(param, absoluteRestUrl = '') {
if (!absoluteRestUrl) {
absoluteRestUrl = config.adminRestEndpoint; // defaultUrl
}
return new Promise(async (resolve, reject) => {
let res = null;
res = await $.getJSON(absoluteRestUrl, param);
resolve(JSON.parse(JSON.stringify(res)));
});
}
function postJSON(param, absoluteRestUrl = '') {
if (!absoluteRestUrl) {
absoluteRestUrl = config.adminRestEndpoint; // defaultUrl
}
return new Promise(async (resolve, reject) => {
let res = null;
res = await $.post(absoluteRestUrl, param, null, 'json');
resolve(JSON.parse(JSON.stringify(res)));
});
}
I'm trying to use alter the xhr object on an ajax request. I'm doing this on a fetch call for a collection. But when I alter the xhr I get no data. The purpose of this is to show the loaders percentage but the xhr isn't even working when I return the new xhr object. I did checkout the xhr that is returned and the url points to /admin/categories
require(['views/categories', 'models/categories', 'helpers/helper'], function(CategoriesView, model, helper) {
var categories = new model.CategoriesCollection;
categories.fetch({ url: "/admin/categories/getcategories", xhr: helper.xhr('#main-loader') }).then(function(response) {
console.log(response);
});
});
and here is my helper file
define(['helpers/helper', 'require'], function(Helper, require) {
'use strict';
var $ = require('jquery');
var Backbone = require('backbone');
var xhr = function(loaderId) {
var _xhr = Backbone.$.ajaxSettings.xhr();
_xhr.addEventListener("progress", function(e){
if (e.lengthComputable) {
console.log(e);
}
}, false);
return _xhr;
}
return {
xhr: xhr
}
});
An easy way to pass options to the xhr object (XMLHttpRequest object) is to use the xhrFields option of the jQuery ajax function. Since Backbone.sync uses jQuery.ajax by default in the background, any options passed to a Backbone syncing function is then passed as the ajax options.
Simple one-off solution
The simplest example of checking progress:
myCollection.fetch({
url: root + '/photos/',
xhrFields: {
onprogress: function() {
console.log("options onprogress");
}
}
});
Permanent solution
But a more convinient way would be to override the global Backbone.sync function to add our own progress callback option and a custom progress event.
Overriding Backbone.sync
Warning: don't override Backbone core if you're writing a library or code that will be shared.
Backbone.sync = (function(syncFn) {
return function(method, model, options) {
options = options || {};
var context = options.context,
progress = options.progress,
xhrFields = options.xhrFields || {},
onprogress = xhrFields.onprogress;
xhrFields.onprogress = function(e) {
var params = [model, e.loaded, _.extend({}, options, { event: e })];
if (progress) progress.apply(context, params)
if (onprogress) onprogress.apply(this, arguments);
model.trigger(['progress'].concat(params));
};
options.xhrFields = xhrFields;
return syncFn.apply(this, arguments);
};
})(Backbone.sync);
How to use
It's really straight-forward to use:
var myCollection = new Backbone.Collection(),
root = 'https://jsonplaceholder.typicode.com';
It provides a custom progress event.
myCollection.listenTo(myCollection, 'progess', function(collection, value, options) {
console.log("collection progress event");
});
It also provides a custom progress callback that can be passed to any Backbone functions that calls Backbone.sync in the background, like fetch, save, destroy. Also, passing xhrFields still works as expected.
myCollection.fetch({
url: root + '/photos/',
success: function() {
console.log(myCollection.models);
},
// custom options callback
progress: function(collection, value, options) {
console.log("collection onprogress callback");
},
// this still works
xhrFields: {
onprogress: function() {
console.log("options onprogress");
}
}
});
You receive the collection or model, the loaded data count, and the options object, which contains all the options of the sync, in addition to the native progress event (options.event).
Note that it's not that useful as the total doesn't always work. As an example, in Chrome the total is always zero, but in firefox, the total is correct. You should check the lengthComputable property.
This is my object definition:
function DrilledLayer(sourceLayerName, sourceTableName, targetLayerName, targetFieldName, operators, baseLayer=false) {
this.sourceLayerName = sourceLayerName;
this.sourceTableName = sourceTableName;
this.targetLayerName = targetLayerName;
this.targetFieldName = targetFieldName;
this.operators = operators;
this.baseLayer = baseLayer;
this.targetLayerId;
this.drilledLayerId;
this.selectedCode;
this.redraw = false;
this.getTargetLayerId(); //this function must initialize this.targetLayerId
}
DrilledLayer.prototype.getTargetLayerId = function(){
$.soap({
url: 'https://url/version_4.8/services/MapService',
method: 'getLayersIdByName',
appendMethodToURL: false,
data : {
mapInstanceKey: mapKey,
layerName: this.targetLayerName,
},
error: function(){
alert("error getLayersIdByName");
},
success: function(soapResponse){
layerId = soapResponse.toJSON().Body.getLayersIdByNameResponse.getLayersIdByNameReturn.getLayersIdByNameReturn.text;
this.targetLayerId = layerId;
}
});
}
This is how I create the object:
drillCs = new DrilledLayer("Drilled CS", "Cs_Franco_General.TAB", "RA_General", "Code_RA", "=")
If I look into drillCs object there is no targetLayerId property defined, but I know the soap request were made successfuly. Why?
this in JavaScript is mostly set by how a function is called. this during the success callback won't be the same as this during your call to your getTargetLayerId function, you have to remember it.
In this case, the easiest way is probably with a variable:
DrilledLayer.prototype.getTargetLayerId = function(){
var layer = this; // <=== Set it
$.soap({
url: 'https://url/version_4.8/services/MapService',
method: 'getLayersIdByName',
appendMethodToURL: false,
data : {
mapInstanceKey: mapKey,
layerName: this.targetLayerName,
},
error: function(){
alert("error getLayersIdByName");
},
success: function(soapResponse){
layerId = soapResponse.toJSON().Body.getLayersIdByNameResponse.getLayersIdByNameReturn.getLayersIdByNameReturn.text;
layer.targetLayerId = layerId; // <=== Use it
}
});
}
More (on my blog):
You must remember this
Separately, of course, you won't see the properly until the async callback fires (which will be some time after the new call returns), but you seem to be comfortable with the async aspect of this.
I have all my ajax calls in a custom JS file. And trust me there are alot of them!
I would like to implement a "retry on fail behavior" in all ajax calls.
Is there a way to do it like "interception"? Or do I have to do it one by one?
My fear here is that a future dev will forget to set the retry policy...
Sample ajax call:
$.ajax({
url: apiRoot + 'reservationItens?reservaId=' + idReservation + '&bagId=' + idBag,
type: 'PUT',
success: function () {
if (onSuccess != null) {
onSuccess();
}
},
error: function (x, y, z) {
if (onError != null) {
onError(x, y, z);
}
}
});
You can use ajaxError which takes a callback that is called on every ajax error.
Additionally you can add a boolean to the settings object and check for it in the callback which ensures that one failed request is only called a second time and not more.
$(document).ajaxError(function (event, jqxhr, settings) {
if(!settings.secondExec) {
settings.secondExec = true;
$.ajax(settings);
}
});
If desired add a timeout for the second request to increase the possibility that a random server or connection problem is resolved in the meantime:
setTimeout(function() {
$.ajax(settings);
}, 500);
If you want to exclude some requests just add another property to the request settings which you then use like secondExec is used in the example.
Here's a working jsfiddle.
I'd do it like this, with a recursive function:
function AjaxRetry(settings, maxTries, interval) {
var self = this;
this.settings = settings;
this.maxTries = typeof maxTries === "number" ? maxTries : 0;
this.completedTries = 0;
this.interval = typeof interval === "number" ? interval : 0;
// Return a promise, so that you can chain methods
// as you would with regular jQuery ajax calls
return tryAjax().promise();
function tryAjax(deferred) {
console.log("Trying ajax #" + (self.completedTries + 1));
var d = deferred || $.Deferred();
$.ajax(self.settings)
.done(function(data) {
// If it succeeds, don't keep retrying
d.resolve(data);
})
.fail(function(error) {
self.completedTries++;
// Recursively call this function again (after a timeout)
// until either it succeeds or we hit the max number of tries
if (self.completedTries < self.maxTries) {
console.log("Waiting " + interval + "ms before retrying...");
setTimeout(function(){
tryAjax(d);
}, self.interval);
} else {
d.reject(error);
}
});
return d;
}
}
And then usage is like this:
var settings = {
url: "https://httpbin.org/get",
data: {foo: "bar"},
contentType: "application/json; charset=UTF-8"
};
var maxTries = 3;
var interval = 500;
// Make your ajax call and retry up to 3 times,
// waiting 500 milliseconds between attempts.
new AjaxRetry(settings, maxTries, interval)
.done(function(data){
alert("My ajax call succeeded!");
})
.fail(function(error) {
alert("My ajax call failed :'(");
})
.always(function(resp){
alert("My ajax call is over.");
});
You can create api method for ajax calls, just like this one. In the ajaxApi function you can create your own handlers. For example for success or error events, thanks to this developer using this api can attach his handlers, without worrying what else handlers to attach.
function outerSuccesFN() {
console.log('outerSuccesFN');
}
function outerErroFN() {
console.log('outerErroFN');
}
function completeFn() {
console.log(completeFn);
}
function ajaxApi(url, dataType, data, timeout) {
var ajaxResults = $.ajax({
url: url,
dataType: dataType,
data: data,
timeout: timeout
});
function mySuccesFn() {
console.log('mySuccesFn');
}
function myErroFn() {
console.log('myErroFn');
}
return ajaxResults.done(mySuccesFn).fail(myErroFn);
}
var ajaxResult = ajaxApi('http://api.jquery.com/jsonp/', 'jsonp', {
title: 'ajax'
}, 15000);
ajaxResult.done(outerSuccesFN).fail(outerErroFN).always(completeFn);
Im trying to develop a class in JavaScript I can use to access a load of data that is gathered by an AJAX request easily. The only problem is I need to make the members of the class accessible only once the AJAX call is complete. Ideally what I would like to end up is something where by I can call this in a script:
courses.getCourse('xyz').complete = function () {
// do something with the code
}
And this will only fire after the AJAX call has been complete and the data structures in the "class" are ready to be used. Ideally I dont want to have to create a .complete member for every function in the class
Here is the "class" I am trying to make so far:
var model_courses = (function() {
var cls = function () {
var _storage = {}; // Used for storing course related info
_storage.courses = {}; // Used for accessing courses directly
_storage.references = new Array(); // Stores all available course IDs
var _ready = 0;
$.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
_storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
_storage.references.push($(this).attr('id'))
})
}
})
console.log(_storage.courses)
}
cls.prototype = {
getCourse: function (courseID) {
console.log(cls._storage)
},
getCourses: function () {
return _storage.courses
},
getReferences: function (),
return _storage.references
}
}
return cls
})()
At the moment getCourse will be fired before the AJAX request is complete and obviously it will have no data to access.
Any ideas will be greatly appreciated, im stuck on this one!
jQuery already handles this for you using deferred objects, unless i'm misunderstanding what you are looking for.
var courses = {
getCourse: function (id) {
return $.ajax({url:"getCourse.php",data:{id:id});
}
};
courses.getCourse("history").done(function(data){
console.log(data);
});
I know this isn't exactly what you are looking for, I'm hoping it's enough to push you in the right direction. Deferred objects are awesome.
The following changes allow you to make the AJAX request just once and you can call your function like
courses.getCourse('xyz', function(course){
// Use course here
});
Here are the changes
var model_courses = (function() {
// This is what gets returned by the $.ajax call
var xhr;
var _storage = {}; // Used for storing course related info
_storage.courses = {}; // Used for accessing courses directly
_storage.references = []; // Stores all available course IDs
var cls = function () {
xhr = $.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
_storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
_storage.references.push($(this).attr('id'))
});
}
});
}
cls.prototype = {
// Made changes here, you'd have to make the same
// changes to getCourses and getReferences
getCourse: function (courseID, callback) {
if (xhr.readyState == 4) {
callback(_storage.courses[courseID]);
}
else {
xhr.done(function(){
callback(_storage.courses[courseID]);
})
}
},
getCourses: function () {
return _storage.courses
},
getReferences: function (),
return _storage.references
}
}
return cls
})()
As a side note, your module pattern will not work very well if you need to instantiate two of these model_courses objects, since the storage objects are all shared in your self calling function's closure. You usually don't mix the module pattern with prototypes (returning a constructor from a module), unless you really know what you are doing, that is, the shared closure variables work as static properties of your class.
This is what I would do if I were you (since you really want private variables)
function ModelCourses() {
var storage = {
courses: {},
references: []
};
var xhr = $.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
storage.references.push($(this).attr('id'))
})
}
});
this.getCourse = function(courseId, callback) {
function getCourse() {
callback(storage.courses[courseID])
}
if (xhr.readyState == 4) {
getCourse();
}
else {
xhr.done(getCourse);
}
};
}
in getStorage either add a check to see if there is any data to pilfer (preferred), or make the "actual" method private than publicize it when it has items it can access. (I would recommend the first though otherwise you'll get exceptions about calling a method that doesn't exists on an object).
You can define a function getData that would perform the ajax request and that would take the getCourse as a callback.
The getData could possibly store locally the result of the Ajax call and test the local storage before performing the ajax call.
You could also specify a private member to allow the ajax call to be run only once.
You might want to check underscore.js for some handy tool
Here is a short example code :
cls.prototype.getData = function(callback) {
/*perform ajax call or retrieve data from cache*/
callback()
}
cls.prototype.getCourse = function(id) {
this.getData(function() {
/*do something with the data and the id you passed*/
}
}