How to execute javascript code with respecting code order - javascript

I am developing a chrome extension and I need to read information from a page, insert the data into the database table and then move on to the next page and do the same thing.
The problem is that the function which inserts the data (using ajax) inserts 6 rows out of 45 before moving to the next page, which means that we are moving to the next page without inserting the rest of the data.
What I want it to do is to respect code order and insert all the rows into the database and then move on to the next page.
the code is as follows :
for (elt of an) {
var t = elt.getElementsByClassName('annonce_titre');
if ((elt.id !== '')) {
//console.log(().getElementsByTagName('a')[0].href);
let titleH2 = t[0].getElementsByTagName('h2');
let titleLink = t[0].getElementsByTagName('a');
var url = titleLink[0].href;
var title2 = titleH2[0].innerHTML;
var last_item = 0;
var w = elt.getElementsByClassName('titre_wilaya')[0].innerHTML;
console.log(w);
var wilaya = w.substring(w.length - 2, w.length);
console.log("leg0 leng " + (w.length - 2) + " ** " + w.length)
console.log("wilaya " + wilaya)
if (isNumber(wilaya)) {
var city = w.substring(0, w.length - 4);
} else {
var city = w;
wilaya = 16;
}
console.log("w c " + wilaya + " ** " + city)
var num = (elt.id).substring(4, 20)
// ADD DELAY OF 5 SECONDS BETWEEN PAGE LOADS
var logger = setInterval(logNextTitle, 10);
var inserts = [
[title2, wilaya, city, url, num]
];
test = test.concat(inserts);
console.log('test spead ');
$.ajax({
data: {
link: url,
h2: title2,
field: "auto",
last: last_item,
numero: num,
wilaya: wilaya,
city: city,
items: test
},
type: "post",
url: "http://localhost/insert.php",
success: function(data) {
console.log("Data Save: " + data);
}
});
}
}
//window.open(first_link ,'_SELF');
console.log("first_link " + first_link)
What this code does is loop through all the elements of an array , insert the data into the DB using ajax and then moving to the next page .
https://imgur.com/Rw4xrMq
This console shows that echoing "first_link" in the code is after the code for insertion , but the later is executed after the echo. There is a miss-order in javascript

Basics of asynchronous calls is they start and the code moves on. Problem with your code is you assume they are all done when they are still queued up waiting to be made. So you need to wait for them to all be done before you do it. Promises make it easier to do that. So look at using jQuery's when.
var ajaxCalls = []
for (elt of an) {
...
ajaxCalls.push($.ajax({...}))
}
$.when.apply($, ajaxCalls).then( function () { console.log('done'); } );
// $.when(...ajaxCalls).then( function () { console.log('done'); } );

Related

JavaScript: Catch errors in async function and stop the rest of the function if there are any

EDIT: ANSWER BELOW
I'm making my first JavaScript project and decided to make a simple weather app. It fetches weather data of a city you put in from the openweathermap.org api and displays it in a table. I firstly made it using fetch() and .then. I then learned about async functions and the await keyword. After converting the script to an asynchronous function, I came across a problem. If the first city you enter isn't a real city (an error is catched while fetching the api), the warning message appears, BUT the table also appears because the rest of the function still executes.
So my question is: how can I stop the async function if any errors are catched?
Here's the website: https://lorenzo3117.github.io/weather-app/
Here's the code:
// Launch weather() function and catch any errors with the api request and display the warning message if there are any errors
function main() {
weather().catch(error => {
document.querySelector("#warningMessage").style.display = "block";
console.log(error);
});
}
// Main function
async function weather() {
// Take city from input and reset input field
var city = document.querySelector("#cityInput").value;
document.querySelector("#cityInput").value = "";
// Get api response and make it into a Json
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=<apiKey>&units=metric");
const jsonData = await apiResponse.json();
// Removes warning message
document.querySelector("#warningMessage").style.display = "none";
// Puts the Json into an array and launches createTable function
var arrayJson = [jsonData];
createTable(document.querySelector("#table"), arrayJson);
// Function to create the table
function createTable(table, data) {
// Makes the table visible
document.querySelector("#table").style.display = "block";
// Goes through the array and makes the rows for the table
for (let i = 0; i < data.length; i++) {
let rowData = data[i];
var row = table.insertRow(table.rows.length);
// This var exists to make the first letter capitalized without making a gigantic line (see insertCell(3), line 53)
// Could be made into a function if needed
var weatherDescription = rowData.weather[0].description;
// Take latitude and longitude for google maps link
var lat = rowData.coord.lat;
var long = rowData.coord.lon;
// Make an a-tag for link to google maps
var mapLink = document.createElement("a");
mapLink.innerHTML = "Link";
mapLink.target = "_blank";
mapLink.href = "https://www.google.com/maps/search/?api=1&query=" + lat + "," + long;
// Making rows in table
row.insertCell(0).innerHTML = rowData.name + ", " + rowData.sys.country;
row.insertCell(1).innerHTML = rowData.main.temp + " °C";
row.insertCell(2).innerHTML = rowData.main.humidity + "%";
row.insertCell(3).innerHTML = weatherDescription.charAt(0).toUpperCase() + weatherDescription.slice(1);
row.insertCell(4).appendChild(mapLink); // appendChild for anchor tag because innerHTML only works with text
}
}
And the repo: https://github.com/lorenzo3117/weather-app
Thank you
you can do this :
async function weather() {
try {
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=02587cc48685af80ea225c1601e4f792&units=metric");
} catch(err) {
alert(err); // TypeError: failed to fetch
return;
}
}
weather();
Actually, the error catched isn't an error with the api itself because the api still sends a json, but the error is catched while trying to read a certain object from the json (which doesn't exist because the json isn't a normal one with weather data). Therefore the function stops far later than expected, after the table was made visible.
I just put the line that made the table visible after the function that creates the table (after where the real error occurs). Also thanks #Dadboz for the try catch method which made the code even more compact. I also added an if else to check if the json file is the correct one so unnecessary code doesn't get executed. Thanks #James for pointing this out to me.
Here's the final code:
// Main function
async function weather() {
try {
// Take city from input and reset input field
var city = document.querySelector("#cityInput").value;
document.querySelector("#cityInput").value = "";
// Get api response and make it into a Json
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=<apiKey>&units=metric");
const jsonData = await apiResponse.json();
if (jsonData.message == "city not found") {
document.querySelector("#warningMessage").style.display = "block";
} else {
// Removes warning message
document.querySelector("#warningMessage").style.display = "none";
// Puts the Json into an array and launches updateTable function
var arrayJson = [jsonData];
updateTable(document.querySelector("#table"), arrayJson);
}
}
catch (error) {
console.log(error);
}
}
// Function to update the table
function updateTable(table, data) {
// Goes through the array and makes the rows for the table
for (let i = 0; i < data.length; i++) {
let rowData = data[i];
var row = table.insertRow(table.rows.length);
// This var exists to make the first letter capitalized without making a gigantic line (see insertCell(3), line 53)
// Could be made into a function if needed
var weatherDescription = rowData.weather[0].description;
// Take latitude and longitude for google maps link
var lat = rowData.coord.lat;
var long = rowData.coord.lon;
// Make an a-tag for link to google maps
var mapLink = document.createElement("a");
mapLink.innerHTML = "Link";
mapLink.target = "_blank";
mapLink.href = "https://www.google.com/maps/search/?api=1&query=" + lat + "," + long;
// Making rows in table
row.insertCell(0).innerHTML = rowData.name + ", " + rowData.sys.country;
row.insertCell(1).innerHTML = rowData.main.temp + " °C";
row.insertCell(2).innerHTML = rowData.main.humidity + "%";
row.insertCell(3).innerHTML = weatherDescription.charAt(0).toUpperCase() + weatherDescription.slice(1);
row.insertCell(4).appendChild(mapLink); // appendChild for anchor tag because innerHTML only works with text
}
// Makes the table visible
document.querySelector("#table").style.display = "block";
}
Thanks everyone for your answers, have a good day!
Lorenzo

Multiple records are added to the database

I'm puzzling over a weird problem I cannot replicate.
Scenario
I wrote a simple nodejs application that, after some UI interaction append some records to an Access 97 database. I'm using node-adodb to connect with it.
Some relevant piece of code.
var DBDATA = require('node-adodb'), dbConnection = DBDATA.open('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=/path/to/my/file.mdb');
function append(data, callback)
{
var table;
var query;
var array = [];
array.push({name: "Date", value: formatDate(data.date)});
array.push({name: "Time", value: formatTime(data.date)});
array.push({name: "Type", value: Number(data.Type)});
array.push({name: "Value", value: Number(exists(data.value, 0))});
// ...other fields
var fields = array.map(function (e) {
return "[" + e.name + "]";
}).join(",");
var values = array.map(function (e) {
return e.value;
}).join(",");
table = "tblData";
query = 'INSERT INTO ' + table + '(' + fields + ') ' + 'VALUES (' + values + ')';
dbConnection
.execute(query)
.on('done', function (data) {
return callback({id: id, success: true});
})
.on('fail', function (data) {
console.log(data);
return callback({id: id, success: false});
});
}
The issue
The above function is called whenever a new record is ready. Usually it works fine, but it happens about 1 time per week (among hundreds of records) that I find in the database multiple rows identical.
Due to the nature of the information this is impossible - I mean, it's impossible that the actual data is the same.
I guessed for a bug in the caller, that for some reasons sends me the same variable's content. Hence I added a check before append the record.
What I tried to do
function checkDuplicate(table, array, callback)
{
var query = "SELECT * FROM " + table + " WHERE ";
array.forEach(function(element)
{
query += "([" + element.name + "]=" + element.value + ") AND ";
});
query = query.substr(0, query.length - 4);
dbConnection
.query(query)
.on("done", function (data) {
return callback(data.records.length > 0);
})
.on("fail", function (data) {
return callback(false);
});
}
in the append function I call this one and if it returns a value > 0 I don't execute the query, because it would mean there already is the same row.
Testing it with fake data gave good results: no multiple records were added.
Unfortunately, this didn't fixed the issue in the real world. After 20 days I noticed that a row was added three times.
Questions
Do you see any evidence of a major mistake in my approach?
Is there a more reliable way to avoid this problem?
Please note I cannot change the database structure because it's not mine.
UPDATE
This is the new code I'm using:
// Add only if there isn't an identical record
query = 'INSERT INTO ' + table + '(' + fields + ') ';
query += ' SELECT TOP 1 ' + values;
query += ' FROM ' + table;
query += ' WHERE NOT EXISTS ( SELECT 1 FROM ' + table + ' WHERE ';
array.forEach(function(element)
{
query += "([" + element.name + "]=" + element.value + ") AND ";
});
query = query.substr(0, query.length - 4);
query += ' );';
dbConnection
.execute(query)
.on('done', function (data) {
return callback({id: id, success: true});
})
.on('fail', function (data) {
console.log(data);
return callback({id: id, success: false});
});
but it doesn't solved the problem, i.e. sometimes I still found two or more records identical in the database.
I'm afraid it could be the same behavior: the client make multiple requests in a while and they are executed in parallel, so each one doesn't find the record, and all will be add it.
Hance, what is the right approach to avoid this without change the database structure?
Is there a way to force node-adodb to execute only one query at time?

How to wait for the first function to finish executing before the second is executed?

I'm trying to figure out a way to wait until the first function (relatedVids(...)) to finish executing and then in turn execute the second function (relatedVidsDetails()). What the code does is simply loop through a single $.get(...) request from the youtube api and loop through each of its item retrieved.
The problem with this as tested in the debugger, is that it will go in the first function (up to the $.get() without actually getting anything yet) then skip into the second function (once again, up to the $.get()). Then, it will proceed to execute the first function till it's finished retrieving all items, then when it gets into the second function, it will do the same thing but for some mysterious reason, the videoIdChainStr which holds all the video ids in a string from the first function is never retrieved or being executed since I suspected it executed the second function's $.get(...) already and never did it again a "second time" when it had the values.
So, my next step is trying to use $.Deferred() which is said to help resolve the first function first before even stepping into executing the second function so it will guaranteed values from the first function to be used in the second without skipping anything. But I'm not sure if I'm doing this right as it still does the same thing with or without using $.Deferred().
First function (relatedVids(...)):
var relatedVidsDefer = function relatedVids(videoId)
{
var r = $.Deferred();
$.get( // get related videos related to videoId
"https://www.googleapis.com/youtube/v3/search",
{
part: 'snippet',
maxResults: vidResults,
relatedToVideoId: videoId,
order: 'relevance',
type: 'video',
key: 'XXXXXXXXXX'
},
function(data)
{
debugger;
$.each(data.items,
function(i, item)
{
try
{
console.log(item);
var vidTitle = item.snippet.title; // video title
var vidThumbUrl = item.snippet.thumbnails.default.url; // video thumbnail url
var channelTitle = item.snippet.channelTitle; // channel of uploaded video
var extractVideoId = null; // var to extract video id string from vidThumbUrl
// check if vidThumbUrl is not null, empty string, or undefined
if(vidThumbUrl)
{
var split = vidThumbUrl.split("/"); // split string when '/' seen
extractVideoId = split[4]; // retrieve the fourth index on the fourth '/'
}
else console.error("vidThumbUrl is either undefined or null or empty string.");
// if video title is longer than 25 characters, insert the three-dotted ellipse
if(vidTitle.length > 25)
{
var strNewVidTitle = vidTitle.substr(0, 25) + "...";
vidTitle = strNewVidTitle;
}
// check whether channelTitle is March of Dimes
if(channelTitle === "March of Dimes")
{
extractedVideoIdArr.push(extractVideoId); // add the extracted video id to the array
// check if extractedVideoIdArr is not empty
if(extractedVideoIdArr !== 'undefined' && extractedVideoIdArr.length > 0)
{
console.log("before join(): ", extractedVideoIdArr);
videoIdChainStr = extractedVideoIdArr.join(", "); // change from an array to a chain string of videoIds for the relatedVidsDetails()
console.log("after join(): ", videoIdChainStr);
}
var vidThumbnail = '<div class="video-thumbnail"><a class="thumb-link" href="single-video.html"><div class="video-overlay"><img src="imgs/video-play-button.png"/></div><img src="' + vidThumbUrl + '" alt="No Image Available." style="width:204px;height:128px"/></a><p><a class="thumb-link" href="single-video.html">' + vidTitle + '</a><br/></div>'; //+ convert_time(vidDuration) + ' / Views: ' + viewCount + '</p></div>';
// print results
$('.thumb-related').append(vidThumbnail);
$(item).show(); // show current video thumbnail item
}
else $(item).hide(); // hide current video thumbnail item
}
catch(err)
{
console.error(err.message); // log error but continue operation
}
}
);
}
);
return r;
};
Second function (relatedVidsDetails(...)):
var relatedVidsDetailsDefer = function relatedVidsDetails()
{
// change extractvideoid into a string by tostring() or join() for param to recognize
console.log("initial: ", extractedVideoIdArr);
$.get(
"https://www.googleapis.com/youtube/v3/videos",
{
part: 'snippet, contentDetails, statistics',
id: videoIdChainStr, // chain string of video ids to be called upon in a single request
key: 'XXXXXXXXXX',
},
function(data)
{
debugger;
$.each(data.items,
function(i, item)
{
try
{
console.log("relatedVidsDetails()", item);
console.log("extractedvideoidarr: ", extractedVideoIdArr[i]);
var _vidDuration = item.contentDetails.duration;
var _viewCount = item.statistics.viewCount;
console.log("id: " + extractedVideoIdArr[i] + " duration: " + _vidDuration);
console.log("id: " + extractedVideoIdArr[i] + " viewCount: " + _viewCount);
}
catch(err)
{
console.error(err.message); // log error but continue operation
}
}
);
}
);
};
Code being skipped when the second function has been tapped into the second time:
$.each(data.items,
function(i, item)
{
try
{
console.log("relatedVidsDetails()", item);
console.log("extractedvideoidarr: ", extractedVideoIdArr[i]);
var _vidDuration = item.contentDetails.duration;
var _viewCount = item.statistics.viewCount;
console.log("id: " + extractedVideoIdArr[i] + " duration: " + _vidDuration);
console.log("id: " + extractedVideoIdArr[i] + " viewCount: " + _viewCount);
}
catch(err)
{
console.error(err.message); // log error but continue operation
}
}
);
The code being skipped due to the second function being stepped into when videoIdChainStr was empty and then skipped when first function completed and had the values ready for the second to use. I couldn't get this videoIdChainStr when it has values to then execute.
Well the answer to the question: "How to wait for the first function to finish executing before the second is executed" would be:
USE CALLBACK OR PROMISES.
Example:
function firstMethod() {
// half of first method actions ...
secondMethod(function() {
// other half of first method actions
});
}
function secondMethod(callBack) {
// Second method actions
setTimeout(function() { // The timeout is just a way to simulate the time that a response make us wait
callBack();
}, 5000);
}

How to trigger a second AJAX call upon clicking of hyperlink?

I've an API that I'm connecting to. I've an index.html page with two columns in it. The left column contains thumbnails of images (see Part A) and the right column will contain details of information about the images once it is clicked on. There are currently two AJAX calls (both calling different URLs) within the same JS script file.
Part A
$.ajax({
url: 'http://gateway.marvel.com:80/v1/public/characters?limit=5&offset=300&apikey=' + publickey + "&ts=" + ts + "&hash=" + hash,
method: 'get',
success: function (res) {
var characters = res.data.results;
var index = 0;
loadCharacters();
function loadCharacters() {
if (index > characters.length) {
index = 0;
return;
}
for (index = 0; index < characters.length; index++) {
var hero = characters[index];
var heroID = hero.id;
var heroName = hero.name;
var image = hero.thumbnail;
var image_url = image.path + '.' + image.extension;
var image_target = $("#comics")
$('<img>').attr({
src: image_url,
width: 80,
height: 80,
}).appendTo($('<a>').attr({
href: '#?' + heroID,
//}).click(dummyCall(hero.id)).appendTo(image_target)); //NOTE: to figure how to pass this hero.id to second ajax call..
}).appendTo(image_target));
image_target.append("<br/>" + heroName + "<br/>");
}
} // end first ajax
}
});
Part B
$.ajax({
url: "https://gateway.marvel.com:443/v1/public/characters/1009269/comics?limit=5&apikey=" + publickey + "&hash=" + hash + "&ts=" + ts,
method: 'get',
success: function (res) {
var comicDetails = res.data.results;
var index = 0;
loadComic();
function loadComic() {
if (index > comicDetails.length) {
index = 0;
return;
}
for (index = 0; index < comicDetails.length; index++) {
var comic = comicDetails[index];
var comicTitle = comic.title;
$("#comics").append("<br/>" + comicTitle + "<br/>");
}
}
}
});
Part B is still incomplete, but for the purpose of this question, it should suffice. I need to know how I can trigger Part B once the user clicks on the anchored image hyperlinks in the left sidebar. I don't want Part B to load immediately because Part B is dependent on the hero.id (from Part A) being clicked and passed to it.
You'll want to have a click handler for the images. You can do this in a couple ways.
Either directly
$imageElements.on('click', functionThatCallsAjaxB);
Or indirectly with a delegate
$("#comics").on('click', 'img', functionThatCallsAjaxB);
The difference being that the first puts a handler on each image and the image has to exist when you execute that line. The second is a single handler attached to the parent that reacts when one of this children has that event.
I also see in your ajax A that your wondering how to pass information to the second ajax call. One way you can do this is with data elements.
$element.data('heroId', 'myvalue'); <-- setter
$element.data('heroId') <-- getter
So you can set them on the images when you create them and reference them later.
An assumption on my part, lets assume that hero id is a unique value for each image. If so, in you loop,
$('<img>').attr({
src: image_url,
width: 80,
height: 80,
id: hero.id,
...
Because you are assigning a click handler, you don't need the '< a>'.
Elaborating on Taplar's answer a little more, $("#comics").on('click', 'img', functionThatCallsAjaxB); after the looping is done (you only have to do it once.
If you do it this one...
$("#comics").on('click', 'img', function(evtObj){ functionThatCallsAjaxB(evtObj);});
with function
functionThatCallsAjaxB(obj) {
// the image that was clicked will be obj.target.
// with the above assumption,
var heroID = obj.target.id;
// the rest of your part b ajax goes here.
}

One of my JavaScript variables seem to be getting reset after a jQuery call

My code sends requests to Twitter for search data gets responses in the from of JSON. After getting the JSON, it stores the count of responses that match a certain condition in an array.
Here is the code that makes the call to the function that queries Twitter.
$(document).ready(function() {
...
graph = new HighCharts.chart({
chart: {
events: {
load: function() {
console.log("events.load");
var that = this;
var update = function() {
if (polling) {
console.log("update()");
// The least index series will be nearest the x-axis
var stackNumber = 0;
var numTweets = updateValues();
console.log(numTweets + "");
for (var i = 0, currentSeries = that.series; i < currentSeries.length; i++) {
var x = (new Date()).getTime(), // current time
y = numTweets[i];
stackNumber += y;
currentSeries[i].addPoint([x, y], true, true);
}
}
}
// set up the updating of the chart each second
var series = this.series[0];
setInterval(update, 1000);
}
}
(I'm probably missing some brace somewhere in the code-paste here, but I know for sure that my problem isn't related to a missing brace)
And here is the function that actually queries Twitter using a series of jQuery calls. The updateValues() function (which is outside the document-ready section) goes as follows:
function updateValues() {
var url = "http://search.twitter.com/search.json?callback=?&q=";
var cls = "Penn_CIS240"
var query = "%23" + cls;
var voteCount = [0,0,0,0];
// create an array of zeros
//for (var i = 0; i < 4; i++)
// voteCount.push(0);
$.getJSON(url + query, function(json){
console.log(json);
$.each(json.results, function(i, tweet) {
var user = tweet.from_user_id;
if (user % 2 == 0) {
voteCount[0] += 1;
}
else if (user % 3 == 0) {
voteCount[1] += 1;
}
else if (user % 5 == 0) {
voteCount[2] += 1;
}
else {
voteCount[3] += 1;
}
console.log("updateValues() -> getJSON -> each -> voteCount = " + voteCount);
});
console.log("updateValues() -> getJSON -> voteCount = " + voteCount);
});
console.log("updateValues() -> voteCount = " + voteCount);
return voteCount;
}
What is happening is that the variable voteCount is getting incremented properly inside the jQuery calls. However, outside of the calls, it is getting reset. So the log outputs look something like this:
updateValues() -> getJSON -> each -> voteCount = [1,0,0,0]
updateValues() -> getJSON -> voteCount = [1,0,0,0]
updateValues() -> voteCount = [0,0,0,0]
Does this problem have to do with jQuery's asynchronous calls, and I'm having interesting variable modification conflicts? Or is it something else?
When you use asynchronous callbacks in JavaScript, they execute later... asynchronously. So if you have:
var x = 5;
console.log("before getJSON", 5);
$.getJSON("/some/url", function (json) {
x = 10;
console.log("inside callback", x);
});
console.log("after getJSON", x);
the output will be
before getJSON 5
after getJSON 5
inside callback 10
Any code you want to execute after the request returns must be inside the callback; putting it physically "after" the $.getJSON call will not suffice. You should think of $.getJSON as "firing off" the JSON-getting process, then immediately returning to you; only later, when your script is done executing normal code and the server has responded, will the JavaScript event loop say "hey I got a response and am idle; time to call that callback that was waiting on the response!"
Because of this, your updateValues function will need to accept a callback of its own in order to notify its own caller that the values have been updated; just calling updateValues will only fire off the value-updating process, and the values won't be updated later until that idle time. Something like:
function updateValues(onUpdated) {
var url = "http://search.twitter.com/search.json?callback=?&q=";
var cls = "Penn_CIS240"
var query = "%23" + cls;
var voteCount = [0,0,0,0];
$.getJSON(url + query, function (json) {
// use of json to update voteCount ellided
onUpdated(voteCount);
});
}
Then calling code uses it as
updateValues(function (voteCount) {
// use the updated vote counts inside here.
});

Categories