JS looping trough fetch in API - javascript

Ok, so recently I've been working on this code, and I was so puzzled and frustrated why it is ordering the output randomly. But then later I realized that it is loading which ever channel loads fastest, then it's index becomes 0, which ever loads the fastest next, becomes 1, whichever loads fastest next becomes 2, etc.
var channels = [
"UCIgnIPif1LQxkdbvBmGNNuA", // channel 1
"UC_Qs0JhHoysG4LItMgGMHHQ", // channel 2
"UC_puGdwVL1kc5VRhaZc6Arw", // channel 3
"UCQvwHZVCrljzvaLVX33Qaaw" // channel 4
];
var key = "cant share but i will tell you the output of the code"
var subCounts = [];
function getSubs(id, key){
for(var i = 0; i < channels.length; i++){
fetch('https://www.googleapis.com/youtube/v3/channels?part=statistics&id='+channels[i]+'&key='+key+'')
.then(response => {
return response.json()
}).then(data => {
//name[i].innerHTML =
subCounts.push(data['items'][0].statistics.subscriberCount);
});
} // for loop close
}
setTimeout(function(){
console.log(subCounts);
// output example: 4, 5, 6, 7
// another output: 5, 7, 6, 4
// another output: 7, 4, 5, 6
}, 1500) // good enough for the fetch to load
it's just random (which ever loads first comes first).
What I want it to do though:
I want it to load based on the order of the channels. So channel 1 needs to load first, then channel 2, then channel 3, etc. Instead of which ever loads first.
Any help, suggestions, tips would be appriciated.
Thank You.

Assuming that it is OK for you to wait for all requests to complete before you begin handling their responses, then Promise.all should allow you to accomplish your goal. Below is your code rewritten roughly to use Promise.all:
var channels = [
"UCIgnIPif1LQxkdbvBmGNNuA", // channel 1
"UC_Qs0JhHoysG4LItMgGMHHQ", // channel 2
"UC_puGdwVL1kc5VRhaZc6Arw", // channel 3
"UCQvwHZVCrljzvaLVX33Qaaw" // channel 4
];
var key = "cant share but i will tell you the output of the code"
var subCounts = [];
function getSubs(id, key) {
var requests = [];
var currentRequest;
for (var i = 0; i < channels.length; i++) {
currentRequest = fetch('https://www.googleapis.com/youtube/v3/channels?part=statistics&id=' + channels[i] + '&key=' + key + '');
requests.push(currentRequest);
} // for loop close
Promise.all(requests).then((responses) => {
for (let j = 0; j < responses.length; j++) {
const currentResponse = responses[j];
const data = currentRequest.json();
subCounts.push(data['items'][0].statistics.subscriberCount);
}
});
}
setTimeout(function () {
console.log(subCounts);
// output example: 4, 5, 6, 7
// another output: 5, 7, 6, 4
// another output: 7, 4, 5, 6
}, 1500) // good enough for the fetch to load
Please note that I generally opted to use modern variable declarations of let/const-- you won't generally want to mix the older var into code using let/const so it would be best to update all of it, or, less desirable, use var for all declarations. Also, this isn't tested (obviously), so it will likely need some rework for your purposes.
If every millisecond counts and you needed to process them in order but as soon as they came in, this would need to be written differently. If you reply as such I can try to put together a POC.

This would be a good use for Promise.all (docs)
Something like:
Promise.all(
channels.map(
channel => fetch('https://www.googleapis.com/youtube/v3/channels?part=statistics&id='+channel+'&key='+key+'')
)
)
.then(Promise.all((responses) => responses.map(response => response.json()))
.then(responses => {
// now responses is an array in order of the results that you can process as you wish
});
An alterantive would be to make subCounts be of the same length as channels, and place the ith channel result at index i in the subCounts array.

You can use async await,
var data = await fetch('https://www.googleapis.com/youtube/v3/channels?part=statistics&id='+channels[i]+'&key='+key+'')
.then(response => {
return response.json()
}).then(data => {
//name[i].innerHTML =
});
subCounts.push(data['items'][0].statistics.subscriberCount);

Related

javascript execute filtering after foreach loop is completed

I have a data from an api request and tried to store it in a temporary array using foreach and filter the temporary array after the foreach is completed.... in my case, I added a counter to compare if the counter is equal the length of api data. However, I notice that it only worked when the api data is small, say, data.length = 15 but when it reaches 20+, somehow it ignores the counter.
here's my code:
var flag = 0;
dato.forEach((el) => {
temp.push({
id: el.id,
sex: el.sex,
name: el.name,
[self.momentFormat(el.date, 'YYYY-MM-DD').toString()]: ({
activity: el.activity,
status : el.status,
record: el.record
})
});
flag++;
});
setTimeout(() => {
//This is what I get when I pass a parameter with date from Apr 1 to Apr 15,
//But when I pass Apr 1 to Apr 20... None of the logs shows up even though the api data is not empty
console.log('dato length=', dato.length); //I get 8
console.log('temp length=', temp.length); //I get 13
console.log('flag length=', flag.length); //I get 8
},5000)
if(flag == dato.length) {
var result = temp.filter(function(v) {
return this[v.id]?
!Object.assign(this[v.id], v) : (this[v.id] = v)
}, {});
self.overtime_dtr_records = result;
}
I've tried setting a timeout but I dont think its the most optimal way. Aside from defeating the purpose of fast api request, I also noticed that some of the temporary array data are not included in the fitlering process.
UPDATE: I think comparing a counter to data length is correct.The problem is that when I log the length of api data, I get 45, but the loop only iterates 4 times

How do I iterate over a list inside an observable?

I'm still pretty much a beginner when it comes to Angular and spent the last few days looking for a solution. I'm accessing an API that returns pages of JSON results in the following format:
{"count": 242, "next": <link to the next page>, "results":[]}
My get function looks like this:
getJournals(searchterm, limit?: number, offset?: number): Observable<any> {
var endpointurl = this.globalvars.apiv1Url + "search-journal?q=" + searchterm;
if (limit != null) {
endpointurl = endpointurl + "&limit=" + limit;
}
if (offset != null) {
endpointurl = endpointurl + "&offset=" + offset;
}
return this.http.get(endpointurl).pipe(map((res: Response) => this.extractJournals(res)));
}
The extract function processes the results and hands them over to a factory so I get journal objects that fit my datamodel.
private extractJournals(res: Response) {
let body: any = { count: res["count"], next: res["next"], journals: [] };
if (body["count"] > 0) {
for (var index = 0; index < res["results"].length; ++index) {
var journalInfo: any = res["results"][index];
this.journalFactory.create(journalInfo, body.journals);
}
}
return body || {};
}
The second parameter of the create function is optional and accepts a list to push results back into.
Now for the problematic part. The page that calls getJournals (the journal declaration fits the journals into an ngx-datatable):
getJournals(limit: number, offset: number) {
this.page.limit = limit;
this.page.offset = offset;
this.api.getJournals("", limit, offset).subscribe((result) => {
console.log(result);
this.page.count = result["count"];
if (result["count"] > 0) {
console.log(result["journals"].length);
result["journals"].forEach((currentjournal) => {
console.log(currentjournal);
var journal: any = {};
this.rows.push(journal);
});
this.rows = [...this.rows];
}
});
}
The first console log returns what I would expect. I know it's an observable and returns not what was inside it at the moment it was logged but gets evaluated as I click on it in the browser. The log contains all my journals in the form I want and everything is fine. The second log returns '0' and the third gets never called.
From what I understand the iteration would work if my observable itself returned a list but it doesn't because I want to iterate over a list that's nested in the result(?). What's also interesting is that result["count"] returns the right result from the API at any time and it gets filled in the extractJournals function. I think that means extractJournals filled the body variable but doesn't wait for the for loop before it returns a result? But it does process the for loop at SOME point because the first log contains the for loop results.
I'm really out of ideas. How do I get this last forEach loop to work since it apparently doesn't wait for the journals list to be filled? I also tried a regular for loop with index btw.
Make advantage of the fact that typescript transpiler can point you where there is a typo in your code. For example:
let body: {count: number, next: string, journals: Journal[]} = {"count": res["count"], "next": res["next"], "journals": []};
this.journalFactory.create(journalInfo, body.journals);
// ^^^ Please notice typo here
Almost every IDE and transpilation process will throw an error when you give a type information to the typescript.

How can I get specific keys from this?

I've used hasOwnProperty and typeof in the past but this one is stumping me...
I'm trying to get all the keys that have keys that match so I can pair them with other keys example:
{"meals": [{
strIngredient1 : lemons
strIngredient2 : paprika
strIngredient3 : red onions
strIngredient4 : chicken thighs
strIngredient5 : vegetable oil
strMeasure1 : 2 Juice
strMeasure2 : 4 tsp
strMeasure3 : 2 finely chopped
strMeasure4 : 16 skinnless
strMeasure5 :
}]}
It's apparent that strIngredient1 matches with strMeasure1 etc...
Any suggestions or help would be greatly appreciated!!
Explained
In this example, you can see that I've provided the solution in two parts, one being a simple way to simply access 'x' ingredient from the array of meals, then another solution which will iterate over the array of meals, printing out each individual ingredient.
As I've stated within my solution, you can use forEach or alternatively, you can also use functions such as map or reduce if you wish. In the event that you don't know when to use which, the basic rule of thumb is that you'd use map or reduce if you wish to follow functional programming concepts. The forEach solution allows for side effects to happen more easily, etc... I mean this is debatable to a certain extent, but that's the basic idea anyways...
Edit
I've included a simple log function just for this demo, long story short, when you run this code snippet, personally I find it disgusting how little space is provided for the console window, so log one thing at a time after some delay and clear the console too.
let delay = 0;
const DELAY_INC = 1500;
// Just for this demo, have the ability to log something,
// after a delay and clear the console.
const log = (arg, alrt) => {
setTimeout(() => {
console.clear();
console.log(arg);
if (alrt != null) {
alert(alrt);
}
}, delay);
delay += DELAY_INC;
};
// Your data.
var data = {
"meals": [{
strIngredient1: 'lemons',
strIngredient2: 'paprika',
strIngredient3: 'red onions',
strIngredient4: 'chicken thighs',
strIngredient5: 'vegetable oil',
strMeasure1: '2 Juice',
strMeasure2: '4 tsp',
strMeasure3: '2 finely chopped',
strMeasure4: '16 skinnless',
strMeasure5: ''
}]
};
// Just some demo.
var meals = data.meals;
var meal = meals[0];
var ingredient = meal.strIngredient1;
log(data); // Log the raw data.
log(meals); // Log the array of meals.
log(meal); // Log a specific meal.
log(ingredient); // Log a specific ingredient.
// If you wish to iterate, log each ingredient for each meal.
data.meals.forEach(meal => Object.keys(meal).forEach(key => log(meal[key])));
// Here's a solution.
const newArray = data.meals.reduce((array, meal) => {
// Rather than iterate over ALL of the keys, just
// do this, basically 50% of the keys.
const subArray = Object.keys(meal).filter(key => key.indexOf('strIngredient' == -1));
// Basically add some ojects to the array.
subArray.forEach(key => {
const int = key.replace(/\D/g, '');
const measureKey = `strMeasure${int}`;
const ingredientKey = `strIngredient${int}`;
const obj = {
ingredient: meal[ingredientKey],
measure: meal[measureKey]
};
array.push(obj);
});
// Make sure to return the array.
return array;
}, []);
// Now just print the resuts, and make sure that you know
// and alert that the app has finished.
log(newArray, 'FINISHED');
For those interested or if it helps anyone here is the final product! All neat and tidy in one array, easy to use! :) Thank you again JO3-W3B-D3V!
getRecipe: function(url) {
request({
url: url,
method: 'GET'
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
var result = JSON.parse(body);
//console.log(result);
// Just some TESTING.
var meals = result.meals; //returns array
var meal = meals[0]; // returns object
//console.log(meal);
// Start here to rename keys and match them to the ingredients.
const newArray = meals.reduce((array, meal) => {
// Rather than iterate over ALL of the keys, just
// do this, basically 50% of the keys.
const subArray = Object.keys(meal).filter(key => key.indexOf('strIngredient' == -1));
// console.log(subArray);
// Basically add some ojects to the array.
subArray.forEach(key => {
const int = key.replace(/\D/g, '');
const measureKey = `strMeasure${int}`;
const ingredientKey = `strIngredient${int}`;
const obj = {
measure: meal[measureKey],
ingredient: meal[ingredientKey]
};
// console.log(obj); //Testing data before
if (obj.measure && obj.ingredient != 'undefined' || undefined || "" || null){
array.push(obj);
// console.log(array); //Testing data after
}
});
const recipeName = meal.strMeal;
const instruction = meal.strInstructions;
const video = meal.strYoutube;
const thumb = meal.strMealThumb;
const nation = meal.strArea;
const category = meal.strCategory;
const recipe = {recipeName, instruction, video, thumb, nation, category};
array.push(recipe);
//console.log(recipe); Testing full array
// Make sure to return the array.
return array;
}, []);
// Now just print the resuts, and make sure that you know
// and alert that the app has finished.
console.log(newArray, "FINISHED");

Create Table Based on Changing JSON Output

So to GET an output like this, I had to use some pretty cool tricks (see Analyzing Data from JSON in JavaScript). Please note my data in this example (# rows) is different.
var feeds = [].concat.apply([], dataSet_parsed.map(s => s.data));
//flattens arrays AGAIN to show all targets
var targets = [].concat.apply([], feeds.map(s => s.data));
//return target hits total
var targetList = targets.reduce(
function(prev, cur) {
var val = cur["val"];
prev[val] = ((prev[val] || 0) + 1);
return prev;
}, {});
// Output: {TargetA: 5, TargetB: 6, TargetC: 4, TargetD: 2}
Basically, I'm trying to get a count of how many times a target was seen per group. And fantastic! this works.
Here's my question. how do I display the output---
{TargetA: 5, TargetB: 6, TargetC: 4, TargetD: 2}
---in a table? There are no guarantees that I will always return the same TYPE of Targets (i.e. next load I could have:
{TargetK: 10, TargetL: 2, TargetM: 5, TargetN: 3, TargetO: 7, TargetP: 8}
I've tried using JSON.stringify and .replace() but I'm not getting very far. And even after that, I don't think I could style that output very well.
JSON.stringify(targetList).replace(/\}/g,'').replace(/\{/g,'')

update object one by one of asyn

For my app, there are matches and schedules. My idea is simple, but i cannot find a way to do it with angularjs for its asyn nature.
for matches, there are so called [a, b, c] three matches as an example.
for schedules, there are more than three resources such as [1, 2, 4, 5, 6, 7].
I would like to pair matches with available schedules so that [a1, b2, c4] with unique pairing.
I just wonder why the follow code doesn't work:
for (var u in matches){
var matchDefer = $q.defer();
var matchPromise = matchDefer.promise;
var match = matches[u]
var schedule = schedules[u];
Matches.get({matchId: match._id}, function(matchResponse){
console.info('schedule', schedule);
matchResponse.AddSchedule(schedule, matchDefer);
});
matchPromise.then(function(result){
console.info('result', result);
}, function(reason){
console.info('reason', reason);
});
}
And the following code is in the match service:
Matches.prototype.AddSchedule = function(schedule, defer) {
this.schedule = schedule._id;
this.$update(function(matchResponse){
schedule.match = matchResponse._id;
schedule.$update(function(scheduleResponse){
defer.resolve(scheduleResponse._id);
});
});
Could anyone help me even just give me some hints to do it, thanks!

Categories