I want to learn such new JavaScript features as fetch() and arrow functions. To this end, I selected a function from a recent app, and attempted to replace older features with new. Very little success. Here's my original function:
function popNames(arNumbers,ctrlName) {
var arSortedList = [];
var strNameList = "";
$.getJSON("NAME.json").done(function(zdata) {
$.each(arNumbers, function(i, ydata) {
$.each(zdata.NAME, function(k,v) {
if(v.idName == ydata) {// important: === did NOT work
if(ctrlName) arSortedList.push(v.last + ", " + v.first + ";" + v.idName);
else arSortedList.push(v.last + ", " + v.first);
}
}); // each element of NAME.json
}); // each idName value in the array passed
if(ctrlName) {
setOptions(arSortedList, ctrlName);
} else {
strNameList = arSortedList.join();
}
}); // getJSON NAME
}
I was successful using this line:
fetch("NAME.json").then(zdata => zdata.json())
but nothing I did after that worked. I'd appreciate seeing an example from which I can learn.
function popNames(arNumbers,ctrlName) {
let arSortedList = [];
let strNameList = "";
fetch("NAME.json").then(zdata => zdata.json())
.then(zdata => {
for(const ydata of arNumbers) {
for(const v of zdata.NAME) {
if(v.idName == ydata) { // important: === did NOT work
if(ctrlName) arSortedList.push(v.last + ", " + v.first + ";" + v.idName);
else arSortedList.push(v.last + ", " + v.first);
}
}
}
if(ctrlName) {
setOptions(arSortedList, ctrlName);
} else {
strNameList = arSortedList.join();
}
}); // getJSON NAME
}
I was researching why I couldn't next two Array.forEach statements, and discovered a new iterable construction (for...of).
Related
I have the following code:
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
};
return result;
};
var instance = "{{ user }}" + makeid(16);
var checksum = "First Request Not recieved";
console.log(instance);
function downloadPlay(){
console.log("\ndownloadPlay - Begin\n")
try{
fetch("/file?instance=" + instance + "&checksum=" + checksum)
.then(function(resp) {
resp.headers.forEach(
function(val, key) {
// console.log("key, val: " + key + ", " + val);
if(key == "checksum"){
console.log("checksum: " + val);
checksum = val;
};
}
);
}
)
.then(file => {
var audio = new Audio("/file?instance=" + instance + "&checksum=" + checksum);
console.log("Done");
audio.addEventListener('ended', (event) => {
delete audio;
downloadPlay();
});
audio.play();
}
)
} catch (error) {
console.log("Something went wrong, Retrying: " + error);
}
console.log("downloadPlay - Complete\n")
};
downloadPlay();
This works perfectly when the promise succeeds. However when it fails(such as when the client device switches networks, i.e. wifi to data or just different access points on the same wifi network) it stops dead and never resumes no matter how many while loops, extra recursion points or try and catch statements I use. The best I could do so far is get it to play ever increasing numbers of the audio mostly in sync with each other and I just dont understand why. It seems I have a general lack of understanding of how this promise thing actually functions, but no matter how many tutorials I read/watch my lack of understanding seems to remain unchanged.
Heres the code that somewhat worked if that helps:
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
};
return result;
};
var instance = "{{ user }}" + makeid(16);
var checksum = "First Request Not recieved";
console.log(instance);
function downloadPlay(){
console.log("\ndownloadPlay - Begin\n")
try{
console.log('fetching')
fetch("/file?instance=" + instance + "&checksum=" + checksum)
.then(function(resp) {
resp.headers.forEach(
function(val, key) {
// console.log("key, val: " + key + ", " + val);
if(key == "checksum"){
console.log("checksum: " + val);
checksum = val;
};
}
);
}
).catch(function(error) {
console.log('request failed', error)
console.log('retrying')
downloadPlay();
return;
})
.then(file => {
var audio = new Audio("/file?instance=" + instance + "&checksum=" + checksum);
console.log("Done");
audio.addEventListener('ended', (event) => {
delete audio;
downloadPlay();
});
audio.play();
}
)
} catch (error) {
console.log("Something went wrong, Retrying: " + error);
}
console.log("downloadPlay - Complete\n")
};
downloadPlay();
Any solution or very simple explanation on what im doing wrong would be much appreciated
Thanks in advance :)
You can do something like this
Just remove the comment and use your original fetching function
You can't use try catch with promises unless you use async await
const fakeChecking = Promise.resolve({headers: {checksum: 'aaaa'}})
const errorChecking = Promise.reject('error')
function downloadPlay(fetching) {
console.log("\ndownloadPlay - Begin\n")
console.log('fetching')
fetching
.then((resp) => resp.headers.checksum)
.then(checksum => {
/*var audio = new Audio("/file?instance=" + instance + "&checksum=" + checksum);
console.log("Done");
/*audio.addEventListener('ended', (event) => {
delete audio;
downloadPlay();
console.log("downloadPlay - Complete\n")
});
audio.play();*/
console.log("downloadPlay - Complete\n")
})
.catch(function(error) {
console.log('request failed', error)
console.log('retrying')
downloadPlay(fakeChecking);
})
};
downloadPlay(errorChecking);
I have a question. I created the following functions:
function checkGender(canidate, callback) {
var query = "SELECT a.* FROM (SELECT Id AS GebruikerId, TIMESTAMPDIFF(year, profiel_Geboortedatum, NOW()) AS Leeftijd FROM " +
"gebruikers WHERE Id = " + canidate.MedereizigerId + ") a INNER JOIN gebruikers ON gebruikers.Id = " + canidate.GebruikerId + " WHERE a.Leeftijd >= gebruikers.medereiziger_MinLeeftijd AND " +
"a.Leeftijd <= gebruikers.medereiziger_MaxLeeftijd GROUP BY a.GebruikerId;";
FYSCloud.API.queryDatabase(query).done(function (data) {
if (data.length == 1) {
callback(data);
}
else {
callback(null);
}
}).fail(function (reason) {
console.log(reason);
callback(null);
});
}
function checkAge(canidate, callback) {
var query = "SELECT a.* FROM (SELECT Id AS GebruikerId, TIMESTAMPDIFF(year, profiel_Geboortedatum, NOW()) AS Leeftijd FROM " +
"gebruikers WHERE Id = " + canidate.MedereizigerId + ") a INNER JOIN gebruikers ON gebruikers.Id = " + canidate.GebruikerId + " WHERE a.Leeftijd >= gebruikers.medereiziger_MinLeeftijd AND " +
"a.Leeftijd <= gebruikers.medereiziger_MaxLeeftijd GROUP BY a.GebruikerId;";
FYSCloud.API.queryDatabase(query).done(function (data) {
if (data.length == 1) {
callback(data);
}
else {
callback(null);
}
}).fail(function (reason) {
console.log(reason);
callback(null);
});
}
[...]
Now the queries are working like a charm but I am using the following code to call these functions:
for(var i=0; i<data.length; i++) {
// CHECK GESLACHT
checkGender(data[i], function(genderData) {
if(genderData != null) {
// CHECK LEEFTIJD
checkAge(data[i], function(ageData) {
if(ageData != null) {
// CHECK BUDGET
checkBudget(data[i], function(budgetData) {
if(budgetData != null) {
// CHECK VAKANTIELAND
checkDestinationCountries(data[i], function(destinationCountryData) {
if(destinationCountryData != null) {
// CHECK GESPROKEN TALEN
checkSpokenLanguages(data[i], function(spokenLanguagesData) {
if(spokenLanguagesData != null) {
}
});
}
});
}
});
}
});
}
});
}
What I am doing here is waiting for the function to finish and then continue with the next one, but only if the result of the function didn't return null. Now this takes up a lot of lines and tabs, so I was wondering if there was a beter way to ask everytime for a null value?
Please let me know, just out of curiosity
You can change your function to a Promise based result, instead of calling callbacks use resolve and reject, like this:
function checkAge(canidate) {
// create a new Promise and return it
return new Promise((resolve, reject) => {
var query = "SELECT a.* FROM (SELECT Id AS GebruikerId, TIMESTAMPDIFF(year, profiel_Geboortedatum, NOW()) AS Leeftijd FROM " +
"gebruikers WHERE Id = " + canidate.MedereizigerId + ") a INNER JOIN gebruikers ON gebruikers.Id = " + canidate.GebruikerId + " WHERE a.Leeftijd >= gebruikers.medereiziger_MinLeeftijd AND " +
"a.Leeftijd <= gebruikers.medereiziger_MaxLeeftijd GROUP BY a.GebruikerId;";
FYSCloud.API.queryDatabase(query).done(function (data) {
if (data.length == 1) {
resolve(data); // when successful, resolve with data
}
else {
reject('no data found'); // any error, call reject()
}
}).fail(function (reason) {
console.log(reason);
reject(reason); // any error, call reject()
});
});
}
With this, you can use async/await feature to write a generic validation method with all others validation functions, because any call to reject will throw a exception:
async function checkData(data) {
try {
const genderData = await checkGender(data);
const ageData = await checkAge(data);
const budgetData = await checkBudget(data);
const destinationCountryData = await checkDestinationCountries(data);
} catch (e) {
// some validation failed
}
}
You can use the && operator to check for a falsy value (null, 0, false, undefined, etc.) before executing a function.
value && functionCall(value)
That might make it a bit cleaner.
checkGender(data[i], genderData => genderData &&
checkAge(data[i], ageData => ageData &&
checkBudget(data[i], budgetData => budgetData &&
checkDestinationCountries(data[i], destinationCountryData => destinationCountryData &&
checkSpokenLanguages(data[i], spokenLanguagesData => spokenLanguagesData && console.log("It Works"))))))
I have a client-side web-application that takes a csv-file, parses it into various data types, searches for something specific, and displays a table with the answer on the screen. The search function returning a null string. This occurs because its search parameter, returned by a callback function and put into lib, returns null.
I'm fairly certain this is a callback issue, but I've messed around with the order so much I'm not sure what goes where anymore in my html...A second set of eyes would be appreciated.
The desired series of events
fileToArray() gives us an array
search() looks in the array for its specified item and returns a csv-format string containing what it found
displayTable takes that csv-format string and outputs it to the desired location
The Code
// jQuery call to fetch the client-side csv file - this works when called by itself.
const fileToArray = () => {
console.log("fileToArray started.");
$.get({
url: CSV_LOCATION,
dataType: "text",
success: function (result) {
console.log("splitting result by newline...");
let csvLines = result.split("\n");
console.log("split successful. generating array into retval ...");
let retval = [];
for (let i = 0; i < csvLines.length; i++) {
// [0][0] is number [0][1] is class, [0][2] is unit, [0][3] is lesson
retval[i] = csvLines[i].split(",");
}
console.log("success! Returning retval.");
return retval;
// callback(result);
// return result;
},
failure: function (xhr, status, error) {
console.log("ERROR: fileToString(): " + xhr + " ||| " + status + " ||| " + error);
alert("ERROR: fileToString(): " + xhr + " ||| " + status + " ||| " + error);
}
})
};
// PRECONDITION: form is #search-params in index.js
// > lib is the result of fileToArray()
// POSTCONDITION: result is a csv-format string to be passed to displayTable() in index.js
const search = (form, callback) => {
console.log("search called...");
// vvvvv The probable root of the problem vvvvv //
let lib = callback;
console.log(lib.length + " is lib's length.");
let result = "";
console.log("search nested for loop called...");
for (let i = 0; i < lib.length; i++) {
// check class
console.log("checking class " + form.class.value + "...");
if (lib[i][1] === form.class.value) {
// check unit
console.log("checking unit " + form.unit.value + "...");
if (Number(lib[i][2]) === Number(form.unit.value)) {
console.log("adding to result...");
result += lib[i] + "\n";
}
}
}
console.log("search success! result: " + result.length + " characters");
console.log(result);
return result;
};
<!-- I'm almost 100% certain I've messed up the callback in this button,
but I still don't quite understand how... I've played with
displayTable(fileToArray(search(...))), but I don't quite know how it should go -->
<button class="btn btn-primary"
onclick="displayTable(search(document.getElementById('search-params'), fileToArray), $('#card-display'))">
Submit
</button>
What I've tried
I have looked to the following sites for inspiration (none have helped):
JavaScript is Sexy
JavaScript: Passing parameters to a callback function
JavaScript Callback Functions
Passing arguments to callback functions
In Summary
It's painfully obvious I still don't understand callbacks fully. Any help would be appreciated.
You could use async / await
const displayTable = async () => {
let arrayFromFile = await fileToArray(); // fileToArray executes and assigns the returned value when it completes
let searchedData = search(form, arrayFromFile);
// Display the table
};
Thanks to #kapantzak for the inspiration!! Turns out, I was using callbacks horribly bass-ackwards. According to this, the old-school async style is something akin to
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
So, the relevant code now looks like this:
const fileToArray = (callback) => {
// console.log("fileToArray started.");
$.get({
url: CSV_LOCATION,
dataType: "text",
success: function (result) {
let csvLines = result.split("\n");
let retVal = [];
for (let i = 0; i < csvLines.length; i++) {
// [0][0] is number [0][1] is class, [0][2] is unit, [0][3] is lesson
retVal[i] = csvLines[i].split(",");
}
callback(retVal);
},
failure: function (xhr, status, error) {
console.log("ERROR: fileToString(): " + xhr + " ||| " + status + " ||| " + error);
alert("ERROR: fileToString(): " + xhr + " ||| " + status + " ||| " + error);
}
})
};
// =======
const search = (form, lib, callback) => {
let result = "";
let formClass = form.class.value.toLowerCase();
let formUnit = form.unit.value.toLowerCase();
let formLesson = form.lesson.value.toLowerCase();
for (let i = 0; i < lib.length; i++) {
// check class
if (lib[i][1].toLowerCase() === formClass) {
// check unit
if (Number(lib[i][2].toLowerCase()) === Number(formUnit)) {
result += lib[i] + "\n";
}
}
}
console.log(result);
callback(result);
};
<button class="btn btn-primary"
onclick="fileToArray(function(result) {
search(document.getElementById('search-params'), result, function(newResult) {
displayTable(newResult, $('#card-display'));
});
});">
Submit
</button>
This righted the wrongs and caused my search and display to function properly.
I'm struggling to get to grips with creating "static" object types in JavaScript (I doubt I've even worded that correctly...).
In the following code, I'm trying to create a jQuery plugin (I suspect that is irrelevant, though), which creates multiple CrmQuery each with their own contextual information passed in as instance. I also want to create a "static" object for all the CrmQuery to use (it can be stateless), and was hoping to contain it within the CrmQuery namespace.
This is the structure I have, but when calling CrmQuery.searchOperator[instance.Operator].buildCondition(instance.fieldName, searchTerm), the buildCondition function doesn't have access to the oDataOperator function in CrmQuery.searchOperator. I've tried referencing it in many ways, not just searchOperator.oDataOperator, and this is a reference to the actual property, not CrmQuery.searchOperator.
Structure:
(function($) {
...
var crmQuery = new CrmQuery(instance);
var data = crmQuery.searchCrm(searchTerm);
...
var CrmQuery = function(instance) {
...
this.oDataUrl = function() {
...
oDataFilter += CrmQuery.searchOperator[instance.Operator].buildCondition(instance.fieldName, searchTerm);
...
return oDataFilter;
}();
this.searchCrm(searchTerm) {
...
url += this.oDataUrl;
}
...
}
CrmQuery.searchOperator = {
oDataFunctionOperator: function oDataFunctionOperator(fieldName, searchTerm, operator) {
return operator + "(" + fieldName + ", '" + searchTerm + "')";
},
oDataOperator: function oDataOperator(fieldName, searchTerm, operator) {
return fieldName + " " + operator + " '" + searchTerm +"'";
},
STARTSWITH: {
operator: "startswith",
buildCondition: function(fieldName, searchTerm) {
return this.oDataFunctionOperator(fieldName, searchTerm, this.operator)
}
},
ENDSWITH: {
operator: "endswith",
buildCondition: function(fieldName, searchTerm) {
return searchOperator.oDataFunctionOperator(fieldName, searchTerm, this.operator)
}
},
CONTAINS: {
operator: "substringof",
buildCondition: function(fieldName, searchTerm) {
return searchOperator.oDataFunctionOperator(fieldName, searchTerm, this.operator)
}
},
EXACTMATCH: {
operator: "eq",
buildCondition: function(fieldName, searchTerm) {
return searchOperator.oDataOperator(fieldName, searchTerm, this.operator)
}
},
toString: function () {
var thisVar = this;
var output = "searchOperator enum. ";
output += $.map(Object.keys(this), function (i) {
if (typeof thisVar[i] !== "function")
return i + ": " + thisVar[i].operator;
}).join(", ");
return output;
}
}
...
}(jQuery));
What is the correct way to implement a pattern like this in JavaScript?
I hope this makes sense? Apologies if it's too verbose, but I didn't want to leave anything out that may be important.
Many thanks,
James
inheritance is one way to solve this, for example:
function searchOperator() {
}
//searchOperator.prototype.toString = ...
functionOperator = function(operator) {
searchOperator.apply(this);
this.operator = operator;
}
functionOperator.prototype = Object.create(searchOperator.prototype);
functionOperator.prototype.buildCondition = function (fieldName, searchTerm) {
return this.operator + "(" + fieldName + ", '" + searchTerm + "')";
}
operators = {
STARTSWITH: new functionOperator('startswith')
}
console.log(operators.STARTSWITH.buildCondition('foo', 'bar'))
I'm trying to repurpose a "legacy function" to pass a function with parameters into another function and get called. I've seen bits and pieces of what I'm looking for, but my arguments keep getting passed as a single string. This is the calling code - the 4th parameter (starts with '_delRideNew') is what I need to call.
MODAL.show("Confirm", "Are you sure you want to delete this ride?","Yes","_delRideNew('" + id + "','" + day + "','" + attuid + "')","No","MODAL.hide();")
Here is the MODAL.show code (using easyui):
MODAL.show = function(title, msg, okbtn, okcallback, canbtn, cancallback) {
if(arguments.length > 2) {
$.messager.defaults.ok = okbtn;
$.messager.defaults.cancel = canbtn;
}
else {
$.messager.defaults.ok = "OK";
$.messager.defaults.cancel = "Cancel";
}
if(arguments.length === 6) {
var me = $.messager.confirm(title, msg, function(r) {
if(r) {
//parse out function and args
var pos = okcallback.indexOf("(");
var func = okcallback.substring(0,pos);
var argss = okcallback.substring(pos,okcallback.length);
argss = argss.replace("(", "");
argss = argss.replace(")", "");
var argArray = argss.split(",");
window[func](argArray);
}
else {
cancallback;
}
});
me.window('move',{
left:400,
top:document.body.scrollTop+document.documentElement.scrollTop+200
});
}
else {
confirm(msg, function(r) {
if(r) {
return true;
}
else {
return false;
}
});
}
}
The problem is when the window[func] gets called it passes the array as a single string here:
function _delRideNew(id,day,attuid){
alert(id); //shows all 3 params as a string
var txtURL = 'delRide.cfm?tech_attuid=' + attuid + '&ride_date=#getParam("month")#/' + day + "/#getParam("year")#";
SYS.loadScript(txtURL);
status = txtURL;
}
It's very much possible that I'm doing this completely wrong, but any help would be, well..helpful.