nested fetch method inside then - javascript

I am using fetch API to get a url of a json file that contains array of objects. I got the values of the objects inserted to the HTML file as I needed.However, I am trying to fetch another url form the json file. each objects has key 'contributors' with a url value that is a json file. could it be possible that I access it with another nested fetch method inside then method?
'use strict';
{
const root = document.getElementById('root');
const select = document.createElement('select');
root.appendChild(select);
const url = 'https://api.github.com/orgs/HackYourFuture/repos?per_page=100';
fetch(url)
.then(resp => resp.json())
.then(data => {
select.innerHTML = data.sort()
.map(repo => `<option value="${repo.id}">${repo.name}</option>`).join("\n");
select.addEventListener('change', function () {
const chosenRepoId = +this.value;
const currentRepo = data.find(repo => repo.id === chosenRepoId);
document.getElementById('repoInfo').innerHTML = "Repositroy Name: " + currentRepo.name + "<br />" + "Description: " + currentRepo.description + "<br />" + "Forks: " + currentRepo.forks + "<br />" + "Update date: " + currentRepo.updated_at;
// contributors section code
const cntrbutorsUrl = currentRepo.contributors_url;
// fetch the contributors json file
fetch(cntrbutorsUrl)
.then(resp => resp.json)
.then(data => console.log(data));
});
});
// trying to render contributors_url and get its ifno to fill the contribuers square.
}
````js

We can make use of async/await,
(async function() {
var firstcallresult = await fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json());
var secondcallresult = await fetch('https://jsonplaceholder.typicode.com/todos/' + firstcallresult[1].id)
.then(response => response.json());
console.log(secondcallresult);
})();

Related

Access to body response on Cache with Javascript

I'm working on a node.js and i want to store some data on cache.
The request is stored successfully.
const version = "3.4.3";
caches.open('v' + version)
.then(async cache => {
await cache.add('/getTranslations');
const data = await cache.match('/getTranslations');
});
Here is what endpoint returns:
app.get("/getTranslations", (req, res) => {
res.status(200).send(/*Here is the object shown
on the next picture and what i want to read from cache*/);
});
I have this on cache on chrome:
I'm trying to access to what is show on the preview but I'm not able to get it.
Using
// In my case the name of the cache is 'v4'
const c = await caches.open('v4');
// Here finds the 7 requests.
await c.keys()
// Here i get the request but i can't not get the data I'm looking for.
await c.match('/getTranslations')
Here i found the solution myself with javascript:
Using as an example what is shown on the question request would be '/getTranslations' and version would be '4'.
cachePetition: async function (version: string, request: string) {
let body = -1;
return caches.open('v' + version).then(async cache => {
// Function to get the cached data
const writeRequestToCache = async () => {
console.log("[Cache] Getting '" + request + "' ...");
await cache.add(request);
const result = await cache.match(request);
return result.json();
}
let data = await cache.match(request);
// If there is no data in the cache, get it from the server
if (data == undefined) {
console.log("[Cache] '" + request + "' not found.");
body = await writeRequestToCache();
} else {
body = await data.json();
// If you can't access to the body of the petition ask it again. This is a workaround for the cache bug.
if (body == undefined) {
console.log("[Cache] '" + request + "' data corrupted. Getting it again...");
body = await writeRequestToCache();
} else {
console.log("[Cache] '" + request + "' found.");
}
}
return body;
});
}

Subcategories aren't populating using fetch

I'm trying to create <optgroup>s that have subcategories pulled from an API. Currently, only the <optgroup>s are populating, but not the subcategories.
let select = document.querySelector("#category");
const categoriesURL = "INSERT_URL"
let str = "";
fetch(categoriesURL)
.then(response => response.json())
.then(data => {
data.forEach(category => {
let categoryTitle = category.title;
str += `<optgroup label=${categoryTitle}></optgroup>`
category.subcategories.forEach(sub => {
let subcategories_id = sub.subcategories_id;
let subcategoriesURL = `INSERT_URL/${subcategories_id}`;
fetch(subcategoriesURL)
.then(response => response.json())
.then(subData => {
str += `<option value=${sub.subcategories_id}>` + subData.title + "</option>";
})
})
})
select.innerHTML = "<option disabled selected>Select a category</option>" + str;
});
When defining your optgroup in the string, you immediately open and close the optgroup, effectively putting every option after the optgroup.
str += `<optgroup label=${categoryTitle}></optgroup>`
Leave the optgroup open in the aforementioned string.
However, the hard part is closing the string after all the fetch requests on the subcategories are complete. We can accomplish this with Promise.all and Array#map.
Loop over all the subcategories with map and return Promise that fetch returns in the loop. When all the fetch requests are done the code will continue to the then block that follows the Promise.all function. In that block you will have the ids and titles of all your subcategories collected in an array. Loop over array to append to your string.
After the loop, close the optgroup element.
fetch(categoriesURL)
.then(response => response.json())
.then(data => {
data.forEach(category => {
let categoryTitle = category.title;
str += `<optgroup label=${categoryTitle}>`;
Promise.all(category.subcategories.map(sub => {
let subcategories_id = sub.subcategories_id;
let subcategoriesURL = `INSERT_URL/${subcategories_id}`;
return fetch(subcategoriesURL)
.then(response => response.json())
.then(({ title }) => ({
title,
id: subcategories_id
}))
})).then(subData => {
subData.forEach(({ id, title }) => {
str += `<option value=${id}>${title}</option>`;
})
str += '</optgroup>';
});
})
select.innerHTML = "<option disabled selected>Select a category</option>" + str;
});
Though, overall this a very expensive script as it will create a lot of requests. If you have any say over the backend, then I'd advice that you send the all the data back in a single request and create your list based on that result.

How to make a chained fetch and show results as each step becomes available?

I'm fetching article list data from API and use/fetch Unsplash API to get relative images according to each fetched article title.
This is my code:
let url = 'http://127.0.0.1:8000/api';
async function getData(url) {
const res = await fetch(url);
const objects = await res.json();
await Promise.all(objects.map(async (object) => {
const res = await fetch('https://api.unsplash.com/search/photos?client_id=XXX&content_filter=high&per_page=1&query=' + object.title);
const image = await res.json();
object.image_url = image.results[0].urls.small
object.image_alt = image.results[0].alt_description
}));
}
let articles_1 = getData(url + '/articles/index/1/');
let articles_2 = getData(url + '/articles/index/2/');
let articles_3 = getData(url + '/articles/index/3/');
I am showing three different categories at once on the same page. That's why I call that function three times.
Question:
When this function kicks, results are shown after both article data and images are fetched. But I want to show article data first when it's been fetched and then images when they get fetched in order to shorten the user waiting time. How can I achieve it wether with Svelte reactive declaration or plain Javascript?
You would want to seperate the two functions, so they can be called in sequence.
const endpoint = 'http://127.0.0.1:8000/api';
const getArticles = async (url) => {
return fetch(url).res.json();
};
const renderArticles = async (articles) => {
// render article set and return it as a DOM object
return articlesDOM;
};
const getImageForArticle = async (articleNode) => {
const res = await = fetch('https://api.unsplash.com/search/photos?client_id=XXX&content_filter=high&per_page=1&query=' + object.title);
const img = new Image();
img.src = res.results[0].urls.small;
img.alt = res.results[0].alt_description;
return {img, articleNode};
};
const renderImage = async (stuff) => {
const {img, articleNode} = stuff;
// inject your img into your article
};
// now call in sequence
getArticles(endpoint+'/articles/index/1/').then(renderArticles).then(articleNodes => {
const promises = articleNodes.map(articleNode => {
return getImageForArticle(articleNode).then(renderImage);
});
return Promise.all(promises);
});
While I'm not completely sure what you're trying to do (I don't have a minimal working example), here's my best attempt at it:
var url = 'http://127.0.0.1:8000/api';
async function getData(url) {
var data = fetch(url)
.then(data => data.json())
await data.then(data => ArticleFunc(data))
await data.then(function(data) {
data.map(function(object) {
fetch('https://api.unsplash.com/search/photos?client_id=XXX&content_filter=high&per_page=1&query=' + object.title)
.then(data => data.json())
object.image_url = image.results[0].urls.small
object.image_alt = image.results[0].alt_description
ImageFunc(object)
})
})
}
function ArticleFunc(data){
//display article
}
function ImageFunc(data){
//display image
}
getData(url + '/articles/index/1/');
getData(url + '/articles/index/2/');
getData(url + '/articles/index/3/');
Note that this is to be treated as pseudocode, as again, it is untested due to the absense of a minimal working example.

Consuming APIs - How to connect to an API

I have most of my code written but I'm not sure what I'm doing wrong on this:
let url = 'https://cors-anywhere.herokuapp.com/https://newsapi.org/v2/top-headlines?sources=hacker-news&apiKey=3dcfcd098261443dae7c7d002f25c062';
fetch(url)
.then(r =>{
return r.json();
})
.then(data => {
let articles = data.articles;
let storyList = document.createElement("ul");
let body = document.querySelector("body");
body.appendChild(storyList);
})
articles.map(articles => {
let storyItem = document.createElement("li");
storyItem.innerHTML = 'a href = "' + articles.href + '">' + articles.title + "</a>";
storyList.appendChild(storyItem);
})
.catch(e => {
console.log('An error has occurred: ${e}');
});
I had taken out the < > from the API code and tried switching things around like switching some properties to say something different but could someone help me understand this a bit better? Thanks in advance!
There were several things that you were doing wrong.
No need to use a proxy when the API you are consuming allows cors requests.
You were trying to access the "articles" out of scope and before the promise was resolved
You were using the wrong method, IMO, on the "articles" array. From here: Array.prototype.map()
The map() method creates a new array with the results of calling a provided function on every element in the calling array.
but you were not trying create a new array, you just wanted to iterate the array's elements. That is what Array.prototype.forEach() is for.
You used single quotes ' on your template literal instead of back-ticks `
let url = 'https://newsapi.org/v2/top-headlines?sources=hacker-news&apiKey=3dcfcd098261443dae7c7d002f25c062';
fetch(url)
.then(response => {
return response.json();
})
.then(data => {
let list = document.createElement('ul');
document.querySelector("body").appendChild(list);
data.articles.forEach(article => {
let item = document.createElement('li');
item.innerHTML = '' + article.title + "";
list.appendChild(item);
});
})
.catch(e => {
console.log(`An error has occurred: ${e}`);
});

Web scraper iterating over pages with Rx.js

About a month ago I built this web scraper using Async / Await as a async way of collecting info for a web scraper. I'm trying to build that very same scraper again using Rx.js. I've read through the docs and it seems to make sense, starting off is the hardest bit, but after that hump I made some progress.
You can see here that I get the first page on the site (page 0) and I need to use that page to get the count of pages (which is around 6000). I have that count and using the getPageURI(page) I can create each page URL, however my issue is that I can't figure out how to trigger, or fire, or pipe information back to the original pageRequestStream. I have this page count number and I need a way to iterate over it pushing data back to the first original pageRequestStream stream.
import cheerio from 'cheerio'
import Rx from 'rx'
import fetch from 'isomorphic-fetch'
const DIGITAL_NYC_URI = 'http://www.digital.nyc'
let getPageURI = (page) => `${DIGITAL_NYC_URI}/startups?page=${page}`
let getProfileURI = (profile) => `${DIGITAL_NYC_URI}${profile}`
function fetchURL(stream, dataType = 'json') {
return stream.flatMap(requestURL => {
return Rx.Observable.fromPromise(fetch(requestURL).then(res => res[dataType]()))
})
}
function getNumberOfPages($) {
let summary = $('.result-summary').text()
let match = summary.match(/Showing 1 - 20 of (\d+) Startups/)
return parseInt(match[1], 10)
}
function getCompaniesOnPage ($) {
let companySelector = 'h3.node-title a'
let companies = $(companySelector).map(function (i, el) {
let name = $(this).text()
let profile = $(this).attr('href')
return {
'name': name,
'profile': profile
}
}).get()
return companies
}
let pageRequestStream = Rx.Observable.just(getPageURI(0))
let pageResponseStream = fetchURL(pageRequestStream, 'text')
let parsedPageHTMLStream = pageResponseStream.map(html => cheerio.load(html))
let numberOfPagesStream = parsedPageHTMLStream.map(html => getNumberOfPages(html))
// not sure how to get this to iterate over count and fire url's into pageRequestStream
numberOfPagesStream.subscribe(pageCount => console.log(pageCount))
let companiesOnPageStream = parsedPageHTMLStream.flatMap(html => getCompaniesOnPage(html))
// not sure how to build up the company object to include async value company.profileHTML
companiesOnPageStream.subscribe(companies => console.log(companies))
// let companyProfileStream = companiesOnPageStream.map((company) => {
// return fetch(getProfileURI(company.profile))
// .then(res => res.html())
// .then(html => {
// company.profileHTML = html
// return company
// })
// })
Have a look at subjects, they allow you to fire events as you go.
Maybe this can serve as some inspiration
import cheerio from 'cheerio';
import Rx from 'rx';
import fetch from 'isomorphic-fetch';
function getCheerio(url) {
var promise = fetch(url)
.then(response => response.text())
.then(body => cheerio.load(body));
return Rx.Observable.fromPromise(promise);
}
const DIGITAL_NYC_URI = 'http://www.digital.nyc';
var pageRequest = new Rx.Subject();
pageRequest
.flatMap(pageUrl => getCheerio(pageUrl))
.flatMap(page$ => {
// here we pipe back urls into our original observable.
var nextPageUrl = page$('ul.pagination li.arrow a').attr('href');
if(nextPageUrl) pageRequest.onNext(DIGITAL_NYC_URI + '/' + nextPageUrl);
var profileUrls = page$('h3.node-title a')
.map(function() {
var url = page$(this).attr('href');
return DIGITAL_NYC_URI + '/' + url;
});
return Rx.Observable.from(profileUrls);
})
.flatMap(url => getCheerio(url))
.map(profile$ => {
// build the company profile here
return profile$('title').text();
})
.subscribe(value => console.log('profile ', value));
pageRequest.onNext(DIGITAL_NYC_URI + '/startups');

Categories