Chrome Extension - XHR URLs from an array - javascript

I have saved some subject URL keys to localStorage and now want to cycle through them a get content of each of them.
// Walk through saved subjects
allSubjects = JSON.parse(localStorage.getItem('subjects'));
var i = 0;
var ii = 0;
var xhrIn = [];
for (i = 0; i < allSubjects.length; i++) {
xhrIn[i] = new XMLHttpRequest();
xhrIn[i].open("GET", "https://myserver.com/" + allSubjects[i], true);
xhrIn[i].onreadystatechange = function() {
if (xhrIn[ii].readyState == 4) {
console.log(xhrIn[ii].responseText);
percents = Math.floor((((ii+1)/allSubjects.length)*100));
$("div#status").text('Downloading... ' + percents + '%');
// Final phase
if ((ii+1) == allSubjects.length) {
$("div#status").text("All downloaded and saved in console.");
}
ii++;
}
};
xhrIn[i].send();
}
}
This is not working, it catches only the first URL, after that my Console log says, that all other URLs were contacted, but xhrIn[i].onreadystatechange closure has never been executed.
It looks like a little bit magical for me... Can anyone explain me this behavior?

Yes, I agree with epascarello, there are some fundamental problems with this code. There is not guarantee that the callbacks assigned to run in the order you are intending. If you want them to run in order, try something like this:
var urls = ['test.php', 'test2.php', test3.php'];// and so on
function myRequest(){
if(urls.length > 0){
var nextUrl = urls.pop(); //TAKE THE NEXT URL (pop() removed from the end)
var xhrIn = new XMLHttpRequest();
xhrIn.open("GET", "https://myserver.com/" + nextUrl, true);
xhrIn.onreadystatechange = function() {
if (xhrIn.readyState == 4) {
console.log(xhrIn.responseText);
//THE FOLLOWING LINE WILL OBVIOUSLY NOT WORK ANY MORE
//percents = Math.floor((((ii+1)/urls.length)*100));
//$("div#status").text('Downloading... ' + percents + '%');
myRequest(); //RECUR WHEN DONE WITH PREVIOUS REQUEST
}
}
}
}

Haven't tested, but should be something like this:
for (i = 0; i < allSubjects.length; i++) {
xhrIn[i] = new XMLHttpRequest();
xhrIn[i].open("GET", "https://myserver.com/" + allSubjects[i], true);
xhrIn[i].onreadystatechange = (function(ii) {
return function() {
if (xhrIn[ii].readyState == 4) {
console.log(xhrIn[ii].responseText);
}
};
})(i);
xhrIn[i].send();
}
Your current percent calculation will jump all over the place as callback functions could be called in random order. You probably would need to rethink that part (create some global counter).

Related

multiple XMLHttpRequest display issue

I was successfully able to make multiple XMLHttpRequest and this is my code:
var index = ["status", "net"]
for (var i = 0; i < index.length; i++) {
var url = "http://localhost:3000/api/" + index[i];
let http = new XMLHttpRequest();
http.open("GET", url);
http.onreadystatechange = function () {
if (http.readyState === XMLHttpRequest.DONE && http.status == 200) {
console.log(http.responseText);
var respond = JSON.parse(http.responseText);
console.log("respond is" + respond.name);
console.log("respond is" + respond.dhcp_on);
console.log("respond is" + respond.sn);
document.getElementById('device-name').value = respond.name;
}
}
http.send();
}
I am getting the proper value in the console but as I am trying to display the value in my input element with id (device-name) I get undefined.
FYI this is how my console log is looking like
{"name":"DEV 1","sn":"123456789","uptime":123}
respond isDEV 1
respond isundefined
respond is123456789
{"dhcp_on":true,"ip":"1.2.3.4","mask":"1.2.3.4","gw":"1.2.3.4","dns":"1.2.3.4"}
respond isundefined
respond istrue
respond isundefined
Could anyone explain why?
Thanks.
The problem is that you're writing to the same element repeatedly in a loop. The response to your first request has name:
{"name":"DEV 1","sn":"123456789","uptime":123}
but the response to your second response does not:
{"dhcp_on":true,"ip":"1.2.3.4","mask":"1.2.3.4","gw":"1.2.3.4","dns":"1.2.3.4"}
Since the loop does
document.getElementById('device-name').value = respond.name;
both times, you're overwriting the result of the first (which has the name) with the result of the second (which doesn't), exactly like this:
const results = [
{"name":"DEV 1","sn":"123456789","uptime":123},
{"dhcp_on":true,"ip":"1.2.3.4","mask":"1.2.3.4","gw":"1.2.3.4","dns":"1.2.3.4"}
];
// (Using your loop structure and variable names)
for (var i = 0; i < results.length; ++i) {
var respond = results[i];
document.getElementById("device-name").value = respond.name;
}
<input type="text" id="device-name">
You only want to set device-name when processing the first response, not the second. You can do that by checking i, or by checking whether name is in the response:
const results = [
{"name":"DEV 1","sn":"123456789","uptime":123},
{"dhcp_on":true,"ip":"1.2.3.4","mask":"1.2.3.4","gw":"1.2.3.4","dns":"1.2.3.4"}
];
// (Using your loop structure and variable names)
for (var i = 0; i < results.length; ++i) {
var respond = results[i];
if ("name" in respond) {
document.getElementById("device-name").value = respond.name;
}
}
<input type="text" id="device-name">
In this case, if ("name" in respond) { could be if (respond.hasOwnProperty("name")) { or if (respond.name !== undefined) {, whatever you prefer. in checks the whole prototype chain, but the chain is quite short when the object came from JSON.parse.
so by simply adding an if condition to check if the id i want to display is in my returned object or not as #T.J. Crowder explained it worked perfectly
var index = ["status", "net"];
for (var i = 0; i < index.length; i++) {
var url = "http://localhost:3000/api/" + index[i];
let http = new XMLHttpRequest();
http.open("GET", url);
http.onreadystatechange = function () {
if (http.readyState === XMLHttpRequest.DONE && http.status == 200) {
console.log(http.responseText);
var respond = JSON.parse(http.responseText);
if ("name" in respond) {
document.getElementById('device-name').value = respond.name;
}}
}
http.send();
}

How to make Node wait for promise to complete in an infinite while loop?

The snoowrap library returns a promise when a function a call is made using it. I would like for node to wait until the call is complete and then execute its callback function. How can I do that? I've tried different forms of using setTimeout, wait.for library, and other solutions but none have worked.
while(1){
for (var i = 0; i < allowableTimes.length; i++) {
if (new Date().getTime() == allowableTimes[i].getTime()) {
reddit.getHot('aww', {limit: 1}).then(sendToSlack);
}
}
}
function sendToSlack(res){
var url = res[0].url;
var title = res[0].title;
bot.sendWebhook({
username: "bawwt",
icon_emoji: ":smile_cat:",
text: "<" + url + "|" + title + ">",
channel: "#random"
});
}
SOLVED: Here is the solution below that worked for me based on the code in the accepted answer:
if (!err) {
reddit.getHot('aww', {limit: 1}).then(handleReddit);
}
});
function handleReddit(res) {
for (var i = 0; i < allowableTimes.length; i++) {
if (validTime(allowableTimes[i])) {
sendToSlack(res);
}
}
queryReddit();
}
function validTime(allowableTime) {
var date = new Date();
var hour = date.getHours();
var minute = date.getMinutes();
var allowableHour = allowableTime.getHours();
var allowableMinute = allowableTime.getMinutes();
return hour == allowableHour && minute == allowableMinute;
}
function queryReddit() {
setTimeout(function() {reddit.getHot('aww', {limit: 1}).then(handleReddit);}, 60000);
}
function sendToSlack(res){
var url = res[0].url;
var title = res[0].title;
bot.sendWebhook({
username: "bawwt",
icon_emoji: ":smile_cat:",
text: "<" + url + "|" + title + ">",
channel: "#random"
});
}
I believe the reason your callback never gets called is because you never give the node runtime a chance to do that. It's a single thread and you are infinitely using it up with your while(1). It will not have a chance to actually handle the resolving network call that the promise responds to as you are keeping the thread busy with more
As mentioned in the comment: In node, you shouldn't block the rest of the server on a callback. Instead, you should make sendToSlack a recursive function that deals with the callback and fires off another call to reddit.getHot() and get rid of the loop
Conceptual code:
var allowableTimes = 20;
var times = 0;
function handleReddit(res)
{
sendToSlack(res);
times = times + 1;
if(times < allowableTimes)
{
reddit.getHot('aww', {limit: 1}).then(handleReddit);
}
}
reddit.getHot('aww', {limit: 1}).then(handleReddit);
Just off hand code

Using Multple Asyncronous Xmlhttprequests to Create/Update Microsoft Dynamics CRM Order Detail Records Not Working

I am trying to send multiple asyncronous xmlhttprequest's using the oData endpoint. Some of them are creating Order details and are updating Order details in Microsoft Dynamics CRM 2013.
If I use the developer tools and manually trace through the code it works fine. However, if I run it from my web resource I constantly get 500 responses from the server. Some of the requests complete correctly, while the others fail.
I am looking for a purely javascript solution. I have tried Googling it and looking at multiple posts on stackoverflow but to no avail. I have used Fiddler2 but the response text is 'Generic SQL Error'. If I run the request again in the composer, it works just fine. Could it be a db locking issue?
Thanks in advance and I can provide more info if need be.
Here is my code with the for-loop:
var updateDetails = function (data) {
var table = document.getElementById("selectedItemTable");
var tbody = table.getElementsByTagName("tbody")[0];
var upsaleQty, qty;
var salesOrderDetailId;
for (var i = 0; i < tbody.childElementCount; i++) {
var prodName = tbody.rows[i].cells[0].innerHTML;
var match = false;
for (var j = 0; j < data.length; j++) {
if (prodName === data[j].product_order_details.tf_ShortName) {
match = true;
upsaleQty = data[j].tf_UpsaleQty ? parseFloat(data[j].tf_UpsaleQty) : 0;
qty = parseFloat(data[j].Quantity) + parseFloat(tbody.rows[i].cells[1].innerHTML);
salesOrderDetailId = data[j].SalesOrderDetailId;
}
}
if (!match) {
var productQuery = odataBaseUrl + "/ProductSet?$filter=tf_ShortName eq '" + prodName + "'&$select=Name,tf_ShortName,ProductId,DefaultUoMId";
performRequest(productQuery, createDetail);
} else {
upsaleQty = upsaleQty + parseFloat(tbody.rows[i].cells[1].innerHTML);
// Update Order Detail
var updateObj = {};
updateObj.tf_UpsaleQty = upsaleQty.toFixed(5);
updateObj.Quantity = qty.toFixed(5);
var updateDetail = JSON.stringify(updateObj);
console.dir("Update " + prodName + ":" + updateDetail);
createUpdateDetail(true, salesOrderDetailId, updateDetail);
}
}
makePdf();
document.getElementById("save").style.visibility = "hidden";
}
Here is the code that sends the create/update request:
var createUpdateDetail = function (update, orderDetailGuid, json) {
var odataReq = odataBaseUrl + "/SalesOrderDetailSet";
if (update) {
odataReq += "(guid'" + orderDetailGuid + "')";
}
var oReq = getXMLHttpRequest();
if (oReq != null) {
oReq.open("POST", encodeURI(odataReq), true);
oReq.setRequestHeader("Accept", "application/json");
oReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
if (update) {
oReq.setRequestHeader("X-HTTP-Method", "MERGE");
}
oReq.send(json);
} else {
alert('Error in creating request.');
}
}
Here is the perform request function:
var performRequest = function (odataUrl, onReadyFunction, concatResults) {
console.dir(odataUrl);
var oReq = getXMLHttpRequest();
if (oReq != null) {
oReq.open("GET", encodeURI(odataUrl), true);
oReq.setRequestHeader("Accept", "application/json");
oReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
oReq.onreadystatechange = function () {
if (oReq.readyState == 4 && oReq.status == 200) {
// Parse the result
if (!concatResults) {
concatResults = new Object();
concatResults.results = new Array();
}
oReq.onreadystatechange = null; //avoids memory leaks
console.dir(oReq.responseText);
var result = window.JSON.parse(oReq.responseText).d;
for (var i = 0; i < result.results.length; i++) {
concatResults.results.push(result.results[i])
}
if (result.__next != null)
performRequest(decodeURI(result.__next), onReadyFunction, concatResults);
else
onReadyFunction(concatResults.results);
}
};
oReq.send();
} else {
alert('Error in creating request.');
}
}
Create Detail function:
var createDetail = function (data) {
// Create Order Detail
var table = document.getElementById("selectedItemTable");
var tbody = table.getElementsByTagName("tbody")[0];
var qty = 0;
for (var i = 0; i < tbody.childElementCount; i++) {
if (data[0].tf_ShortName === tbody.rows[i].cells[0].innerHTML) {
qty = parseFloat(tbody.rows[i].cells[1].innerHTML).toFixed(5);
}
}
var createObj = {};
createObj.SalesOrderId = { Id: orderGuid, LogicalName: "salesorder" };
createObj.ProductId = { Id: data[0].ProductId, LogicalName: "product" };
createObj.Quantity = qty;
createObj.tf_UpsaleQty = qty;
createObj.UoMId = { Id: data[0].DefaultUoMId.Id, LogicalName: data[0].DefaultUoMId.LogicalName };
var createDet = JSON.stringify(createObj);
console.dir("Create:" + createDet);
createUpdateDetail(false, "", createDet);
}
I think ExecuteMultipleRequest to SOAP endpoint it's your solution. As a result you get only one service call instead making multiple service call which is currently implemented in your solution.
In case you avoid generating request string to soap endpoint in your code I would like to recommend you this JS library.
I ended up creating an array and treated it like a queue. I put all of the odata requests to create and update the Order Details in the array and then processed them sequentially. The onreadystatechange would trigger the next request. Granted, it's not as efficient as running the processed in parallel, but it worked for my needs and resolved the 500 errors. Thanks for your help.

Variable from for loop always returns 0

I am reasonably new to node.js / sails.js and have run into a problem that I know the answer is simple but I cannot seem to work it out.
My code is as follows
SendCompleted : function(req,res)
{
var updated = 0;
var params = req.params.all();
var dt = JSON.parse(decodeURI(params.id));
var connection = new sql.Connection(testrmis, function (err)
{
if (err) {
}
for(var i = 0; i < dt.length; i++) {
var obj = dt[i];
var request = new sql.Request(connection);
request.stream = true;
request.input('branchid', sql.Int(), obj.branch_id);
request.input('picklistid', sql.Int(), obj.picklist_id);
request.input('scanned',sql.Int(),obj.scanned);
request.input('expected', sql.Int(),obj.expected);
request.input('poscode', sql.VarChar(),obj.poscode);
request.input('label', sql.VarChar(), obj.label);
request.input('dt', sql.VarChar(), obj.dt);
request.execute('WAREHOUSE_InsertPiPackData');
request.on('done', function(returnValue) {
updated = updated + returnValue;
console.log(updated);
});
}
res.send("[{\"ReturnValue\":" + updated + "}]");
});
}
I am sending in 4 lines of results and my console.log(updated) counts up as it should for each line, e.g 1,2,3,4
However the res.send result for updated is always 0.
Could anyone please explain why this is happening? My var updated is outside of my loop and this is getting updated correctly, however when the loop is finished it seems to get reset to 0?
returnValue == ##rowcount from the stored procedure
request is async so
res.send("[{\"ReturnValue\":" + updated + "}]");
gets executed even before you get the callback on request as JS doesn't wait for the callback and executes the next line. What you can do is use a counter and place your res.send inside for loop.
SendCompleted : function(req,res)
{
var updated = 0;
var params = req.params.all();
var dt = JSON.parse(decodeURI(params.id));
var connection = new sql.Connection(testrmis, function (err)
{
if (err) {
}
var count = dt.length;
for(var i = 0; i < dt.length; i++) {
var obj = dt[i];
var request = new sql.Request(connection);
request.stream = true;
request.input('branchid', sql.Int(), obj.branch_id);
request.input('picklistid', sql.Int(), obj.picklist_id);
request.input('scanned',sql.Int(),obj.scanned);
request.input('expected', sql.Int(),obj.expected);
request.input('poscode', sql.VarChar(),obj.poscode);
request.input('label', sql.VarChar(), obj.label);
request.input('dt', sql.VarChar(), obj.dt);
request.execute('WAREHOUSE_InsertPiPackData');
request.on('done', function(returnValue) {
count--;
updated = updated + returnValue;
console.log(updated);
if(count == 0) res.send("[{\"ReturnValue\":" + updated + "}]");
});
}
});
}
Try for this:
May be Async problem:
for(var i = 0; i < dt.length; i++) {
//Your logic
if(i=== dt.length){
res.send("[{\"ReturnValue\":" + updated + "}]");
}
}
This is because at the time you do request.send, the value of updated is not incremented. This is because request.execute is asynchronous and done handler will be invoked after the res.send has been executed.
I would recommend a promise library (example, q). You can combine the promises and then use Q.all to do req.send when all the promises are done.
See more details here

Saving specific value Javascript

So when I execute the following code it gets the value. Though the program goes through and clicks some stuff for me and the value naturally changes. But I wish to save the value of this beforehand and then compare it to the second value.
Executed code :
var Category = [];
var ID1;
var ID2;
var dispatchMouseEvent = function(target, var_args) {
var e = document.createEvent("MouseEvents");
e.initEvent.apply(e, Array.prototype.slice.call(arguments, 1));
target.dispatchEvent(e);
}
var Level1Cats = document.getElementsByClassName("p-pstctgry-lnk-ctgry "); //GETTING LEVEL 1 CATS
var Level1CatsLen = Level1Cats.length; //GETTING LEVEL 1 CAT LEN
for (i = 0; i <= Level1CatsLen-1; i++) {
var ID1 = Level1Cats[i].id;
var temp1 = Level1Cats[i].innerHTML;
temp1.replace(/&/gi, "&").replace(/<[^>]*>/gi, "");
function GoToLevel2(i) { //GO TO NEXT LEVEL!
dispatchMouseEvent(Level1Cats[i], "mouseover", true, true);
dispatchMouseEvent(Level1Cats[i], "click", true, true);
}
function GetLevel2() { //GET NEXT LEVEL
var Level2Cats = document.getElementsByClassName("p-pstctgry-lnk-ctgry");
return Level2Cats.length;
}
setTimeout(GoToLevel2(i),100); //RUN IT WITH TIMING
var Level2CatsLen = GetLevel2();
// END OF LEVEL 1
var extracats2 = Level2CatsLen - Level1CatsLen;
console.log(extracats2+"e");
if (extracats2 !== 2 || extracats2 !== 0) {
for (ii = 0; ii < extracats2; ii++) { //LEVEL 2
console.log(ii);
ID2 = Level2Cats[ii+Level1CatsLen].id;
var temp2 = Level2Cats[ii+Level1CatsLen].innerHTML;
temp2.replace(/&/, "&").replace(/<[^>]*>/gi, "");
var Level2Children = [];
for (l = 0; l < level1CatsLen; l++) {
Level2Children.push(Level2Cats[l].id);
}
//DO SOMETHING WITH CATEGORIES - Level 1
Category.push({Name: temp1, ID: ID2, ParentID: 'null', ChildrenIDs: Level2Children});
//FINISH
Though when it finishes, if I call Level1CatsLen it is not 16, which is the inital number it is now 33 which is the final stage.
Any ideas how one would go about doing this?
Updated answer:
There are a couple of issues here.
You have function declarations within control structures, which is a syntax error:
// ...
for (i = 0; i <= Level1CatsLen-1; i++) {
var ID1 = Level1Cats[i].id;
var temp1 = Level1Cats[i].innerHTML;
temp1.replace(/&/gi, "&").replace(/<[^>]*>/gi, "");
function GoToLevel2(i) { // <=== Error
dispatchMouseEvent(Level1Cats[i], "mouseover", true, true);
dispatchMouseEvent(Level1Cats[i], "click", true, true);
}
// ...
}
Browsers have a tendency to tolerate it, but the way they tolerate it varies from browser to browser. To create a function within a control structure, you need to use a function expression, not a function declaration:
var GoToLevel2 = function(i) {
dispatchMouseEvent(Level1Cats[i], "mouseover", true, true);
dispatchMouseEvent(Level1Cats[i], "click", true, true);
};
But see #2.
There's no need to recreate your functions on every loop, if you're going to pass i into them anyway.
Your setTimeout call is incorrect:
setTimeout(GoToLevel2(i),100);
That calls GoToLevel2, passing in i, and passes its return value into setTimeout, exactly the way foo(bar()) calls bar and passes its return value into foo.
To set up a timed callback to GoToLevel2, you use the function reference. To ensure that it receives a specific value (i), you can use Function#bind to get a new function that will call GoToLevel2 with that i:
setTimeout(GoToLevel2.bind(null, i),100);
It's possible there are further issues, those are the ones that jumped out at me.
Original answer:
You're doing that, on this line:
var Level1CatsLen = Level1Cats.length;
As the DOM changes, Level1Cats.length may change (because getElementsByClassName returns a live NodeList), but Level1CatsLen won't.
var Level1Cats = document.getElementsByClassName("p-pstctgry-lnk-ctgry");
var Level1CatsLen = Level1Cats.length;
snippet.log("Initial: Level1CatsLen = " + Level1CatsLen + ", Level1Cats.length = " + Level1Cats.length);
tick();
function tick() {
var div = document.createElement('div');
div.className = "p-pstctgry-lnk-ctgry";
div.innerHTML = String(Level1Cats.length + 1);
document.body.appendChild(div);
snippet.log("Updated: Level1CatsLen = " + Level1CatsLen + ", Level1Cats.length = " + Level1Cats.length);
if (Level1Cats.length < 10) {
setTimeout(tick, 500);
}
}
.p-pstctgry-lnk-ctgry {
border: 1px solid #888;
color: green;
}
<div class="p-pstctgry-lnk-ctgry">1</div>
<div class="p-pstctgry-lnk-ctgry">2</div>
<div class="p-pstctgry-lnk-ctgry">3</div>
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Categories