Rerun a function while a list still has elements - javascript

I think this may be a fairly trivial issue but I am still quite new to Node and Javascript.
I have a list and a function as follows:
var my_list = [1, 7, 9, 112, 15, 17, 19, 25, ...]
// Main search function
const search = async(_) => {
// Loop from ID 1 to X
for (let index = 1; index < 10000; index++) {
if (my_index.indexOf(index) !== -1) {
// Try to find a brewery with this ID
const brewery = await findBreweriesById(index);
// If it's found ..
if (brewery) {
console.log(brewery.brewery_name);
}
} else {
console.log(`Nothing found for ID ${index}..`);
}
}
console.log("Finished searching!");
};
search();
The function is going to be running as a call to a rate limited API which means not all values in my_list will be fetched at once. I have to repeat the fetching function. What I want to do is to be able to remove any successfully fetched value from the main list, i.e. my_list and rerun the function until all values in my_list have been fetched.
I know two things,
I need to remove the fetched index element from the list. I have figured out I could do this using my_list.splice(indexOf(index),1)
I know I can use a while loop but I have no idea where exactly I'd put it and what condition to set. This also confuses me as to where exactly then I would be splicing to remove the element.
Any help would be appreciated.
EDIT
To be more specific, the API call is rate limited to 100 calls per minute. After that it returns a 429 Too many requests. So I want to remove all the 200 returns and recycle, i.e. rerun the function for the 429 which should remain in the list.

Maybe try this:
var my_list = [ 1, 7, 9, 112, 15, 17, 19, 25 ]
// Main search function
const search = async _ => {
while( my_list.length > 0 ){
const index = my_list[my_list.length - 1]
// Try to find a brewery with this ID
const brewery = await findBreweriesById(index);
if (brewery) {
my_list.splice(indexOf(index),1)
console.log(brewery.brewery_name);
} else {
console.log(`Nothing found for ID ${index}..`);
}
}
}
search();

Related

Updating Json Value with that of another Json

I want to update automatically the value of comments_list with the values in the comments JSON object
const tweet = JSON.stringify({"tweet_id":1,"created_at":"2022-06-28","comments_list":[]})
const comments = JSON.stringify({"tweet_id":1,"commenter_id": 2"commenter_first_name":"tito","commenter_username":"tito_lulu"})
The final output should look like this
{"tweet_id":1,"created_at":"2022-06-28","comments_list":[{"commenter_id": 2"commenter_first_name":"tito","commenter_username":"tito_lulu"}]}
I'd work with those strings in an object form, otherwise string-manipulation could be slow in some cases.
This is by no means the fastest solution but perhaps the idea behind it can be helpful.
const tweet = [{
"tweet_id": 1,
"created_at": "2022-06-28",
"comments_list": []
}]; // There could be many tweet objects so wrap it in an array
const comments = [{
"tweet_id": 1,
"commenter_id": 2,
"commenter_first_name": "tito",
"commenter_username": "tito_lulu"
},
{
"tweet_id": 1,
"commenter_id": 5,
"commenter_first_name": "me-too",
"commenter_username": "me294"
}
]; // Same here, could be many comments right?
let UpdatedTweets = [];
// There are faster ways to do this, but for your question
tweet.forEach((tweet, tweetIndex) => {
// Loop each tweet
let post = tweet;
comments.forEach((comment, commentIndex) => {
if (comment.tweet_id == tweet.tweet_id) {
// we have a match lets combine them
tweet.comments_list.push({
commenter_id: comment.comment_id,
commenter_first_name: comment.commenter_first_name,
commenter_username: comment.commenter_username
});
}
});
UpdatedTweets.push(post);
});
console.log(JSON.stringify(UpdatedTweets));
The general idea is:
Parse the JSON into JS objects
Update the target object with the complementary information
Stringify the target object into JSON (only if you need to, eg. send the data to some other machine)
In your case:
const tweet = JSON.stringify({"tweet_id":1,"created_at":"2022-06-28","comments_list":[]});
const comments = JSON.stringify({"tweet_id":1,"commenter_id": 2,
"commenter_first_name":"tito","commenter_username":"tito_lulu"});
let o_tweet = JSON.parse(tweet)
, o_comments = JSON.parse(comments)
;
if (Array.isArray(comments)) { // Test whether that is a single or multiple comments
comments.forEach( c => { o_tweet.comments_list.push(c); });
} else {
o_tweet.comments_list.push(o_comments);
}
console.log(o_tweet);
// Only if needed:
// let newtweet = JSON.stringify(o_tweet)

Retrieving a full row of google sheets data to JavaScript

I am trying to build a form that can recall data based on a specific number being inputted using Google Scripts and JavaScript. When a number is inputted the JavaScript should call the Google Scripts so that it can locate the values I need based on the number. For example bellow is a google sheets. I want when the user inputs a number it searches for that value in column C and then grabs the data from that row. Ex. if the number is 14 then values: 2021-05-12, 5, 6 and 7 are returned to the JavaScript.
UPDATED:
Everything I want is working however, when I try to retrieve the date from the array in the JavaScript it does not work. The numbers are the only thing functioning. I know the date is in the array as I can see it in the logs.
JavaScript:
function callDataRetriever(){
var number = document.getElementById("number").value;
google.script.run.withSuccessHandler(dataRetriever).retreiveData(number);
}
function dataRetriever(data){
document.getElementById("location").value = data[0]; //This works
document.getElementById("dateOpened").value = data[1]; //This does not work. Stops the function from continuing its task.
document.getElementById("value1").value = data[2]; //Without the date everything here down works
document.getElementById("value2").value = data[3];
document.getElementById("value2").value = data[4];
document.getElementById("value4").value = data[5];
//...
}
Google Scripts (I have 28 total values) Everything here works perfectly as seen in the logs bellow.
function retreiveData(number){
var url = "urlToSpreadsheet";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Data");
var data = ws.getRange(1,1, ws.getLastRow(), ws.getLastColumn()).getValues();
var dataValues = [];
var filterData = data.filter(
function(r){
if(r[2] == number){
var i = 3;
while(i < 29){
dataValues.push(r[i]);
i++;
}
}
}
)
return dataValues;
}
I am not sure if the problem lies in the way the date is formatted.
Thank you!
You can use filter to perform the operation you want
var filterData = data.filter(
function(r){
//Select the index init in 0 in your case is 2
return r[0] == 'YOUR_SEARCH_VALUE'
}
)
// Use your filter data
ui.alert(filterData[0]) //data[row][column]
I believe your goal as follows.
When number is given and run the function retreiveData, you want to search the value of number from the cells "C4:C" of "Data" sheet, and want to retrieve the values of the columns "D", "E" and "F" for the searched row.
In this case, I would like to propose to use TextFinder. When the TextFinder is used, searching value is run in the internal server side. By this, the process cost will be a bit low. Ref
When this is reflected to your script, it becomes as follows.
Modified script:
In this case, please modify retreiveData at Google Apps Script side as follows.
function retreiveData(number){
var url = "linkToSpreadsheet";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Data");
var data = ws.getRange("C4:C" + ws.getLastRow()).createTextFinder(number).matchEntireCell(true).findNext();
if (data) {
return data.offset(0, 1, 1, 4).getValues()[0];
}
return Array(4).fill("No value");
}
And, please modify dataRetriever at Javascript side as follows.
function dataRetriever(data){
console.log(data) // Here, you can check the value from `retreiveData` at the console.
data.forEach((v, i) => {
document.getElementById(`value${i + 1}`).value = v;
});
}
Note:
In this case, when the search value of number is not found, No value is put to the input tags of value1 to value4. When you want to modify this, please modify return Array(4).fill("No value"); for your actual situation.
References:
Benchmark: Process Costs for Searching Values in Spreadsheet using Google Apps Script
Class TextFinder
There are two ways:
var data = [
[1, 55, 5545, 54, 51],
[2, 45, 541, 848, 1215],
[3, 323, 3232, 215, 3051],
[4, 13, 5151, 1513, 2315]
]
number = 3;
// via object where the 'numbers' are keys
var data_obj = Object.fromEntries(data.map(d => [d[0], d.slice(1,)]));
console.log(data_obj[number]); // [ 323, 3232, 215, 3051 ]
// via filter (about the same as #macorreag's answer)
var res = data.filter(d => d[0] == number)[0].slice(1,);
console.log(res); // [ 323, 3232, 215, 3051 ]
An object makes sense in the cases when you need to extract info from the same data several times. For single requests a filter looks better, I think.
And if the numbers always start from 1 and go sequentially 2, 3, 4, 5, ... etc, the simpliest solution is an array and its indexes:
var data = [
[1, 55, 5545, 54, 51],
[2, 45, 541, 848, 1215],
[3, 323, 3232, 215, 3051],
[4, 13, 5151, 1513, 2315]
]
var number = 3;
var data_array = data.map(d => d.slice(1,));
console.log(data_array[number-1]); // [ 323, 3232, 215, 3051 ]
In this case you don't even need the first column 'numbers' at all. You can just extract the next four columns and get the rows by indexes (-1): number 1 is data[0], number 2 is data[1] etc.

createTextFinder always finds a match in the first row when it shouldn't

I am having a problem with the createTextFinder function. It always finds a match in the first row of the sheet when it should not.
What I am trying to do is to find out if the data in an array is in the sheet and if so, copy the row with the match to another sheet and delete it from the original. However, the most important thing is that I only need to transfer the first match, i.e. if there are two elements in the sheet that appear in the search array, I only have to delete one of them and transfer them once from the original sheet to the new one.
The code I am running is:
function moveData(){
let dataToSearch = [22, 10, 12];
const date = new Date();
dataToSearch.forEach(data => {
let dataFinder = sheet.createTextFinder(data.toString());
let result = dataFinder.findAll();
if(result.length>0){
let matchValues = sheet.getRange(result[0].getRow(), 1, 1, 5).getValues();
for (let value in matchValues){
found.appendRow([date, "user", matchValues[value][0], matchValues[value][1], matchValues[value][2], matchValues[value][3], matchValues[value][4]]);
sheet.deleteRow(result[0].getRow());
}
}
})
}
This would be the sheet without executing the code:
Expected output:
As you can see in the image, only the first entries of id 22, 10 and 12 have been removed, leaving the rest on the sheet.
Any idea why it takes the first element of the sheet even though it is not in the array?
Explanation:
When I execute your code, I get the expected output as shown in your screenshot.
However, since you want to remove the first match only, there is no need of using that inner for (let value in matchValues) loop, since you only want to delete/move one match only.
Also it is not a good idea to use appendRow iteratively but instead append the data to an array and then use setValues to copy the data.
Modified Script:
function moveData(){
let dataToSearch = [22, 10, 12];
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName('Sheet1'); // change that to your case
const found = ss.getSheetByName('found'); // change that to your case
const date = new Date();
const found_data = [];
dataToSearch.forEach(data => {
let dataFinder = sheet.createTextFinder(data.toString());
let result = dataFinder.findAll();
if(result.length>0){
let matchValues = sheet.getRange(result[0].getRow(), 1, 1, 5).getValues();
found_data.push([date, "user", ...matchValues.flat()]);
sheet.deleteRow(result[0].getRow());
}
});
found.getRange(found.getLastRow()+1,1,found_data.length,found_data[0].length).setValues(found_data);
}

Why does function A work and function B throw an error?

I have 2 functions, one works and the other does not - the reason the second function is more important is that essentially I want to be able to change what's in the final array. Deconstruction is great and all but I don't expect to have the same incoming data landing in the same place - elsewhere I fixed this issue by creating individual arrays and just having all my functions return data by index, but I'm a little bamboozled here.
Heres the first one (createLogs) that does what I want it to do and if the data always came, in the same way, would do more than enough.
function createLogs(array,runtimeArray) {
let basicsLog=[]
array.map(function callback(currentValue,index) {
let currentRuntime = runtimeArray[index]
let [,date,time,systemMode,systemSetting,calEvent,ProgramMode,coolSetTemp,heatSetTemp,currentTemp] = currentValue
basicsLog.push([index,date,time,systemMode,systemSetting,calEvent,ProgramMode,coolSetTemp,heatSetTemp,currentTemp,currentRuntime])
})
console.log(basicsLog)
return basicsLog
}
Returns
[ 36,
2018-11-19T05:00:00.000Z,
'second failure',
'heat',
'heatOff',
'hold',
'Sleep',
70,
70,
69.5,
135 ],
[ 37,
2018-11-19T05:00:00.000Z,
'2:35:00',
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
null ] ]
Now here is createResults() - I think much better for a situation where what comes in is highly variable (basically its a table , and the columns in the table are different in different situations)
createResults() looks like this:
function createResults(originalThermostatArray,dateArray,timeArray, systemSettingsArray, systemModeArray, calendarEventArray, programModeArray, coolSetTempArray, heatSetTempArray, currentTempArray, thermostatTempArray, outdoorTempArray, combinedRuntimesArray) {
let resultArray = []
originalThermostatArray.forEach(function(currentValue,index) {
let currentDate = dateArray[index]
let currentTime = timeArray[index]
let currentSystemSetting = systemSettingsArray[index]
let currentSystemMode = systemModeArray[index]
let currentCalEvent = calendarEventArray[index]
let currentProgramMode = programModeArray[index]
let currentCoolSetTemp = coolSetTempArray[index]
let currentHeatSetTemp = heatSetTempArray[index]
let currentAvgTemp = currentTempArray[index]
let currentThermostatTemp = thermostatTempArray[index]
let currentOutdoorTemp = outdoorTempArray[index]
let currentRuntime = combinedRuntimesArray[index]
resultArray.push([index, currentDate, currentTime, currentSystemSetting, currentSystemMode, currentCalEvent,currentProgramMode, currentCoolSetTemp, currentHeatSetTemp, currentAvgTemp, currentThermostatTemp, currentOutdoorTemp, currentRuntime])
})
console.log(resultArray)
return resultArray
}
And Returns
[ 36,
2018-11-19T05:00:00.000Z,
'second failure',
'heat',
'heatOff',
'hold',
'Sleep',
70,
70,
69.5,
69.5,
23.5,
135 ],
[ 37,
2018-11-19T05:00:00.000Z,
'2:35:00',
null,
null,
null,
null,
null,
null,
null,
null,
23.5,
null ] ]
On the front end the code looks like this
div.resultSummaries.equipmentSummary
h1.summaryHeader.equipmentSummaryHead Possible Equipment Failures
h3.equipmentSummaryBody #{equipresultCount}
p.equipmentSummaryBody This table looks for increasing thermostat temperatures during a cooling call.
table.fullResults.equipmentResultsTable
caption.equipTitle Possible Equipment Failures Log
thead
tr
th.resultHeader.equipmentHeader Index
th.resultHeader.equipmentHeader Date
th.resultHeader.equipmentHeader Time
th.resultHeader.equipmentHeader System Setting
th.resultHeader.equipmentHeader System Mode
th.resultHeader.equipmentHeader Calendar Event
th.resultHeader.equipmentHeader Program Mode
th.resultHeader.equipmentHeader Cool Set Temp
th.resultHeader.equipmentHeader Heat Set Temp
th.resultHeader.equipmentHeader Avg Temp
th.resultHeader.equipmentHeader Thermostat Temp
th.resultHeader.equipmentHeader Outdoor Temp
th.resultHeader.equipmentHeader Runtime(sec)
tr
tbody
- let equipFailsResultLog = result.equipLogs;
for item in equipFailsResultLog
tr.equipRow
for item in equipArray
td.resultTable.result.equipResult #{item}
tfoot
But createResults() returns this error on the front end:
/views/upload.pug:137 135| for item in equipFailsResultLog 136| tr.equipRow > 137| for item in equipArray 138| td.resultTable.result.equipResult #{item} 139| tfoot 140| Cannot read property 'length' of undefined.
I think the fact that they return differently is causing the error but why? note that I tried writing createResults() as a for each and a map
function1 (createlogs) allows me to get into the array and access the individual elements and place them correctly
function2 (createResults) does not..
How can I access the nested elements using function 2 or display them the way I would want to?
added:
create results is called like this
const basicLogsArray = returnLogic.createResult(originalThermostatArray,dateArray,timeArray,systemSettingsArray,systemModeArray,calendarEventArray,programModeArray,coolSetTempArray,heatSetTempArray,currentTempArray,thermostatTempArray,outdoorTempArray,combinedRuntimesArray)
create log is called similarly but with just array and runtime array.
const equipFailsLogs = reportLogic.rowsByIndex(
basicLogsArray,
possibleEquipmentFailuresArray
)
console.log(equipFailsLogs)
This is what goes to the front end. (i run another function to get the whole row each bit of date is associated with and return it as an html table in pug.
return {
"Results": {
"coolCount":coolFailsRows.length/2 , coolLogs:coolFailsLogs,
"heatCount":heatFailsRows.length/2 , heatLogs:heatFailsLogs,
"miscCount":miscFailsRows.length/2 , miscLogs:miscfailsLogs,
"equipCount":equipFailsLogs.length/4 , equipLogs:equipFailsLogs,
}};
}
exports.runThermostat = runThermostat

AJAX - parallel calls with sequential update?

I have a table. Initially it is blank. there are supposed to be 10 rows in total. For that each row has to make a ajax call and get the data. so total 10 parallel AJAX calls. and requirement is also to update the rows sequentially. which means even if the data of row 5 comes before 2, the row 5 data is not update until row 2, 3 and 4 is updated. How do we achieve this?
One solution might be to use jQuery's promise interface alongside passing in an array of those promises to $.when using apply:
var urls = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// return a promise for each url
function getData(url) {
return $.ajax({
url: url
});
}
// return an array of promises
function getPromises(urls) {
return urls.map(function (url) {
return getData(url);
});
}
// when all the promises have been returned loop over the returned
// data and update the table sequentially with each row
$.when.apply(null, getPromises(urls)).then(function () {
for (var i = 0, l = arguments.length; i < l; i++) {
// update row with the data from arguments[i]
}
});
Note that I've not tested this code, but this is certainly the route I would attempt to take.

Categories

Resources