I'm trying to send a several requests from a single instance of XMLHttpRequest object, like this:
GC3D.Application.prototype.setMapTiles = function() {
var ajaxInstance = new GC3D.Ajax();
for ( var i = 0, j = 0; i < 10; i++ ) {
var urlFinal = this.prepareRequestForTileDownload( i, j );
ajaxInstance.sendRequest({
id: { i: i, j: j },
HttpMethod: 'GET',
UrlEndpoint: urlFinal
}).then( function( item ) {
//...
});
}
};
It doesn't work and sends only the one request. BUT! If to change source code to the next:
for ( var i = 0, j = 0; i < 10; i++ ) {
var ajaxInstance = new GC3D.Ajax();
...
It begins to send so many requests as the total number of for loop iterations and all is working perfectly. I prefer to get known some features. As in C# development in my past I never create a new instance of some TCP socket in loop, if I want to make it asynchronously - I create a single instance with delegates to async functions and if there is a similar situation in C# project like in JavaScript code here in the question's content, because it's doesn't produce new objects in for loop, it takes less memory and as for architecture it's represented as more clean and good solution.
GC3D.Ajax is defined as the next prototype:
GC3D.Ajax = function() {
this.httpRequest = undefined;
this.listExceptions = undefined;
this.init();
};
GC3D.Ajax.prototype.init = function() {
this.listExceptions = [];
if ( window.XMLHttpRequest ) this.httpRequest = new XMLHttpRequest();
else if ( window.ActiveXObject ) {
try {
this.httpRequest = new ActiveXObject( 'Microsoft.XMLHTTP' );
}
catch ( exception ) {
this.listExceptions.push( exception );
try {
this.httpRequest = new ActiveXObject( 'Msxml2.XMLHTTP' );
}
catch ( exception ) {
this.listExceptions.push( exception );
try {
this.httpRequest = new ActiveXObject( 'Microsoft.XMLHTTP' );
}
catch ( exception ) {
this.listExceptions.push( exception );
}
}
}
}
if ( !this.httpRequest ) {
console.error( 'Can\'t create a HTTP Request instance for AJAX! Possible problems:' );
console.error( this.listExceptions );
}
};
GC3D.Ajax.prototype.sendRequest = function( properties ) {
var defer = new GC3D.Defer();
if ( this.httpRequest !== undefined ) {
this.httpRequest.onreadystatechange = function() {
if ( this.httpRequest.readyState === 4 && this.httpRequest.status === 200 ) {
var objectOutput = {};
objectOutput.id = properties.id;
objectOutput.content = this.httpRequest.responseText;
defer.resolve( objectOutput );
}
else {
var message = 'There was a problem with the request in GC3D.Ajax.';
defer.reject( message );
}
}.bind( this );
this.httpRequest.open( properties.HttpMethod, properties.UrlEndpoint );
this.httpRequest.send();
}
else console.error( 'HTTP Request instance isn\'t defined!' );
return defer.promise;
};
GC3D.Ajax.prototype.get = function() {
return this.httpRequest;
};
The XMLHttpRequest API is very well defined and leaves no doubt about this behaviour.
The definition of the open() method, step 13, states that the method should:
Terminate the request.
http://www.w3.org/TR/XMLHttpRequest/#the-open()-method
Opening a debugger in the browser and having a look at the network traffic reveals that, in fact, several requests are being send asynchronously, with each but the last request being canceled by the next one.
In this case, the open() method is being invoked repeatedly on the same instance of an XMLHttpRequest inside the sendRequest method of ajaxInstance.
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'm trying to run this code:
function smConnect() {
ws = new WebSocket('ws://127.0.0.1:1805/');
delete ws.URL;
ws.onopen = function(response) {};
ws.onmessage = function(response) {};
ws.onclose = function(response) {};
ws.onerror = function(error) {};
}
smConnect();
ws.send('message', 'hi');
But it returns me this error:
Uncaught InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state.
What could be the problem?
You could do it like this, it adds some logging, the sending of the info you can handle outside of your constructor, and you would also have abstracted away the SocketWrapper in its own namespace (ok, yes, it's in window now :))
You can check the dev tools (F12 in most browsers) to see whats happening in the logs/error, eg: here it throws an error, as there is no socket available :)
And you are not required to give a value for all events, just the ones you need (your case onopen + maybe onmessage?)
(inside the defined callbacks, this would point towards the socket, not the SocketWrapper, the SocketWrapper also doesn't offer the ws variable, it's kind of private, but it should do i guess)
There is a send method on the SocketWrapper, that throws an error, when you send to a closed stream, but in case it wasn't opened yet, it will queue the messages till it gets opened and then empty the queue to the websocket (so in a sence, you are not required to set the onopen callback, just adding it using the send method should be fine ;)
(function(nameSpace) {
function createMethod(method, options, stateCallback) {
var that = this;
this[method] = function() {
if (stateCallback && stateCallback.apply) {
stateCallback(method);
}
console.info(method);
if (options[method] && options[method].apply) {
options[method].apply(that, arguments);
}
};
}
function SocketWrapper(options) {
var ws,
events = ['onopen', 'onmessage', 'onclose', 'onerror'],
i, len, prop = {
opened: false,
closed: false,
error: false
},
method;
if (typeof options === 'undefined' || !options) {
throw 'ArgumentException: please add default constructor options';
}
this.queue = [];
this.onEventTrigger = function(eventName) {
var i, len;
if (eventName === 'onopen') {
prop.opened = true;
prop.closed = false;
// openend send queue
if (this.queue.length > 0) {
for (i = this.queue.length; --i >= 0;) {
this.send.apply(this, this.queue[0]);
this.queue.splice(0, 1);
}
}
}
if (eventName === 'onerror') {
prop.error = true;
}
if (eventName === 'onclosed') {
prop.opened = false;
prop.closed = true;
}
};
this.init = function() {
var cb = this.onEventTrigger.bind(this);
ws = new WebSocket(options.url);
for (i = 0; i < events.length; i++) {
method = events[i];
createMethod.apply(ws, [method, options, cb]);
}
};
this.send = function() {
if (prop.closed) {
throw 'InvalidOperation: Cannot send messages to a closed Websocket!';
}
if (!prop.opened) {
this.queue.push(arguments);
} else {
ws.send.apply(ws, arguments);
}
};
this.init();
return this;
}
window.SocketWrapper = SocketWrapper;
}(window));
var socket = new window.SocketWrapper({
url: 'ws://127.0.0.1:1805',
onopen: function() {
this.send('message', 'hi');
},
onmessage: function() {
console.log(arguments);
},
onclose: function() {
socket = null;
},
onerror: function() {
console.log('error occured, oh no!');
console.error(arguments);
}
});
socket.send('i am message send to soon, but since i check the state of the ws object, i will be queued and send when appropriate');
I'm an old 'C' programmer and with Javascript always struggle populating my data following ajax calls; i.e. I always resort to using global references. I'd rather be able to pass in the objects I need to update. Here's one example of what I do now - 'app' is the global (I'd have used a pointer in C :))
treeMapp.login = function ( dialog_div, form_div, server_call ) {
// validate the fields
if ( 0 ) {
}
else {
// send to server & parse JSON response (single line)
var jqxhr =
$.getJSON( server_call,
$( "#" + form_div ).serialize() )
.done( function( data, status ) {
if( status == 'success' ) {
// hack!?
app.user.username = data.username;
app.user.organisation = data.organisation;
app.user.loggedIn = true;
//close the dialog
$( '#' + dialog_div ).dialog('close');
}
else {
// login failed
alert( "login failed!" );
}
})
.fail( function() {
alert( "login: server error" );
}); // end var jqxhr =
} // end else (field validation ok)
}; // end treeMapp.login()
What's the best way of updating passed in parameters?
thanks
mini
You can pass app as an argument to your treeMapp.login function, then within the scope of it it would be local.
treeMapp.login = function ( dialog_div, form_div, server_call, app )
You could assign the data object returned by your jQuery result to app.user, thus avoiding the need for element by element assignment.
i.e. app.user = data
However, normally you ensure the global object can self initialise through a method, or you pass a reference to the global object to the method so it can initialise. Directly using assignment to global variables is (with a few exceptions) poor programming in Javascript as in any other language
UPDATE: the following shows an amalgamation of the answers...
treeMapp.login = function ( dialog_div, form_div, server_call, theapp ) {
var app = theapp; // may be needed for scope issue..
// validate the fields
if ( 0 ) {
}
else {
// send to server & parse JSON response (single line)
var jqxhr =
$.getJSON( server_call,
$( "#" + form_div ).serialize() )
.done( function( data, status ) {
if( status == 'success' ) {
// not a hack
$.extend(this.app.user, data, { loggedIn: true })
//close the dialog
$( '#' + dialog_div ).dialog('close');
}
else {
// login failed
alert( "login failed!" );
}
})
.fail( function() {
alert( "login: server error" );
}); // end var jqxhr =
} // end else (field validation ok)
}; // end treeMapp.login()
I have the following code, which throws a InvalidStateError in Firefox (when calling objectStore).
The message is A mutation operation was attempted on a database that did not allow mutations..
Strangely, when I place the creation of the transaction inside the getBlob callback it seems to work.
function saveFile( fileEntry ) {
var transaction = this;
transaction.onerror = function( event ) {
console.log( event );
};
fileEntry.getBlob( "", function( blob ) {
var store = transaction.objectStore( STORE_NAME );
try {
var request = store.put( blob, fileEntry.getFullname() );
request.onsuccess = function( event ) {
console.log( "file: " + fileEntry.getFullname() );
++(progressBar[0].value);
};
request.onerror = function( event ) {
console.log( "error in file " + event );
++(progressBar[0].value);
};
} catch( ex ) {
console.log("getBlob " + ex );
}
}, function() {
}, true );
}
function saveRecursive( dirEntry, missingFiles ) {
var db = this;
var transaction = db.transaction( [STORE_NAME],
MODES.READ_WRITE );
for( var i in dirEntry.children ) {
var entry = dirEntry.children[i];
if( entry.directory ) {
createDirectory( dirEntry, entry );
saveRecursive.call( db, entry, missingFiles );
continue;
}
var index = missingFiles.indexOf( entry.getFullname() );
if( index == -1 )
continue;
// Still missing - add
missingFiles.splice( index, 1 );
saveFile.call( transaction, entry );
}
}
Could someone explain to me, why this is not working?
If getBlob is async function, this will not work:
fileEntry.getBlob( "", function( blob ) {
var store = transaction.objectStore( STORE_NAME );
because transaction is already committed, when you get the blob.
Not sure if blob supported in indexeddb yet.
I am developing a web-app using both WebSQL and IndexedDB. IndexedDB works well in browsers that support it, and my WebSQL implementation works in most browsers, however on Android javascript execution seems to be halting inside db.transaction.
Here is my code. I receive no console logs after "Initiating transaction..." (and no further JS seems to execute).
window.store.getFile = function( filename, callback ) {
var db = window.store.websql.db;
console.log('Initiating transaction...');
db.transaction(function (tx) {
var filename2 = filename;
var query = 'SELECT * FROM file WHERE filename = "'+filename+'"';
console.log('Executing query:'+query);
tx.executeSql(query, [], function(t, r) {
if ( r.rows.length > 0 ) {
var len = r.rows.length, i;
for (i = 0; i < len; i++) {
var filename = r.rows.item(i).filename;
var data = r.rows.item(i).data;
if ( callback ) callback( true, filename2, data );
break; // Break here as only one result should be returned
}
} else {
if ( callback ) callback( false, filename2, null );
}
}, function(t, e) {
console.log(e);
if ( callback ) callback( false, filename2, null );
});
}, function(e) {
console.log('Database transaction error: '+e);
if ( callback ) callback( false, filename, null );
}, function() {
console.log('Transaction success');
});
console.log('Transaction initiated...');
};
(P.S. I also welcome any critique of my code...)
The error you are chasing is probably raised on the db level error handler (passed in openDatabase). Some android phones just don't accept null or undefined for that error handler the the symptoms are what you described. No error, no success just nothing...
Also: your success block should be in try/catch to prevent that error to bubble up automatically.
Also: note that the return value to success handler (false/true) will determine what happens when an error is thrown (rollback or just error without rollback).