I'm drowning in Promises trying to get a, what I thought would be simple, piece of data back from my GitHub Repos.
All I want to do is find the percentage of languages usage across each of my repositories on GitHub. I've got it working but I am pretty confident I'm miles away from doing it correctly or as succinctly as I could if I knew what I was doing. I would really appreciate any feedback on how to go about this the right way and possibly a refactor that uses more up to date methods.
Here's my code so far (which works). Ignore how I'm outputting it for now, that's just for the proof of concept.
const username = '<GH_USERNAME>' //your username here
const apiUrl = `https://api.github.com/users/${username}/repos`;
fetch(apiUrl)
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((data) => {
languagePromises = [];
for (let i in data) {
let repo = data[i];
if (!repo['fork'] && repo['language']) {
// console.log(repo['name'], repo['language']);
let langs = repo['languages_url'];
languagePromises.push(fetch(langs))
}
}
Promise.all(languagePromises)
.then((responses) => {
let languages = [];
for (let r of responses) {
if (r.ok) languages.push(r.json())
}
Promise.all(languages)
.then((resolved) => {
let languageTotals = {};
for (langData of resolved) {
for (lang in langData) {
if (!languageTotals[lang]) {
languageTotals[lang] = 0
}
languageTotals[lang] += (langData[lang]);
}
}
// console.log(languageTotals);
let sum = 0;
for (let l in languageTotals) { sum += languageTotals[l] }
for (let l in languageTotals) {
let p = (languageTotals[l] / sum * 100).toFixed(0) + '%';
document.write(l,' ', p, '<br>');
}
});
});
})
.catch((e) => {
console.error('Error:', e);
});
Like is say. It's working but I'm basically looking for advice on how to do this properly.
Here's an example that cleans/reduces a lot of your code:
const apiUrl = `https://api.github.com/users/${username}/repos`;
fetchRepos(apiUrl)
.then((data) => {
return (data
.filter(repo => !repo.fork && repo.language)
.map(repo => ghFetchJson(repo.languages_url))
)
)
.then(languageResults => {
const languageTotals = new Map();
for (const langData of languageResults) {
for (const [lang, count] of Object.entries(langData)) {
languageTotals.set(lang, (languageTotals.get(lang) ?? 0) + count);
}
}
const sum = Array.from(languageTotals).reduce((acc, cur) => acc+cur[0], 0);
for(const [language, count] of languageTotals) {
const p = (count / sum * 100).toFixed(0) + '%';
console.log('%s: %i', language, count);
}
});
}).catch((e) => {
console.error('Error:', e);
});
async function ghFetchJson(url) {
const res = await fetch(url);
return res.json();
}
const username = 'octocat'
const apiUrl = `https://api.github.com/users/${username}/repos`;
fetch(apiUrl)
.then((response) => {
if (!response.ok) {
throw new Error('Request failed')
}
return response.json();
})
.then((repos) => repos.filter(repo => !repo['fork'] && repo['language']).map(repo => fetch(repo['languages_url'])))
.then(e => Promise.all(e))
.then((resps) => resps.filter(r => r.ok).map(r => r.json()))
.then(e => Promise.all(e))
.then((resolved) => {
console.log(resolved)
const languageTotals = {};
for (const langData of resolved) {
for (const lang in langData) {
if (!languageTotals[lang]) {
languageTotals[lang] = 0
}
languageTotals[lang] += (langData[lang]);
}
}
let sum = 0;
for (const lang of languageTotals) {
sum += lang
}
for (const lang of languageTotals) {
let p = (lang / sum * 100).toFixed(0) + '%';
document.write(l, ' ', p, '<br>');
}
})
.catch((e) => {
console.error('Error:', e);
});
Related
i missed the first part of the code that was involved
router.hooks({
before: (done, params) => {
const page =
params && params.data && params.data.page
? capitalize(params.data.page)
: "Home";
if (page === 'Goexploring') {
axios
.get(process.env.NATIONAL_PARK_API_URL)
.then((response) => {
console.log(response);
let hikingActivity = [];
response.data.data[0].parks.map((parks) => {
let output = '';
hikingActivity.map((parks, index) => {
output += `${parks.states}: ${parks.states[activities]}, `;
return parks.states === 'TN';
});
console.log(hikingActivity);
state.Goexploring.parks = hikingActivity;
done();
})
.catch((err) => console.log(err));
});
}
still saying it's miss matched. i left out a little bit of the code before sorry! wouldn't let me put it in a comment
You are missing a couple of } and one ) at the end, fixed:
router.hooks({
before: (done, params) => {
const page =
params && params.data && params.data.page
? capitalize(params.data.page)
: "Home";
if (page === "Goexploring") {
axios.get(process.env.NATIONAL_PARK_API_URL).then((response) => {
console.log(response);
let hikingActivity = [];
response.data.data[0].parks
.map((parks) => {
let output = "";
hikingActivity.map((parks, index) => {
output += `${parks.states}: ${parks.states[activities]}, `;
return parks.states === "TN";
});
console.log(hikingActivity);
state.Goexploring.parks = hikingActivity;
done();
})
.catch((err) => console.log(err));
});
}
},
});
I'm currently looping through a paginated API like this, but it seems extremely bulky and repeated and I'm wondering if there's a simpler and faster way to do this.
async function myFunc () {
const totalPages = await getTotalPages()
const res = await new Promise(async (resolve) => {
const completedPages = []
const failedPages = []
let allPages = []
for (let pageNum = 0; pageNum < totalPages; pageNum++) {
getPage(pageNum).then((page) => {
allPages.push(...page)
completedPages.push(pageNum)
if ((completedPages.length + failedPages.length) === totalPages) {
return resolve({ all_pages: allPages })
}
}).catch((err) => {
console.log(pageNum, err)
failedPages.push(pageNum)
if ((completedPages.length + failedPages.length) === totalPages) {
return resolve({ all_pages: allPages })
}
})
}
})
return res
}
which get data from API:
const countries = [
'Spain',
'England',
];
const getLeagueID = () => {
const newData = [];
return new Promise(resolve => {
for (let i = 0; i < countries.length; i++) {
getApi(`https://www.thesportsdb.com/api/v1/json/1/search_all_leagues.php?c=${countries[i]}`)
.then(({ countrys }) => {
countrys.forEach((league, index) => {
if (league.strSport === 'Soccer') {
const getData = {
strSport: league.strSport,
strLeague: league.strLeague,
};
newData.push(getData);
}
if (index === countrys.length - 1 && i === countries.length - 1) {
resolve(newData);
}
});
})
.catch(err => {
console.log(err);
});
}
});
};
In first for loop I doing increment by countries from list.
When Api return data I create second foreach method. Inside this method I get data and push it to arra newData. The problem is with resolve:
if (index === countrys.length - 1 && i === countries.length - 1) {
resolve(newData);
}
I don't know how to write instruction if, which wait for the end foreach and for loop. My instruction if is wrong because not return all data. First time return 3 records, next time 7 records.
It's work, but sure that it can be improved
const getLeagueID = () => {
return new Promise((resolve, reject) => {
const promises = [];
for (let i = 0; i < countries.length; i++) {
promises.push(
getApi(`https://www.thesportsdb.com/api/v1/json/1/search_all_leagues.php?c=${countries[i]}`)
);
}
Promise.all(promises)
.then(res => {
const newData = [];
res.map(row => {
const data = JSON.parse(row);
const {countrys} = data;
countrys.forEach((league, index) => {
if (league.strSport === 'Soccer') {
const getData = {
strSport: league.strSport,
strLeague: league.strLeague,
};
newData.push(getData);
}
});
});
resolve(newData);
})
.catch(err => {
console.log(err);
reject(err);
});
});
}
I'm trying to get data from a SQLite Database in React-Native (android). The function returns an Array with data.
If I try logging the array to the console, it works fine, but when I log the length of the Array to the console, it suddenly prints "0".
Functions:
getActByButton(category) {
return new Promise((resolve) => {
const activities = [];
const request = 'SELECT activity FROM activity_button WHERE category = ? AND activity IS NOT NULL';
this.initDB().then((db) => {
db.transaction((tx) => {
tx.executeSql(request, [category]).then(([tx, results]) => {
//console.log('Query COMPLETED');
var len = results.rows.length;
for (let i = len; i > 0; i--) {
activities.push(results.rows.item(i - 1));
}
resolve(activities);
})
}).then((result) => {
this.closeDatabase(db);
})
})
})
}
getIconByAct(activity_ids) {
return new Promise((resolve) => {
const icons = [];
const request = 'SELECT name, icon FROM activity WHERE id = ?';
this.initDB().then((db) => {
db.transaction((tx) => {
for (let id in activity_ids) {
tx.executeSql(request, [id]).then(([tx, results]) => {
console.log('Query COMPLETED');
var len = results.rows.length;
let icon = results.rows.item(0).icon;
let description = results.rows.item(0).name;
icons.push({ icon, description });
})
}
resolve(icons);
}).then((result) => {
this.closeDatabase(db);
})
})
})
}
Function-call:
db.getActByButton(this.props.category).then((data) => {
result_ids = data;
}).then(() => {
db.getIconByAct(result_ids).then((data) => {
console.log('DATA:');
console.log(data);
console.log(data.length);
console.log(data[1]);
})
})
Console:
How is this possible?
And how can I fix it?
Try this:
db.getActByButton(this.props.category).then((data) => {
result_ids = data;
}).then(() => {
db.getIconByAct(result_ids).then((data) => {
console.log(JSON.stringify(data));
console.log(JSON.stringify(data.length));
})
})
Actually i have 15 http requests which are send to the API.
All i want to do is to handle responses one by one whithout waiting the end of all the requests (I have a request which can take some minutes to send result).
Service side :
findOneByOne(): Observable<any> {
const calls = this.getCardsPath().map(el => this.getPromises(el));
return Observable.forkJoin(calls)
.map(res => {
const tab = [];
for (let i = 0; i < res.length; i++) {
tab.push(this.checkInfoService(res[i].json()));
}
return tab;
});
}
getPromises(str: String): Promise<any> {
return this.requester.obtain({
restUrl: "/administration/" + str,
method: RequestMethod.Get
})
.toPromise()
.then(res => res)
.catch(err => err);
}
Component side :
displayDashboardInfoService() {
if (this.featuresFlag.getCurrentVersion() !== "1.08" && this.featuresFlag.getCurrentVersion() !== "-1") {
this.busy = this.dashboardInfoService.findAll()
.then((res: DashboardInfo[]) => this.findPaths(res))
.then((res: DashboardInfo[]) => this.loadItems(res))
.catch((err: any) => {
if (environment.debugLevel >= 3) console.error(err);
});
}
else {
this.dashboardInfoService.findOneByOne()
.subscribe((res) => {
const tab = [];
for (let i = 0; i < res.length; i++) {
tab.push(res[i][0]);
}
this.findPaths(tab);
this.loadItems(tab);
});
}
}
Thanks :)
A solution would be to change the forkJoin to merge so that instead of getting one event when all the requests are done you get an event after each one of them finishes.
If you'd have for example this:
waitForAll() {
this.values = [];
this.loadAllAtOnce([100, 200, 300, 400, 3000])
.subscribe(values => {
this.values = values;
});
}
loadAllAtOnce(values: number[]) {
return forkJoin(
values.map(x => of (x).pipe(delay(x)))
).pipe(
tap(values => {
console.log(values);
})
);
}
It could be rewritten to this:
asTheyCome() {
this.values = [];
this.loadAsSoonAsAvailable([100, 200, 300, 400, 3000])
.subscribe(value => {
this.values.push(value);
});
}
loadAsSoonAsAvailable(values: number[]) {
return merge(
...values.map(x => of (x).pipe(delay(x)))
).pipe(
tap(value => console.log(value))
);
}
You can find a working example here.