I have a function, which will either return a cached template or if the template has not been cached - it will load it via AJAX and then return it. Here's what I've got:
var getTpl = function( name ) {
var cached = cache.get( 'templates' ) || {};
if( cached.hasOwnProperty( name ) ) {
console.log( 'template ' + name + '.mustache found in cache' );
return cached[ name ];
}
else {
console.log( 'requesting ' + name + '.mustache template via AJAX' );
var tpl;
$.ajax( {
url: path.templates + '/' + name + '.mustache',
async: false,
success: function( data ) {
tpl = data;
var cached = store.get( 'miniTemplates' ) || {};
var newTemplate = {};
newTemplate[ name ] = data;
if( ! cached.hasOwnProperty( name ) ) cache.set( 'templates', _.extend( cached, newTemplate ) )
},
error: function() { tpl = false; }
} );
return tpl;
}
}
This works fine. However, Chrome is complaining about:
Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check http://xhr.spec.whatwg.org/.
Therefore I wanted to switch to using $.deferred, but I can't wrap my head around it. How can I re-write the function above, so calling getTpl would always return a template (either form the cache or directly from the AJAX request)?
You can use promise/deferred concept to achieve your needs
var getTpl = function( name ) {
var promise;
var cached = cache.get( 'templates' ) || {};
if( cached.hasOwnProperty( name ) ) {
console.log( 'template ' + name + '.mustache found in cache' );
var df = new $.Deferred();
df.resolve(cached[ name ]);
promise = df.promise();
} else {
console.log( 'requesting ' + name + '.mustache template via AJAX' );
promise = $.ajax({
url: path.templates + '/' + name + '.mustache'
}).then(function(data) {
tpl = data;
var cached = store.get( 'miniTemplates' ) || {};
var newTemplate = {};
newTemplate[ name ] = data;
if( ! cached.hasOwnProperty( name ) ) cache.set( 'templates', _.extend( cached, newTemplate ) )
return tpl;
});
}
return promise;
}
Then, call your method like this:
getTpl('xyz')
.then(function(template) {
// you have the template, either from cache or fetched via ajax
})
.fail(function(err) {
console.log(err);
});
Since you appear to appear to already be using underscore/lodash, you can make use of memoization rather than maintaining your own cache.
The beauty of promises is that you can access them again and again and they will always produce the same value:
var getTpl = _.memoize(function( name ) {
console.log( 'requesting ' + name + '.mustache template via AJAX' );
return $.ajax({
url: path.templates + '/' + name + '.mustache'
});
});
Yes, it really is that simple.
Then you can just use it like any other promise:
getTpl('myTemplate').then(function (template) {
// use template
}, function (error) {
console.log('Could not retrieve template.', error);
});
Related
I've got 3 functions, checkusername(username), checkemail(email), checkpassword(password). The API each one checks against has a rate limit and there has to be a delay of 15 milliseconds between each call. I have tried:
setTimeout(checkusername(username), 1500);
setTimeout(checkemail(email), 1500);
setTimeout(checkpassword(password), 1500);
That doesn't work. I've tried various other ways like increasing the delay by 1500 using a variable:
var delay = 1500;
checkusername(username);
if(delay == 1500) {
delay = delay + 1500;
checkemail(email);
}
else if(delay == 3000) {
delay = delay + 1500;
checkpassword(password);
}
console.log('Checks done!');
None of it works, still getting the code 429 (rate limit exceeded) come up in the console as per from HaveIbeenpwned API.
Either an answer using Javascript or jQuery or I could possibly try it in PHP if there is a solution that way?
By the way each function uses jQuery $.ajax({}) call to the API.
EDIT:
function checkusername(username) {
$.ajax({
url: 'https://haveibeenpwned.com/api/v2/breachedaccount/'+username+'?includeUnverified=true',
type: 'GET',
dataType: 'json',
success: function(data) {
var html = '<h4>Your username was found in the following hacked site breaches:</h4>';
for(i=0;i<data.length;i++) {
var breachName = data[i].Title;
var breachDesc = data[i].Description
html += '<h3><span class="label label-danger">'+breachName+'</span></h3>';
html += '<p>'+breachDesc+'</p>';
html += '<br>';
}
$('#results').append(html);
}
});
}
First, here's a helper function that will return a Promise that will resolve in 15 milliseconds.
function delay15 () {
return new Promise(function(resolve) { setTimeout(resolve, 15) })
}
You'd need to write your checkusername, checkemail, & checkpassword functions to return Promises when the request has completed.
Example checkusername function (note the added return statement):
function checkusername(username) {
return $.ajax({
url: 'https://haveibeenpwned.com/api/v2/breachedaccount/'+username+'?includeUnverified=true',
type: 'GET',
dataType: 'json',
success: function(data) {
var html = '<h4>Your username was found in the following hacked site breaches:</h4>';
for(i=0;i<data.length;i++) {
var breachName = data[i].Title;
var breachDesc = data[i].Description
html += '<h3><span class="label label-danger">'+breachName+'</span></h3>';
html += '<p>'+breachDesc+'</p>';
html += '<br>';
}
$('#results').append(html);
}
});
}
Then you'd write code something like this:
checkusername(username)
.then(delay15)
.then(function() { return checkemail(email) })
.then(delay15)
.then(function() { return checkpassword(password) })
Here's the same code in ES2015, which is shorter, though not as well-supported in browsers:
function delay15 () {
return new Promise(resolve => setTimeout(resolve, 15))
}
checkusername(username)
.then(delay15)
.then(() => checkemail(email))
.then(delay15)
.then(() => checkpassword(password))
your setTimeout method is triggering all 3 requests essentially simultaneously, but 1.5 seconds after the setTimeout is invoked. try chaining your requests as promises or callbacks so you're not sending them all at the same time.
EDIT:
here's your example:
function checkUsername(u) {
return new Promise(function(resolve, reject) {
//check username
if (success) {return resolve();}
else {return reject();}
}
}
checkUsername('username').then(function() {checkNextThing()});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
You probably have this sorted by now but on the off-chance that you do not then perhaps the following mighht be of use ~ seems to finally work OK! Rather than using jQuery ( I don't use it ) or the traditional XMLHttpRequest this relies upon the newer Fetch api which is based around the notion of Promises
<!doctype html>
<html>
<head>
<title>Have I been pwned?</title>
<script>
var flags={
capture:false,
passive:true,
once:false
};
/* Results from each request will be stored here */
var data={};
/* Whether or not to return FULL data as response */
var truncate=false;
/* Whether or not to include unverified results */
var verified=false;
/* A randomly chosen email address & password for testing */
var username='dave#gmail.com';
var email='andy#yahoo.com';
var password='knickers';
/* No idea who dave#gmail.com or andy#yahoo.com is btw */
/* Request configuration */
var config={
'method':'get',
'mode':'cors'
};
/* Error callback function */
var evtError=function(err){
console.info('oops: %s',err.message);
};
/* Time to wait */
const t=1500;
/* Promise to wait specified time */
const wait = ms => new Promise( resolve => setTimeout( resolve, ms ) );
function create( t, a, p ) {
try{
var el = ( typeof( t )=='undefined' || t==null ) ? document.createElement( 'div' ) : document.createElement( t );
for( var x in a ) if( a.hasOwnProperty( x ) && x!=='innerHTML' ) el.setAttribute( x, a[ x ] );
if( a.hasOwnProperty('innerHTML') ) el.innerHTML=a.innerHTML;
if( p!=null ) typeof( p )=='object' ? p.appendChild( el ) : document.getElementById( p ).appendChild( el );
return el;
}catch(err){
console.warn('createNode: %s, %o, %o',t,a,p);
}
}
function process( data ){
if( typeof( data )=='object' ){
/* use the data */
console.info(data)
var div=document.getElementById('results');
var _username=data.username;
var _email=data.email;
var _pwd=data.password;
/* Process username pwnage */
create('h1',{innerHTML:'Username pwnage for: '+username },div );
for( var n in _username ){
if( typeof( _username[ n ] ) =='object' ){
var keys=Object.keys( _username[ n ] );
var values=Object.values( _username[ n ] );
keys.forEach(function(e,i,a){
create(null,{innerHTML:e+': '+values[i]},div);
});
}
}
/* Process email pwnage */
create('h1',{innerHTML:'Email pwnage for: '+email },div );
for( var n in _email ){
if( typeof( _email[ n ] ) =='object' ){
var keys=Object.keys( _email[ n ] );
var values=Object.values( _email[ n ] );
keys.forEach(function(e,i,a){
create(null,{innerHTML:e+': '+values[i]},div);
});
}
}
/* Finally - password pwnage */
create('h1',{innerHTML:'Password pwnage for: '+password },div );
create(null,{innerHTML:_pwd},div);
}
}
function pwned( event ){
var urls={
username:'https://haveibeenpwned.com/api/v2/breachedaccount/'+encodeURIComponent( username )+'?truncateResponse='+truncate+'&includeUnverified='+verified,
email:'https://haveibeenpwned.com/api/v2/breachedaccount/'+encodeURIComponent( email )+'?truncateResponse='+truncate+'&includeUnverified='+verified,
password:'https://haveibeenpwned.com/api/v2/pwnedpassword/'
};
/* Get the first url using "fetch" rather than XMLHttpRequest */
fetch( urls.username, config ).then( function( res ){
if( res.ok ) return res.json();
if( res.status==404 )return 404;
throw new Error('Failed to check username');
}).then( function( json ){
/* Add the response data to output object */
data.username=json;
}).then( function(){
/* wait for pre-determined time */
return wait( t );
}).then( function(){
/* Get the second url */
fetch( urls.email, config ).then(function( res ){
if( res.ok ) return res.json();
if( res.status==404 )return 404;
throw new Error('Failed to check email');
}).then( function( json ){
/* Add new response data to output object */
data.email=json;
}).then( function(){
/* Loiter, with intent, for a while... twiddle the thumbs etc */
return wait( t );
}).then( function(){
/* Finally get the last url - using POST as I found I was having issues with GET for some reason */
var headers=new Headers();
headers.append('Content-Type','application/x-www-form-urlencoded; charset=UTF-8')
var config={
'mode':'cors',
'method':'post',
'body':'Password='+encodeURIComponent( password ),
'headers':headers
};
fetch( urls.password, config ).then( function( res ){
if( res.ok ) return res.json();
if( res.status==404 )return 404;
throw new Error('Failed to check password');
}).then( function(status){
/* And store the response */
data.password=status==200 ? 'pwned' : 'ok';
return status;
}).then(function(status){
return data;
}).then( function( data ){
/* play with the data */
process.call( this, data );
}).catch( evtError );
}).catch( evtError );
}).catch( evtError );
}
document.addEventListener('DOMContentLoaded',function(){
var bttn=document.querySelector('input[type="button"][name="pwnage"]');
bttn.onclick=pwned;
},flags);
</script>
</head>
<body>
<form method='post'>
<input type='button' name='pwnage' value='Check pwnage' />
<div id='results'></div>
</form>
</body>
</html>
I found that it was possible to lower the timeout period to 1000ms on occasion but seemed a little flaky. The implementation of the method chaining could probably be improved upon but it was my first attempt at a really complicated usage of fetch
Doing further testing lead me to try some of my own email addresses as I'd not been on have I been pwned for quite a while - not a happy bunny after finding a couple had been pwned :(
I am trying to store a session id in a variable that I need in order to make other calls to the external API I am using. I am running it all on the server side, sorry if I messed any of this up, I am new to stack overflow and API use in general.
Here is the code that establishes the connection.
if (Meteor.isServer) {
var devId = "XXXX";
var authKey = "XXXXXXXXXXXXXXXXXXXXXXX";
var utcTime = moment.utc().format("YYYYMMDDHHmmss");
var signature = CryptoJS.MD5(devId + 'createsession' + authKey + utcTime).toString()
HTTP.call('GET', 'http://api.smitame.com/smiteapi.svc/createsessionJson/' + devId + '/' + signature + '/' + utcTime, {
}, function(error, response){
if ( error ) {
console.log( error );
} else {
console.log( response );
}
});
}
Here is the data displayed back into my terminal, how can I grab that session_id?
data:
I20160108-22:23:29.324(-7)? { ret_msg: 'Approved',
I20160108-22:23:29.324(-7)? session_id: '270E9528F59E40DD88F504BE63A9DC6E',
I20160108-22:23:29.325(-7)? timestamp: '1/9/2016 5:23:29 AM' } }
Okay, if anyone ever needs this for something, I figured out what I needed for this. Here is the correct version for what I was trying to do.
HTTP.call('GET', 'http://api.smitame.com/smiteapi.svc/createsessionJson/' + devId + '/' + signature + '/' + utcTime, {
}, function(error, response){
if ( error ) {
console.log( error );
} else {
console.log( response );
var sessionId = response.data.session_id;
}
});
}
I am learning about localStorage and it sounds like each browser gives a domain 5MB.
I wrote this code to cache the data returned from an ajax call and it works. But how do I test to see if localStorage is full? If there is no localStorage space available I imagine that the ajax request should be made again.
Here's my code:
if ( localStorage && localStorage.getItem('myGithub') ) {
console.log('if statement');
console.log( JSON.parse( localStorage.getItem( 'myGithub') ) );
render( JSON.parse( localStorage.getItem( 'myGithub') ) );
}
else {
console.log('else statment');
$.ajax({
url : 'https://api.github.com/users/xxxxxxxxx',
dataType : 'json',
success : function (data) {
if ( localStorage ) {
localStorage.setItem( 'myGithub', JSON.stringify(data) );
}
console.log(data);
render(data);
}
});
}
//Render method for printing the results to the <body> element.
//Returns html from the ajax call or from localStorage.
function render (myObjx) {
var results = '';
for (var prop in myObjx) {
results += '<p>data.' + prop + ' = ' + myObjx[prop] + '</p>';
}
var printData = $('body').html(results);
return printData;
};
You can use the below approach. You can change it as per your requirement.
function checkAvailable(){
var test = 'test';
try {
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch(e) {
return false;
}
}
// And you call below to check the availablity
if(checkAvailable() === true){
// available
}else{
// unavailable
}
I'm using bootstrap's typeahead to get "family's name" from a database. I've read the docs, and I assume everything is ok. However, when I start to search, I 've realized 'search' is too slow and I think it is because the "typeahead" is sending a request for everytime I press a key.
How can I stop sending many requests or try to send the whole string after filling the textbox?
Maybe this information can be helpful
This is the view:
This is the developer tool's view :
This is the JS:
I use a callback to send the request:
var familias = {};
var familiaLabels = [];
//Callback for searching the string
var buscarFamilia = _.debounce(function( query, process ){
$.getJSON( '{/literal}{$smarty.const.FAMILIARES_FE_ROOT}{literal}/ws/familia-ajax/', { q: query }, function ( data ) {
//Clean containers
familias = {};
familiaLabels = [];
//Using some underscore.js for getting data from each item
_.each( data, function( item, ix, list ){
//Fill with the name of each item
familiaLabels.push( item.nombre );
//Fill data for the template
familias[ item.nombre ] = {
id:item.id,
nombre:item.nombre,
padre:item.padre,
madre:item.madre
};
});
//Return the array
process( familiaLabels );
},800);
});
This is the config for the "typeahead":
$('#alu_familia').typeahead({
source: function ( query, process ) {
buscarFamilia ( query, process )
}
, updater: function (itemSelected) {
//This is for getting the id
$( "#alu_fami_id" ).val( familias[ itemSelected].id );
return itemSelected;
}
,
minLength:2,
matcher: function () { return true; }
,highlighter: function(item){
var p = familias[ item ];
var itm = ''
+ "<div class='typeahead_wrapper'>"
+ "<div class='typeahead_labels'>"
+ "<div class='typeahead_primary'>" + p.nombre + "</div>"
+ "<div class='typeahead_secondary'><b>Padre: </b>" + p.padre +"</div>"
+ "<div class='typeahead_secondary'><b>Madre: </b>"+p.madre+ "</div>"
+ "</div>"
+ "</div>";
return itm;
}
});
Thanks in advance.
You can avoid making two Ajax requests for the same query by storing any previously retrieved values in a cache object, like this:
var familias = {};
var familiaLabels = [];
var familiasCache = {};
//Callback for searching the string
var buscarFamilia = _.debounce(function( query, process ){
if (typeof familiasCache[query] !== "undefined") {
return process(familiasCache[query]);
}
$.getJSON( '{/literal}{$smarty.const.FAMILIARES_FE_ROOT}{literal}/ws/familia-ajax/', { q: query }, function ( data ) {
//Clean containers
familias = {};
familiaLabels = [];
//Using some underscore.js for getting data from each item
_.each( data, function( item, ix, list ){
//Fill with the name of each item
familiaLabels.push( item.nombre );
//Fill data for the template
familias[ item.nombre ] = {
id:item.id,
nombre:item.nombre,
padre:item.padre,
madre:item.madre
};
});
// store result in cache
familiasCache[query] = familiaLabels;
//Return the array
process( familiaLabels );
},800);
String.prototype.getLanguage = function() {
$.getJSON('http://ajax.googleapis.com/ajax/services/language/detect?v=1.0&q=' + this + '&callback=?',
function(json) {
return json.responseData.language;
});
};
How can I return the value to the caller value?
Thanks.
EDIT: I've tried this:
String.prototype.getLanguage = function() {
var returnValue = null;
$.getJSON('http://ajax.googleapis.com/ajax/services/language/detect?v=1.0&q=' + this + '&callback=?',
function(json) {
returnValue = json.responseData.language;
});
return returnValue;
};
But it's not working either. It returns null.
I'm assuming you want to use a synchronous event so that your String.prototype.getLanguage() function will just return the JSON. Unfortunately you can't do that with jQuery from a remote API.
As far as I know jQuery does not support synchronous XMLHttpRequest objects, and even if it did, you'd need to have a proxy on your server to make the sync request while avoiding the restrictions of the same-origin policy.
You can, however, do what you want using jQuery's support for JSONP.
If we just write String.prototype.getLanguage() to support a callback:
String.prototype.getLanguage = function( callback ) {
var thisObj = this;
var url = 'http://ajax.googleapis.com/ajax/services/language/detect?v=1.0&q=' + this + '&callback=?';
$.getJSON( url,function(json) {
callback.call(thisObj,json.responseData.language);
});
}
Then we can use the function as such:
'this is my string'.getLanguage( function( language ) {
//Do what you want with the result here, but keep in mind that it is async!
alert(this);
alert(language);
});
var test = function(fun)
{
String.prototype.getLanguage = function() {
.getJSON('http://ajax.googleapis.com/ajax/services/language/detect?v=1.0&q=' + this + '&callback=?',
function(json) {
fun.call(json.responseData.language);
});
};
};
test(retCall);
var retCall = function(xjson){
alert(xjson);
};