Don't understand flow using xmlhttp

M

marty3d

Hi!
I'm building a simple newsletter application using Classic ASP. In
order to avoid page timeout, I thought of using AJAX to send each mail
asynchronously.

So, my thought was
1. create an array using an ordinary asp recordset parsed to js.
2. loop through the array and for each e-mail address do a http
request for a ASP page doing the validation and sending the mail to
the current email address

This worked out ok, the mails go out, but not in the order they are
sent to the for-loop. A more serious problem appear when I try to add
a emails-to-go-counter and some "what's-currently-happening" in the
loop.

Here's my real problem exemplified:
In the below example, I set the innerHTML of resultdiv to 'Wait...' .
When the loop through mail addresses is done, it should be changed to
'Done!'. But what's really happening is that the innerHTML is changed
way sooner so when it says 'Done!', the mails are still processed.
Is there a way of not continuing the loop until the response has
arrived or something? After several years of programming, this
behaviour is new to me :(

Here's a simplified code for the process:
<script>
function myFunction(){
var aList = new Array();
var resultdiv = document.getElementById('result');
resultdiv.innerHTML='Wait...';

aList[0] = new Array('1', (e-mail address removed)');
aList[1] = new Array('2', '(e-mail address removed)');
aList[2] = new Array('3', '(e-mail address removed)');

var iNumberOfItems = aList.length;
for(var x=0;x<a0_len;x++){
makeHttpRequest('send_ajax.inc.asp?ajax_send='+aList[x]
[1]+'&issue='+<%=issueid%>, 'fAlert');
}
resultdiv.innerHTML='Done!';
}

//This is tastelessly copied from an example found on the net... I
will make my own as soon as I understand the above problem :)
function makeHttpRequest(url, callback_function, return_xml){
var http_request = false;
if (window.XMLHttpRequest) { // Mozilla, Safari,...
http_request = new XMLHttpRequest();
if (http_request.overrideMimeType) {
http_request.overrideMimeType('text/xml');
}
}
else if (window.ActiveXObject) { // IE
try {
http_request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
http_request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
}

if (!http_request) {
alert('Unfortunatelly you browser doesn\'t support this feature.');
return false;
}
http_request.onreadystatechange = function() {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
if (return_xml) {
eval(callback_function + '(http_request.responseXML)');
} else {
eval(callback_function + '(http_request.responseText)');
}
} else {
alert('There was a problem with the request.(Code: ' +
http_request.status + ')');
}
}
}
http_request.open('GET', url, true);
http_request.send(null);
}
function fAlert(text){
//Do something with the response
}
</script>

Thanks alot!
/Martin
 
T

Thomas 'PointedEars' Lahn

marty3d said:
In the below example, I set the innerHTML of resultdiv to 'Wait...' .
When the loop through mail addresses is done, it should be changed to
'Done!'. But what's really happening is that the innerHTML is changed
way sooner so when it says 'Done!', the mails are still processed.
Is there a way of not continuing the loop until the response has
arrived or something? After several years of programming, this
behaviour is new to me :(

"Marty, you're not thinking fourth dimensionally!" ;-)

You are issuing an HTTP request with *asynchronous* handling of the
request-response-chain (as indicated by the true value of the third argument
of XHR::eek:pen()), so preliminary execution of the next loop while the
previous request is still being performed should not come to you as a
surprise. You could switch to synchronous handling (false value), but that
would stall the user agent until the client receives the response, and in
your situation it would not help anyway.

Because the solution is to not have your server send the response until the
operation completes. However, you are ignoring how sending e-mails usually
works with an MTA: the e-mails are put into a queue, tried to be sent when
all other mails that are before it in the queue have been processed (but not
necessarily sent) and if that fails for some reason, kept in the queue for a
certain time (for example, the postfix default is 5 days) until the message
is considered undeliverable.

Therefore, the correct answer to your question is "probably not".


PointedEars
 
T

Thomas 'PointedEars' Lahn

Thomas said:
You are issuing an HTTP request with *asynchronous* handling of the
request-response-chain (as indicated by the true value of the third argument
of XHR::eek:pen()), so preliminary execution of the next loop while the ^^^^^^^^^^^
previous request is still being performed should not come to you as a
surprise. [...]

The word I was looking for was "premature". Sorry if I caused confusion.


PointedEars
 
M

marty3d

Thomas said:
You are issuing an HTTP request with *asynchronous* handling of the
request-response-chain (as indicated by the true value of the third argument
of XHR::eek:pen()), so preliminary execution of the next loop while the
^^^^^^^^^^^

previous request is still being performed should not come to you as a
surprise. [...]

The word I was looking for was "premature". Sorry if I caused confusion.

PointedEars

No confusion added to the stack :)
But let me try to change the angle of my question a bit. How about
forgetting that the app is mailing, but say, just making a simple
server call fetching a recordset. So, can one count the number of
requests OR the responses, and if > 5 wait for a number of seconds,
then issue a new batch?
Some pseudo code:

for x = 0 to 20
for y = 0 to 5
make http request
return httpresponse
next
while httpresponse count > 0
wait 1 second
end while
next

Is this possible at all or should I go about another way?
 
T

Thomas 'PointedEars' Lahn

marty3d said:
But let me try to change the angle of my question a bit. How about
forgetting that the app is mailing, but say, just making a simple
server call fetching a recordset. So, can one count the number of
requests OR the responses, and if > 5 wait for a number of seconds,
then issue a new batch?
Some pseudo code:

for x = 0 to 20
for y = 0 to 5
make http request
return httpresponse

Returning a sensible value right after issuing the request is not possible
with asynchronous request handling as the request is already in progress.
And you don't want the synchronous one, trust me.
next
while httpresponse count > 0
wait 1 second

Your readystatechange event listener would have to keep a record about the
number of requests already made (in a globally available property) and then
use window.setTimeout() to delay the execution of the next request for the
specified time.

This would require that subsequent requests are issued from the event
listener of a previous request instead of a loop. IIRC, I have posted an
example of that before.

But I don't see your approach addressing your problem.
end while
next

Is this possible at all or should I go about another way?

The way you have written it is only possible with synchronous request handling.


PointedEars
 
N

Nick Fletcher

Some pseudo code:

for x = 0 to 20
for y = 0 to 5
make http request
return httpresponse
next
while httpresponse count > 0
wait 1 second
end while
next

Is this possible at all or should I go about another way?

If I understand you correctly, you're trying to send out the emails in
batches of 5 while waiting a second between each batch. Each email
should be sent in order.

You should be able to do the same thing with a queue while still being
asynchronous. For example:

var content = ""; // this should be the email content
var emailQueue = []; // populate this
var batchCount = 0;
var failCount = 0;

function processNextEmail() {
if (emailQueue.length == 0) {
/* Do something to indicate emailing is complete */
return;
}

// Get first element and remove it from the queue
var email = emailQueue[0];
emailQueue.splice(0, 1);

// Send email asynchronously
doAjax({
url: "mailer.asp?email=" + email + "&content=" + content,
// Handle success
success: function () {
failCount = 0;

// If we've sent 5 emails, wait a second before sending
more
batchCount++;
if (batchCount >= 5) {
batchCount = 0;
setTimeout(function() {
processNextEmail();
}, 1000);
} else {
processNextEmail();
}
},
// Try 3 emailing to this address 3 times before aborting
error: function () {
if (failCount >= 3) {
alert("Failed to send email to '" + email + "'.
Aborting..");
return;
}
failCount++;

// Put failed email back in the front of the queue and try
again
emailQueue.splice(0, 0, email);
processNextEmail();
}
});
}

processNextEmail();
 
M

marty3d

Some pseudo code:
for x = 0 to 20
for y = 0 to 5
make http request
return httpresponse
next
while httpresponse count > 0
wait 1 second
end while
next
Is this possible at all or should I go about another way?

If I understand you correctly, you're trying to send out the emails in
batches of 5 while waiting a second between each batch. Each email
should be sent in order.

You should be able to do the same thing with a queue while still being
asynchronous. For example:

var content = ""; // this should be the email content
var emailQueue = []; // populate this
var batchCount = 0;
var failCount = 0;

function processNextEmail() {
if (emailQueue.length == 0) {
/* Do something to indicate emailing is complete */
return;
}

// Get first element and remove it from the queue
var email = emailQueue[0];
emailQueue.splice(0, 1);

// Send email asynchronously
doAjax({
url: "mailer.asp?email=" + email + "&content=" + content,
// Handle success
success: function () {
failCount = 0;

// If we've sent 5 emails, wait a second before sending
more
batchCount++;
if (batchCount >= 5) {
batchCount = 0;
setTimeout(function() {
processNextEmail();
}, 1000);
} else {
processNextEmail();
}
},
// Try 3 emailing to this address 3 times before aborting
error: function () {
if (failCount >= 3) {
alert("Failed to send email to '" + email + "'.
Aborting..");
return;
}
failCount++;

// Put failed email back in the front of the queue and try
again
emailQueue.splice(0, 0, email);
processNextEmail();
}
});

}

processNextEmail();

Thanks all for answering!

I actually found an answer to this, using the setTimeout function to
slightly delay each xmlhttp call. But it took me a while to understand
the flow of things :)

Thank you again, have a good weekend!
/Martin
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,981
Messages
2,570,188
Members
46,731
Latest member
MarcyGipso

Latest Threads

Top