How to properly fetch iTunes API? - javascript

Basically, I need to fetch and use promises in order to do this exercise. However, I cannot get the results I want. The goal is to fetch data from the iTunes API and encode the response as JSON, passing it into my helper function.
//What I've tried
const URL_TEMPLATE = "https://itunes.apple.com/search?entity=song&limit=25&term={searchTerm}";
function fetchTrackList(param) {
let url = URL_TEMPLATE.replace('{searchTerm}', param);
let promise = fetch(url);
promise.then(function(res) {
return res.json();
})
.then(function(res) {
renderSearchResults(res);
});
}
//My render methods
function renderTrack(track) {
let newDiv = $('<img>');
newDiv.attr({src: track.artworkUrl100, alt: track.trackName, title: track.trackName});
$('#records').append(newDiv);
}
function renderSearchResults(obj) {
$('#records').empty();
obj.results.forEach(result => {
renderTrack(result);
})
}
//Now it's the time to practice using `fetch()`! First, modify the `index.html`
//file to load the polyfills for _BOTH_ the fetch() function and Promises, so
//that your example will work on Internet Explorer.
//Use version 2.0.4 of the `fetch` polyfill.
//Define a function `fetchTrackList()` that takes in a "search term" string as a
//parameter and uses the `fetch()` function to downloads a list of tracks from
//the iTunes Search API. You can use the below `URL_TEMPLATE` string for the URL,
//replacing `"{searchTerm}"` with the passed in search term (no {})
//Send the AJAX request, _then_ encode the response as JSON once it is received,
//and _then_ should call you `renderSearchResults() function and pass it the
//encoded data.
//
//IMPORTANT: Your `fetchTrackList()` method must also _return_ the Promise
//returned by the end of the `.then()` chain! This is so the method itself will
//be asynchronous, and can be further chained and utilized (e.g., by the tester).
//
//You can test this function by calling the method and passing it the name of
//your favorite band (you CANNOT test it with the search button yet!)
const URL_TEMPLATE = "https://itunes.apple.com/search?entity=song&limit=25&term={searchTerm}";
I expect tracks to actually render, however; nothing renders. I can only assume I've done something wrong, but do not know where to go about it.

Related

Best way to return full object made from data from different APIs

Im working on an app that mainly is a large form that contains several dropdowns. These dropdowns should
populate with some data from the DB. The app has a server with Express that it works as a proxy: I call it from the front end
and then that server calls some external API's, format the info and then send them back to the front.
Im thinking on having on the Backend a route like "/dropdown" call that route from the front end. And once I get the data (I would like to receive it like this)
data: {
dataForDropdown1:[data],
dataForDropdown2:[data],
dataForDropdown3:[data],
dataForDropdown4:[data],
...etc
}
store it on Redux and call them with a selector and populate all the dropdowns required.
The problem is on the server. I need to get that info from different APIs so in the route in Express I was thinking on use something like
Promise.all([promisefetchUrl1],[promisefetchUrl2],[promisefetchUrl3],...etc)
but I don't know how to format like the object above, because as I understand Promise.all doesn't have an order it just returns the info with the first promise resolved, then the second, etc so it would be hard to know
which info is which.
I was thinking on using async await but its way too dirty, like this:
const info1 = await fetch(url1)
const info2 = await fetch(url2)
const info3 = await fetch(url3)
const info4 = await fetch(url4)
etc
then return it like this
const data = {
dataForDropdown1:info1.data,
dataForDropdown2:info2.data,
dataForDropdown3:info3.data,
dataForDropdown4:info4.data
...etc
}
any advices?
Promise.all doesn't have an order it just returns the info with the first promise resolved, then the second, etc so it would be hard to know which info is which.
It does have an order - the first element in the resolve array will correspond to the first Promise in the array passed to Promise.all, etc, regardless of the order in which the promises resolve.
Use:
const results = await Promise.all([
getData(promisefetchUrl1),
getData(promisefetchUrl2),
getData(promisefetchUrl3),
getData(promisefetchUrl4), // this could be made less repetitive
]);
const data = {
dataForDropdown1: results[0],
dataForDropdown2: results[1],
dataForDropdown3: results[2],
dataForDropdown4: results[3],
};
where getData takes the URL and returns a Promise that resolves to the data corresponding to the URL.

Zomato-API behaving weirdly different api calls

const zomato = require("zomato-api");
const client = zomato({
userKey: "my-user-key",
});
// fetching one data and chaining another fetch
client.getGeocode({lat: lat-value, lon: lon-value}).then(result => {
console.log(result);
// now using the data fetched we get the restaurant details
return client.getRestaurant({res_id: result.popularity.nearby_res[0]})
}).then(res_data => {
console.log(res_data);
});
So, the idea was to extract the nearby_res array from the geocode API's result and then use one of the elements (each element is an id) as the parameter for the next API call.
client.getRestaurant({res_id: result.popularity.nearby_res[0]})
Now, this should originally return all the details about the restaurant with the id specified.
When invoked independently with an id, the getRestaurant() function returns the result in the expected format. But when it is chained like this, the result is basically the same as the result of the first API call ( geocode API).
Am I doing anything wrong here? or is the Zomato API reluctant to do this kind of chaining.
Please help.

Parse Server Upset Data in loop

I'm trying to query an API to get some data then I want to upsert all of it into my table.
But for some reason I'm not having any luck.
What's the best way to go about this?
I don't think my method of doing a query in a loop is best.
var coin = new Parse.Object.extend("Coins");
axios.get('https://api.coinmarketcap.com/v1/ticker/')
.then(response => {
let data = response.data;
// Put into database
data.map(entry => {
let q = new Parse.Query(Model.Coins.className());
q.equalTo('symbol', entry.symbol);
q.first()
.then(record => {
record.set('symbol', entry.symbol);
record.set('price_usd', entry.price_usd);
return record.save(null, {useMasterKey: true});
});
});
res.success(response);
});
you should avoid fetching and updating objects in a loop. In order to make it better you need to use 2 things:
In your query, instead of using equalTo and first you need to use containedIn for query all the records in one call. Then you need to iterate on the query results, for each record in the loop you need to do the following:
record.set('symbol', entry.symbol);
record.set('price_usd', entry.price_usd);
Finally you need to use saveAll to save all the objects in one call (please notice that saveAll is static function of Parse.Object and you should pass an array into it. Please review the docs before doing it)
Check your data. You may find you have unexpectedly updated records.
Assuming that's the entire body of a cloud function, your function initiates an asynchronous server call, then immediately tells the requestor that the operation was successful, but response isn't populated yet so you pass back undefined. However, parse-server will still run that asynchronous code.
The solution is to put the res.success call inside another .then chain, so the function won't return a response until after the server call + save finishes.
You're also going to get an uncaught error if the symbol doesn't exist on your table, though. You don't check to make sure the query returned a response to the first() call.

React-Redux Javascript app - trying to get lots of API calls to fire in order

I'm having an issue with successive API calls (using JQuery's AJAX) to two different APIs in order to build objects with certain attributes. Here's the summary of my app and what I'm trying to do:
The user enters in the name of an actor or director, and the app is meant to return a total of five movies, each of which has certain attributes like title, overview, year, budget, revenue, and a link to a YouTube preview. I'm using The Movie Database API, plus the YouTube API for the YouTube link.
Here's the order of how things currently to work, with all of this happening in the action creator of the Redux app:
Actor name gets sent to the TMDB API -- returns ActorID number
ActorID number gets sent to the TMDB API -- returns 20 movies with: title, overview, year, poster link, and MovieID number
For EACH movie in that list, the MovieID number gets sent to the API -- returns more attributes: budget, revenue, and IMDB-ID (to use in a link later)
Also for EACH movie in step 2, the title gets sent to the YouTube API -- returns a link to the preview.
Once all of this information is assembled for each movie, I want to return the first five movies and dispatch them as the action payload to the Redux store.
I'm using some promises, and I've tried everything I could think of in terms of rearranging the flow of functions, but I can't get all the information I need with one click of the submit button. The funny thing is, it works with TWO clicks of the submit button, I think because by then all the async AJAX calls are finally done. But after the first click, I have an empty array where the movie objects should be.
Here's some code that should summarize what things look like:
var personId
var movies = []
function actorByRating(UserInput) {
Step 1: get actor ID number:
function searchActors() {
return $.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/search/person?query=${UserInput}&api_key=<key>`
}).done(function(response){
personId = response.results[0].id
})
}
Step 2: Use Actor ID to get list of movies, start assigning them attributes:
function getMovies() {
$.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/discover/movie?with_cast=${personId}&vote_count.gte=20&sort_by=vote_average.asc&budget.desc&api_key=<key>&include_image_language=en`
}).done(function(response) {
response.results.forEach((m) => {
var movie = {}
movie.title = m.title
movie.year = m.release_date.split("-")[0]
movie.movieId = m.id
movie.overview = m.overview
movie.poster = "http://image.tmdb.org/t/p/w500" + m.poster_path
getMovieInfo(movie) //step 3
getYouTube(movie) //step 4
saveMovie(movie)
})
})
}
function saveMovie(movie){
movies.push(movie)
}
Step 3 function, takes in a movie object as an argument:
function getMovieInfo(m){
return $.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/movie/${m.movieId}?&api_key=<key>&append_to_results=imdb_id`
}).done(function(response) {
m.revenue = response.revenue
m.budget = response.budget
m.imdbId = response.imdb_id
})
}
Step 4 function, to get Youtube link. Also takes a movie object:
function getYouTube(movie){
$.ajax({
method: "GET",
url: `https://www.googleapis.com/youtube/v3/search?part=snippet&q=${movie.title.split(" ").join("+")}+trailer&key=<key>`
}).done(function(yt){
movie.youtubeLink = `http://www.youtube.com/embed/${yt.items[0].id.videoId}`
})
}
After this, the filtering functions work fine, when they have an array of movies to work with. The problem is, I think, all these successive API calls keep firing before the previous ones are done, and the latter ones need info from the earlier ones to search with. Thus, when I click submit the first time, the final movies array is empty, so the dispatched payload is an empty array. THEN the movie objects get filled in, so when you click submit again, the movies are already there to work with, and the rest of the app works fine.
I've tried everything I can think of to slow the process down, chain promises together (which doesn't work because Step 2 has to run for several movies, i.e. the return values of each function keep changing, so I can't ".then" them), reorganizing the information that comes in...but I can't get it to give me movie objects with all the attributes I need by the time the filtering functions actually run to create the proper payload.
Any help or suggestions would be greatly appreciated!
(Note: the "key" stuff above is just placeholder text)
UPDATE:
I changed the code to basically the following:
searchActors()
.then(function(response){
const actorId = response.data.results[0].id
return actorId
})
.then((personID) => {
return getMoviesFromPersonID(personID)
})
.then(function(response) {
const movieList = []
response.data.results.forEach((m) => {
var movie = {}
movie.title = m.title
movie.year = m.release_date.split("-")[0]
movie.movieId = m.id
movie.overview = m.overview
movie.poster = "http://image.tmdb.org/t/p/w500" + m.poster_path
movieList.push(movie)
// saveMovie(movie)
})
return Promise.all(movieList)
})
.then((movieList) => {
const deepMovieList = []
movieList.forEach((movie) => {
getMovieInfo(movie)
.then(function(response) {
movie.revenue = response.data.revenue
movie.budget = response.data.budget
movie.imdbId = response.data.imdb_id
deepMovieList.push(movie)
})
})
return Promise.all(deepMovieList)
})
.then((deepMovieList) => {
const finalMovies = []
deepMovieList.forEach((movie) => {
finalMovies.push(getYouTube(movie))
})
return Promise.all(finalMovies)
})
Everything works fine right up until the first mention of "deepMovieList". I can't seem to figure out how to have that particular step to work properly, as it essentially involves making 20 API calls with each movie in the movieList. I can't figure out how to 1) get the info back from the API, 2) assign the attributes to the movie object that is passed in to getMovieInfo, and then 3) push that movie object (with the new attributes) to an array that I can use Promise.all on, all without interrupting the promise chain.
Either it moves on to the next "then" function too early (while deepMovieList is still an empty array), or, with other random stuff I've tried, the array ends up being undefined.
How can I have the next "then" function wait until 20 API calls have been made and each movie object has its updated attributes? This will also run into the same problem in the next step, for the YouTube link.
Thanks!
TL;DR: use fetch and promises instead of jQuery, group promises using Promise.all.
The Longer Version
Ok, I'm not going to repeat all your code here. I'm going to abbreviate some stuff to keep it simple.
Basically, you have a bunch of tasks to perform. I'm going to pretend each of them is a function that returns a promise which is resolved with the data you want.
searchActor() - returns a promise resolved with some ID number
getMoviesFromActorID(actorId) - returns a promise that resolves with an array of movie IDs
getMovie(movieId) - returns a promise that resolves with the details for the given movie ID
getYoutube(movie) - returns a promise that resolves with the Youtube embed code.
Given this basic setup (and I admit I'm leaving out a lot of stuff), the code looks like this:
// search for an actor
searchActor('Brad Pitt')
// then get the movie IDs for that actor
.then((actorId) => getMoviesFromActorID(actorId))
// then iterate over the list of movie IDs & build an array of
// promises. Use Promise.all to create a new Promise which is
// resolved when all are resolved
.then((movieIdList) => {
const promiseList = [];
movieIdList.forEach((id) => promiseList.push(getMovie(id)));
return Promise.all(promiseList);
})
// then get Youtube links for each of the movies
.then((movieDetailsList) => {
const youtubeList = [];
movieDetailsList.forEach((movie) => youtubeList.push(getYoutube(movie)));
return Promise.all(youtubeList);
})
// then do something with all the information you've collected
.then((finalResults) => {
// do something interesting...
});
The key to this is Promise.all (documentation here), which will take an array of Promises (or any other iterable object containing promises) and create a new Promise which will resolve when all of the original promises have resolved. By using Promise.all, you can create a step in your promise chain which can include a variable number of parallel actions which must complete before the next step.
You could do something like this will jQuery and callbacks, but it would be pretty darn ugly. One of the great benefits of promises is the ability to lay out a series of steps like above.

NodeJS: How to handle a variable number of callbacks run in parallel and map their responses to requests?

As an exercise to teach myself more about node js I started making a basic CRUD REST server for SimpleDB (sdb) using the aws-sdk.
Everything was running smoothly until I got to a function for reading the domains. The aws-sdk has two functions for this purpose: listDomains and domainMetadata. listDomains returns an array of sdb domain names. domainMetadata will return additional statistics about a domain, but will only return them for one domain at a time. It does not include the domain name in the results.
My script is running listDomains and returning an array in the JSON response just fine. I would like to make my api readDomains function more ambitious though and have it return the metadata for all of the domains in the same single api call. After all, running a handful of domainMetadata calls at the same time is where node's async io should shine.
The problem is I can't figure out how to run a variable number of calls, use the same callback for all of them, match the results of each domainMetadata call to it's domainName (since it's async and they're not guaranteed to return in the order they were requested) and tell when all of the metadata requests have finished so that I can send my final response. Put into code my problem areas are:
domain.receiveDomainList = function(err, data){
var domainList = [];
for(var i=0; i<data.DomainNames.length; i++){
sdb.domainMetaData({"DomainName":data.DomainNames[i]},domain.receiveMetadata);
// alternatively: domainList.push({"DomainName":data.DomainNames[i]});
}
// alternatively:
// async.map(domainList, sdb.domainMetadata, domain.receiveMetadata)
console.log(domainList);
}
domain.receiveMetadata = function (err, data){
// I figure I can stash the results one at a time in an array in the
// parent scope but...
// How can I tell when all of the results have been received?
// Since the domainname used for the original call is not returned with
// the results how do I tell what result matches what request?
}
Based on my reading of async's readme the map function should at least match the metadata responses with the requests through some black magic, but it causes node to bomb out in the aws sync library with an error of " has no method 'makeRequest'".
Is there any way to have it all: requests run in parallel, requests matched with responses and knowing when I've received everything?
Using .bind() you can set the context or this values as well as provide leading default arguments to the bound function.
The sample code below is purely to show how you might use .bind() to add additional context to your response callbacks.
In the code below, .bind is used to:
set a domainResults object as the context for the receiveMetaData callback
pass the current domain name as an argument to the callback
The domainResults object is used to:
keep track of the number of names received in the first request
keep track of the completedCount (incremented on each callback from the metaData request)
keep track of both error and success responses in list
provide a complete callback
Completely untested code for illustrative purposes only:
domain.receiveDomainList = function(err, data) {
// Assuming err is falsey
var domainResults = {
nameCount: data.DomainNames.length,
completeCount: 0,
list: {},
complete: function() {
console.log(this.list);
}
};
for (var i = 0; i < data.DomainNames.length; i++) {
sdb.domainMetaData({ "DomainName": data.DomainNames[i] },
domain.receiveMetadata.bind(domainResults, data.DomainNames[i]));
}
}
domain.receiveMetadata = function(domainName, err, data) {
// Because of .bind, this === domainResults
this.completeCount++;
this.list[domainName] = data || {error: err};
if(this.completeCount === this.nameCount) {
this.complete();
}
}

Categories

Resources