How I want my code to work:
Website sends a SQL Insert to the database. (I'm using a POST and Node.js)
Website waits for the rowID for the database record that will be created
Node.js sends back the rowID that was just inserted
DOM is updated
How my code is working:
Website sends a SQL Insert to the database. (I'm using a POST and Node.js)
I wrote a Sleep function because the Website is not waiting for the rowID
Node.js sends back the rowID that was just inserted
DOM is updated
I thought I correctly wrote a callback function so this would work. However, the DOM is only correctly updated when I created a sleep function. The code I wrote to get this working is not the proper way to implement this and I would like to do it properly.
//add a record to the table
function addRecord() {
event.preventDefault();
var newTech = document.getElementById("newTechnician");
if(newTech.elements.firstnameInput.value.length === 0 || newTech.elements.lastnameInput.value.length === 0 )
{
console.log("User didn't enter any data");
}
else {
//stackoverflow.com/questions/9713058/send-post-data-using-xmlhttprequest
var http = new XMLHttpRequest(),
method = 'POST',
url = '/tech';
//build the url
var usersInput = "type=insert&First_Name="+newTech.elements.firstnameInput.value+
"&Last_Name="+newTech.elements.lastnameInput.value;
http.open(method, url, true);
//Send the proper header information along with the request
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
function updateDatabase (callback) {
http.send(usersInput);
http.onreadystatechange = () => callback();
}
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
function callbackFunction () {
//developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
if(http.readyState == 4 && http.status == 200) {
if(http.status >= 200 && http.status < 400){
//added because callback isn't working correctly and isn't waiting until
//it receives a response with the rowID from Node.js/mySQL database
sleep(500);
//add the new row to the table
var response = JSON.parse(http.responseText);
//without the sleep function, id is undefined
var id = response.rowID;
var table = document.getElementById("technicianTable");
//-1 add record at the end of the table
var row = table.insertRow(-1);
var newTechID = document.createElement('td');
newTechID.textContent = id;
row.appendChild(newTechID);
var newFirstName = document.createElement('td');
newFirstName.textContent = newTechnician.elements.firstnameInput.value;
row.appendChild(newFirstName);
newTechnician.elements.firstnameInput.value = "";
var newLastName = document.createElement('td');
newLastName.textContent = newTechnician.elements.lastnameInput.value;
row.appendChild(newLastName);
newTechnician.elements.lastnameInput.value = "";
var newWorkoutDeleteCell = document.createElement('td');
var newWorkoutDeleteBtn = document.createElement('input');
newWorkoutDeleteBtn.setAttribute('type','button');
newWorkoutDeleteBtn.setAttribute('name','deleteButtonValue');
newWorkoutDeleteBtn.setAttribute('value','Delete');
newWorkoutDeleteBtn.setAttribute('onClick', 'deleteRecord(' + id + ')');
var deleteRowID = document.createElement('input');
deleteRowID.setAttribute('type','hidden');
deleteRowID.setAttribute('id', 'identifer' + id);
newWorkoutDeleteCell.appendChild(deleteRowID);
newWorkoutDeleteCell.appendChild(newWorkoutDeleteBtn);
row.appendChild(newWorkoutDeleteCell);
}
}
};
updateDatabase(callbackFunction);
}
}
I think you need to reverse your send call with setting onreadystatechange. You want to register your callback before sending the HTTP request:
function updateDatabase (callback) {
http.onreadystatechange = () => callback();
http.send(usersInput);
}
But you may find the Fetch API easier to work with than using the older XMLHttpRequest methods. Like #Raymond's suggestion, it's Promise-based. The downside is it's not fully supported in IE or Edge.
If better browser support is needed, try axios, an open source, Promise-based HTTP client library with a lot of community support.
I used to have this problem, I ended up putting my Node.JS MySQL insert function in a promise. That way we know it finishes before we should continue on the backend Node.JS server. I had a hard time with this exact thing, until I switched to promise's. My MySQL functions would complete but the data wouldn't be there in the response.
function insert() {
return new Promise((reoslve, reject) => {
let results = []; // Store results
con.connect(function(err) {
if (err) throw reject(err); // Reject errors
console.log("Connected!");
con.query(sql, function (err, result) {
if (err) throw err;
console.log("Result: " + result);
results.push(result); // Push result to results array
});
resolve(results); // We made sure we waited
});
})
}
insert().then((response) => {
// Success, made sure it finishes
// console.log(response);
}).catch((error) => {
// Error's
});
Basic promise's example
function insert() {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve('Response')
}, 500);
// reject() for errors
});
}
console.log('This runs first');
// This runs, and wait's for respoinse
insert().then((response) => {
// We waited for response first
console.log('Our new promise callback', response);
}).catch((error) => {
// Error
});
#benjarwar "reverse your send call with setting onreadystatechange" fixed the issue issue!
Thank you #benjarwar and #Raymond for your help. I'll read up on Promise because I spent way too much time on this issue.
Related
I have a server that consistently updates a JSON file. Tho the code I setup bellow (javascript) reads the JSON file and shows it to the client always refreshes the page.
I would like to know how I would be able to read my JSON file every time it's updated without refreshing the web page.
From what I looked up, it seems that I'll have to use AJAX to get this done. But couldn't find much else. Or should I make any update on my JSON file?
This is the index.html code I'm using to get the data from my archive.json file:
<script>
fetch('archive.json')
.then(function (response) {
return response.json();
})
.then(function (data) {
appendData(data);
})
.catch(function (err) {
console.log('error: ' + err);
});
function appendData(data) {
console.log(data.velas.length);
var mainContainer = document.getElementById("myData");
for (var i = 0; i < data.velas.length; i++) {
var div = document.createElement("div");
div.innerHTML = 'Tempo: ' + data.velas[i].tempo;
mainContainer.appendChild(div);
}
}
</script>
Thanks for any help!
You can try this, i have explained everything in code comment
<script>
const REFRESH_INTERVAL = 5; // 5 seconds
var mainContainer = document.getElementById("myData");
async function appendData() {
// make the request
const response = await fetch('archive.json');
const data = await response.json();
// check the data response
if(data == undefined && data == null) return
// manipulate DOM
var mainContainer = document.getElementById("myData");
mainContainer.innerHTML = '' // clear the content of the previous mainContainer
for (var i = 0; i < data.velas.length; i++) {
var div = document.createElement("div");
div.innerHTML = 'Tempo: ' + data.velas[i].tempo;
mainContainer.appendChild(div);
}
}
setInterval(() => { // send a request to server each 5 seconds (REFRESH_INTERVAL) <- you can change it here
appendData()
}, REFRESH_INTERVAL * 1000)
</script>
I hope I understood your question correctly.
document.querySelector('button').addEventListener('click',()=>{
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
document.querySelector('.get').innerHTML=`${JSON.parse(this.responseText).ip}`
};
}
xhttp.open("GET", "https://ipapi.co/json/", true);
xhttp.send();
})
<button>Get</button>
<div class="get">
</div>
Just acknowledge my mistake of it refreshing not because of JSON nor AJAX codes... but because I was running it on a local server. So it would refresh anyway.
But adilson's respond to the topic with a timer completed what I needed:
https://stackoverflow.com/a/68090848/16179486
Thanks for the help guys!
I am calling an API and updating the objects variables on interval.
Within the updateAPI function I wanna check if the variable increases and if it does I wanna call a function.
I have tried setters and getters, storage and other methods. Also played around a lot with different variants of current code but I can't understand the other solutions.
function updateAPI() {
var getJSON = function(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
var status = xhr.status;
if (status == 200) {
callback(null, xhr.response);
} else {
callback(status);
}
};
xhr.send();
};
getJSON('api.example.com', function(err, response) {
if (err != null) {
console.error(err);
} else {
let data = response.data
var followers = data.followers_count
var old = followers;
function check() {
if (old > followers) {
console.log('changed');
var old = followers;
}
if (old < followers) {
console.log('not changed');
var old = followers;
}
}
setInterval(check, 5000);
}
});
}
updateAPI();
setInterval(() => { updateAPI() }, 10000);
The current code does not log any changes happening to the API. But I can console.log('followers') and see the value changing.
Just some minor things to start:
Move getJSON outside and above of updateAPI. Right now every time it gets called, it is creating a new function which isn't needed. Also you can probably just replace it with the fetch api. You can also abstract the check function and allow it to accept an argument (or two) and tell you if you should update.
Secondly have your tried debugging this some how? Either using console.log statements or the Chrome debugger? The problem lies in this code:
var followers = data.followers_count
var old = followers;
followers and old will always be equal. You're re-assigning them every time you call your function. That is why you're seeing it change, but never seeing it log anything.
This question was posted a couple of days ago, but since I'm a nub it was filled with spaghetti code and that sort of thing (please pardon the form handling as well) That aside, I've added some notes and given some context, but the problem still lies in the second AJAX call.
This is the error that Chrome is throwing "Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource."
I have hidden the URL because it contains an API key that I would rather not share.
Any and all criticisms are warmly welcomed
/*
This module will take a user's name, return an ID
then search more stats in the api with the ID.
*/
var search = document.getElementById('search');
search.addEventListener('click', function(){
var demo = document.getElementById('demo');
var player_name = document.getElementById('player_name').value;
var player_id;
// Interpolated API URLs
var name_url = 'URL.pre'+player_name+'URL.end';
var stats_url; //nested in the second ajax call to pass updated player_id
// Get player ID
var xhr = new XMLHttpRequest();
var id_return_text;
xhr.onload = function(){
if(xhr.status === 200) {
id_return_text = JSON.parse(xhr.responseText);
player_id = id_return_text[player_name].id;
demo.innerHTML = id_return_text[player_name].name +', your player ID is: '+player_id;
}
};
xhr.open('GET', name_url, true);
xhr.send();
// Search stats with ID
var xhr_2 = new XMLHttpRequest();
var stats_return_text;
xhr.done = function(){
stats_url = "URL.pre"+player_id+"URL.end";
if(xhr_2.status == 200) {
stats_return_text = JSON.parse(xhr_2.responseText);
demo.innerHTML += stats_return_text['playerStatsSummaries'].playerStatType;
}
};
xhr_2.open("GET",stats_url, true);
xhr_2.send();
});
<div id="container">
<img id="duck" src="duck.png" alt="duck">
<div class="form_wrapper">
<h1 id="app_header">*QUACK* What's Your player ID?</h1>
<form>
<input
type="text"
id="player_name"
placeholder="Summoner Name">
<input type="button" id="search" value="Search">
</form>
</div>
<p id="demo"></p>
</div>
<script type="text/javascript" src="script.js"></script>
So your primary error was that if you need to make CORS requests (or any AJAX requests, really), you need to run the code from a server (even localhost).
Google (and most browsers) will freak out at you if your page's protocol is "file:///" and you're trying to load things from the internet (or vice versa). And "file:///" cannot make requests for other files, either.
Future reference: you also can't make "http" requests from an "https" page.
That out of the way, the second issue (the one that was being hidden by CORS security), is that your AJAX requests are being run in parallel right now.
In order to make this work the way you think it should (after the first one returns, run the second one), you would need to:
move all of the code at the bottom, relating to xhr_2 inside of the xhr.onload
move all of the code inside of xhr.done at the bottom inside of the xhr.onload and replace all of the duplicate information (and use the references to the returned results directly)
This results in something like:
var search = document.getElementById('search');
search.addEventListener('click', function(){
var demo = document.getElementById('demo');
var player_name = document.getElementById('player_name').value;
var player_id;
// Interpolated API URLs
var name_url = 'https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/'+player_name+'?api_key=<THIS IS THE API KEY>';
var stats_url; //nested in the second ajax call to pass updated player_id
// Get player ID
var xhr = new XMLHttpRequest();
var id_return_text;
xhr.onload = function(){
if(xhr.status === 200) {
id_return_text = JSON.parse(xhr.responseText);
player_id = id_return_text[player_name].id;
demo.innerHTML = id_return_text[player_name].name +', your player ID is: '+player_id;
// Dropped the XHR_2 stuff here
var xhr_2 = new XMLHttpRequest();
var stats_return_text;
stats_url = "https://na.api.pvp.net/api/lol/na/v1.3/stats/by-summoner/"+player_id+"/summary?season=SEASON2016&api_key=<THIS IS THE API KEY>";
// CHANGED THIS TO BE XHR_2.onload -- IN HERE I KNOW XHR_1 IS ALREADY FINISHED
xhr_2.onload = function(){
if(xhr_2.status == 200) {
stats_return_text = JSON.parse(xhr_2.responseText);
demo.innerHTML += stats_return_text['playerStatsSummaries'].playerStatType;
}
};
xhr_2.open("GET",stats_url, true);
xhr_2.send();
}
};
xhr.open('GET', name_url, true);
xhr.send();
});
That should solve practically all of your woes.
The point of this is that onload is a callback which gets fired long after the program has been run, but xhr_2 was firing immediately after you requested data for xhr_1 (not after it was returning the data).
As such, player_id was undefined.
We want to wait until after we know we have player_id, and we know we have it (or some error) when we're inside the callback to xhr_1.onload.
This gets terribly confusing and very nested, and while I think that Promises and Async Functions / Generators are brilliant solutions for managing that complexity, that's way beyond the scope of this; so instead, I'd suggest looking at some functional composition, to simplify all of this:
function noop () { } // do nothing
function getJSON (url, onload, onerror) {
var xhr = new XMLHttpRequest();
onload = onload || noop; // what I've been given or nothing
onerror = onerror || noop; // " "
xhr.onload = function () {
var data;
var error;
try {
// it's possible for parse to throw on malformed JSON
data = JSON.parse(xhr.responseText);
} catch (e) {
error = e;
}
return error ? onerror(error) : onload(data); // fire one or the other (don't fall into the handler, if onload throws)
};
xhr.onerror = onerror;
xhr.open("GET", url);
xhr.send();
}
// localize URL construction
function buildPlayerIdUrl (name) { return "https://______" + name + "_____"; }
function buildPlayerStatsUrl (id) { return "https://______" + id + "_____"; }
// gets player by name and runs a function after the player has been loaded
function getPlayer (player_name, done, error) {
var id_url = buildPlayerIdUrl(player_name);
function buildPlayer (response) {
var player = response[player_name];
return player;
}
function onload (response) {
done(buildPlayer(response));
}
// Load the JSON, build the player, pass the player to done()
getJSON(url, onload, error);
}
// get stats by player id and runs a function after the stats have been loaded
function getPlayerStats (player_id, done, error) {
var stats_url = buildPlayerStatsUrl(player_id);
function buildStats (response) {
var summary = response.playerStatsSummaries;
return summary;
}
function onload (response) {
done(buildStats(response));
}
// Load the JSON, build the stats, pass the stats to done()
getJSON(stats_url, onload, error);
}
// perform a search by player name
// note: All changes in step-number (1, 2, 3) are asynchronous,
// and thus, must be nested in callbacks of some sort
function search (player_name) {
// Step 1: load the player
getPlayer(playerName, function (player) {
// Step 2a: update the DOM with the player name/id
updatePlayerDom(player);
// Step 2b: load the player stats
getPlayerStats(player.id, function (stats) {
// Step 3: update the DOM with the stats
updateStatsDom(stats);
});
});
}
// player DOM update; keeping it nice and simple
function updatePlayerDom (player) {
document.querySelector(".Player-id").textContent = player.id;
document.querySelector(".Player-name").textContent = player.name;
}
// stats DOM update; same as above
function updateStatsDom (stats) {
document.querySelector(".Player-stats").textContent = stats.playerStatType;
}
// bootstrap yourself to your UI
some_button.onclick = function () {
var player_name = some_input.value;
search(player_name); // kick the whole thing off
};
It's definitely more code, but it's also simpler to make edits to each individual piece, without stepping on the toes of other pieces.
It's (hopefully) also easier to see the _eventual timeline_ of all of the pieces, and how they flow, inside of the search( ) itself.
I am trying to make several server requests inside a for loop. I found this question and implemented the suggested solution. However it doesn't seem to work.
for (var i = 1; i <= 10; i++)
{
(function(i) {
if(<some conditions>)
{
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp[i]=new XMLHttpRequest();
} else { // code for IE6, IE5
xmlhttp[i]=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp[i].onreadystatechange=function() {
if (xmlhttp[i].readyState==4 && xmlhttp[i].status==200) {
document.getElementById("preselection").innerHTML=xmlhttp[i].responseText;
}
}
xmlhttp[i].open("GET","getBuoys.php?q="+i,true);
xmlhttp[i].send();
}
})(i);
}
If I remove the for loop and change all xmlhttp[i] to xmlhttp, everything works just fine for one element, but I can't make several requests. Thanks in advance for any suggestions.
Try the snippet below
// JavaScript
window.onload = function(){
var f = (function(){
var xhr = [], i;
for(i = 0; i < 3; i++){ //for loop
(function(i){
xhr[i] = new XMLHttpRequest();
url = "closure.php?data=" + i;
xhr[i].open("GET", url, true);
xhr[i].onreadystatechange = function(){
if (xhr[i].readyState === 4 && xhr[i].status === 200){
console.log('Response from request ' + i + ' [ ' + xhr[i].responseText + ']');
}
};
xhr[i].send();
})(i);
}
})();
};
// PHP [closure.php]
echo "Hello Kitty -> " . $_GET["data"];
Response
Response from request 0 [ Hello Kitty -> 0]
Response from request 1 [ Hello Kitty -> 1]
Response from request 2 [ Hello Kitty -> 2]
First thing first, that's awful formatting. A small request to keep it a bit more parseable in future please.
We can clean this up though.
var XMLHttpRequest
= XMLHttpRequest || require('xmlhttprequest').XMLHttpRequest;
// Makes a request for 4 buoy page responses.
requestAllBuoys(4, function(requests) {
console.log('Got results!');
// Take out the responses, they are collected in the order they were
// requested.
responses = requests.map(function(request) {
return request.responseText;
});
// Left to you to implement- I don't know what you're going to do with
// your page!
updateDom(responses);
});
// Makes request to all buoy url's, calling the given callback once
// all have completed with an array of xmlRequests.
function requestAllBuoys (n, cb) {
var latch = makeLatch(n, cb);
makeBuoyURLTo(n).map(function (url, i) {
startXMLRequest('GET', url, latch.bind(undefined, i));
});
}
// Generates a latch function, that will execute the given callback
// only once the function it returns has been called n times.
function makeLatch (n, cb) {
var remaining = n,
results = [],
countDown;
countDown = function (i, result) {
results[i] = result;
if (--remaining == 0 && typeof cb == 'function') {
cb(results);
}
}
return countDown;
}
// Generates an array of buoy URL's from 1 to n.
function makeBuoyURLTo (n) {
var i, buoyUrls = [];
for (i = 1; i <= n; i++) {
buoyUrls.push('getBuoys.php?q=' + i);
}
return buoyUrls;
}
// Create and initiate an XMLRequest, with the given method to the given url.
// The optional callback will be called on successful completion.
function startXMLRequest (method, url, cb) {
var xmlRequest = createXMLRequest();
xmlRequest.onreadystatechange = function () {
if (isXMLFinished(xmlRequest)) {
if (cb && typeof cb == 'function') {
cb(xmlRequest, method, url);
}
}
}
xmlRequest.open(method, url, true);
xmlRequest.send();
return xmlRequest;
}
// Initiates an XMLRequest from either HTML5 native, or MS ActiveX depending
// on what is available.
function createXMLRequest () {
var xmlRequest;
if (XMLHttpRequest) {
xmlRequest = new XMLHttpRequest();
} else {
xmlRequest = new ActiveXObject('Microsoft.XMLHTTP');
}
return xmlRequest;
}
// Verifies that XMLRequest has finished, with a status 200 (OK).
function isXMLFinished (xmlRequest) {
return (xmlRequest.readyState == 4) && (xmlRequest.status == 200);
}
This may seem longer, but it makes things infinitely clearer, and the time you spent making it so is time you don't spend debugging.
It also allows you to access the final result together, in the order that they came as a standard array. This is the main added bulk.
I would say you have a good idea of what you're actually doing here, as to me the only thing about your code that wouldn't work is the updating of the dom (surely you'll just be assigning them rapidly all into the same element? replacing each other each time...).
Have a look at this answer about handling async callbacks if you're still struggling. But please, for your own sake, keep your code cleaner.
thanks in advance for your help! I am working with (and super new to) JavaScript, node.js with express, and sqlite3. I am trying to make an AJAX request to get a list of all the messages that have been posted to the chatroom page:
var meta = document.querySelector('meta[name=roomName]');
var roomName = meta.content;
window.addEventListener('load', function(){
var intervalID = setInterval(updateMessages, 4000);
}, false);
function updateMessages() {
var req = new XMLHttpRequest();
req.open('GET', '/' + roomName + '/messages.json', true);
req.send();
document.getElementById('messages').innerHTML = req.responseText;
}
Two questions: 1. I think I should be using setTimeout instead of setInterval. How would I go about switching to using this method? 2. Is the server-side code below that corresponds to the code above correct? How do I get access to the data that comes back after this request?
app.get('/:roomName/messages.json', function(request, response){
var roomName = request.params.roomName;
var sql = "SELECT ALL body FROM messages where room="+roomName+";";
conn.query(sql, function(error, result) {
if(error) {
console.log("There was an error.");
}
response.send(result);
});
});
setInterval is the appropriate thing to use here.
However, keep in mind that you will never see any messages because AJAX is asynchronous, so req.responseText won't have anything. You should use a readystatechange event:
req.open(......);
req.onreadystatechange = function() {
if( this.readyState == 4) {
document.getElementById('messages').innerHTML = this.responseText;
}
};
req.send();