Retrieve variable used in request from response - javascript

I'm working on a function that make a certain amount of requests based on many ids. And then it picks the id of the resulting elements.
I would like to kind of propagate the id i used for the request in the response. Something like this: (illustrative only)
const fetchSomeIds = async (ids, Service) => {
const requests = ids.map(id => { return { url: `/something/` + id + `/something-else`}});
const responses = await Promise.all(requests.map(
request => Service.get(request.url)
));
const someIds = responses.map(element => {return {id: id/*(used in request)*/, some_id: element.data.some_id};});
return someIds;
};
So if i use ids=[27,54] and i get some_id=133 and some_id=32. the response should look like this:
[
{
"id": 27,
"some_id": 133
},
{
"id": 54,
"some_id": 32
}
]

Since Promise.all preserves the order, you can access the original array at the same index:
const someIds = responses.map((element, index) => {
return {id: ids[index], some_id: element.data.some_id};
});
However, it might be simpler to just put the processing all in a single map callback:
function fetchSomeIds(ids, Service) {
return Promise.all(ids.map(async id => {
const request = {url: `/something/` + id + `/something-else`};
const response = await Service.get(request.url);
const element = response;
return {id, some_id: element.data.some_id};
}));
}

Related

Loop through an array list to make a post request for a rest API dynamic

I update an object. This object is an array which can have a length from one to five. This is my object ["max", "dog"].
Now a post method is to be called. It is so if the user has only 2 things filled in, only two things should be sent there. If 5 then 5 (see example for a better explanation.) Does anyone have an idea how best to set this up?
const tag = ["max", "dog"]
const updateInterest = () => {
axios
.post("...", {
first1: max,
first2: dog,
// first3: ...,
// first4: ...,
// first4: ...,
})
.then((res) => {
if (res.status === 200) {
// API update interest
}
})
.catch((error) => {
console.log(error);
});
};
What I try
const tag = ["max", "dog"]
const updateInterest = () => {
const object = "";
tags.map((tag, index) =>{
console.log(tag + " " + index)
object =
`first${index}`: `${tag}`,
})
axios
.post("...", {
object
})
.then((res) => {
if (res.status === 200) {
// API update interest
}
})
.catch((error) => {
console.log(error);
});
};
My loop doesn't really make much sense. How can I add this to an object so that it can be sent in later in an API?
You can use Object.fromEntries() to map an array of arrays to object, like in the following example:
const arr = ["max", "dog"];
const mapped = arr.map((el, i) => [`first${i+1}`, el]);
console.log(Object.fromEntries(mapped))
You can also use Array.prototype.reduce() to achieve the same thing:
const arr = ['cat', 'dog'];
const obj = arr.reduce((acc, cur, i) => ((acc[`first${i + 1}`] = cur), acc), {});
console.log(obj);
Reference: Convert Array to Object

Merging various backend requests in the express res.send()

I'm trying to make several asynchronous backend calls to generate a JSON response in my express API. Because of the nature of the API, I have 3 requests that are being made that are dependent on each other in some way.
Request 1: Returns an Array of values that are used to make request 2. Each value will be used as a mapping for the remaining requests. That is to say, it will be a unique identifier used to map the response from the requests in Request 3.
Request 2 (Parallel Batch): A request is made using each value from the Array returned in request 1. Each of these returns a value to be used in each of the Request 3s. That is to say, it's a 1-to-1
Request 3 (Parallel Batch): This request takes the response from Request 2, and makes a 1-to-1 follow up request to get more data on that specific mapping (the id from request 1)
I would like the final data I send to the consumer to look like this:
{
id1: details1,
id2: details2,
id3: details3,
...
}
Here is the code I have so far...
app.get("/artists/:artist/albums", (req, res) => {
console.log("#############")
const artistName = req.params.artist
let response = {};
let s3Promise = s3.listAlbums(artistName)
let albumDetailsPromises = []
s3Promise
.then((data) => {
data.map((album) => {
// Each album name here will actually be used as the unique identifier for
// the final response
// Build an Array of promises that will first fetch the albumId, then use
// that album id to fetch the details on the album
albumDetailsPromises.push(
discogs.getAlbumId(artistName, album).then( // Returns a promise
({ data }) => {
let masterId = data.results[0].id
let recordName = data.results[0].title
// Storing the album name to carry as a unique id alongside the promise
return [album, discogs.getAlbumDetails(masterId) // Returns a promise ]
}
)
)
})
})
.then(() => {
// When all the albumIds have been fetched, there will still exist a promise in the
// second index of each element in the albumDetailsPromises array
Promise.all(albumDetailsPromises)
.then((namedPromises) => {
namedPromises.map(
(album) => {
let albumName = album[0] // Unique Id
let albumDetailPromise = album[1]
// Resolving the albumDetailsPromise here, and storing the value on
// a response object that we intend to send as the express response
albumDetailPromise
.then(
({ data }) => {
response[albumName] = data
})
.catch(err => response[albumName] = err)
})
})
})
.catch((err) => console.log(err))
})
As of now, everything seems to be working as expected, I just can't seem to figure out how to "await" the response object being updated at the end of all these Promises. I've omitted res.send(response) from this example because it's not working, but that's of course my desired outcome.
Any advice is appreciated! New to javascript...
I would recommend rewriting this using async/await as it helps to reduce nesting. You can also extract the logic the get the album-details into a separate function, as this also increases the readability of the code. Something like this (this still needs error-handling, but it should give you a start):
app.get("/artists/:artist/albums", async (req, res) => {
const artistName = req.params.artist;
const albumNames = await s3.listAlbums(artistName);
const result = {};
const albumDetailPromises = albumNames.map(albumName => requestAlbumDetails(discogs, artistName, albumName));
const resolvedAlbumDetails = await Promise.all(albumDetailPromises);
// map desired response structure
for(const albumDetail of resolvedAlbumDetails) {
result[albumDetail.albumName] = albumDetail.albumDetails;
}
res.json(result);
});
async function requestAlbumDetails(service, artistName, albumName) {
const albumInfo = await service.getAlbumId(artistName, albumName);
const masterId = albumInfo.results[0].id;
const albumDetails = await service.getAlbumDetails(masterId);
return { albumName, albumDetails };
}
To answer your question how you could do it with your code:
You'd need to wait for all details to be fulfilled using another Promise.all call and then just send the response in the then-handler:
Promise.all(albumDetailsPromises)
.then((namedPromises) => {
const detailsPromises = namedPromises.map(
(album) => {
let albumName = album[0];
let albumDetailPromise = album[1];
return albumDetailPromise
.then(({ data }) => {
response[albumName] = data;
})
.catch(err => response[albumName] = err);
});
return Promise.all(detailsPromises)
.then(() => res.json(response));
})
Refactored using async/await...
app.get("/artists/:artist/albums", async (req, res) => {
const artistName = req.params.artist
let response = {};
let albums = await s3.listAlbums(artistName)
const promises = albums.map(async (album) => {
let result = await discogs.getAlbumId(artistName, album)
try {
let masterId = result.data.results[0].id
let tempRes = await discogs.getAlbumDetails(masterId)
return [album, tempRes.data]
} catch (error) {
return [album, { "msg": error.message }]
}
})
responses = await Promise.all(promises)
responses.map(data => { response[data[0]] = data[1] })
res.send(response)
})

Do axios request n times,where n is the length of previous response

Using axios, I would like to convert addresses to their respective coordinates.
Get list of addresses from an API.
Take the object RESPONSE from number 1 and convert each address of the RESPONSE to
coordinate using Google API
Then I would like to add those coordinate key to each object RESPONSE.
Here's my attempt but it won't work since it's asynchronous.
let array = [];
axios.get('https://www.data.qld.gov.au/api/3/action/datastore_search?resource_id=346d58fc-b7c1-
4c38-bf4d-c9d5fb43ce7b')
.then((response) => {
const records = response.data.result.records;
records.forEach((record) => {
axios.get('https://maps.googleapis.com/maps/api/geocode/json', {
params: {
address: record.address,
key: GOOGLE_KEY,
}
}).then(response => {
record.response.coordinate;
array.push(record);
});
});
I was thinking, is it possible to do the then((response)) n times based on the length of RESPONSE object?
You can leverage Promise.all to achieve the desired outcome.
(async () => {
const {
data: {
result: { records }
}
} = await axios.get(
"https://www.data.qld.gov.au/api/3/action/datastore_search?resource_id=346d58fc-b7c1-4c38-bf4d-c9d5fb43ce7b"
);
const coordinates = await Promise.all(
records.map(async (record) => {
const response = await axios.get(
"https://maps.googleapis.com/maps/api/geocode/json",
{
params: {
address: record.address,
key: GOOGLE_KEY
}
}
);
return response.coordinate;
})
);
})();

Angular 7 adding data to result of http request

I get an array of IDs (assetIDs) and using those IDs I want to ask for data.
For each http request I'm receiving one or more datasets.
I want to add the request ID to each dataset and then return the data.
Getting and returning the data works just fine, but I don't know how to add that assetID to the dataset.
When I do it like in the following code snippet, I only get the first dataset of each ID. (Of course...because of the [0]). But how can I iterate over all datasets?
getData(assetIds: Array<string>): Observable<any> {
const data = assetIds.map(assetId => {
// for each assetId
const path = this.serverUrl + '?' + 'assetid=' + assetId;
return this.httpClient.get(path).pipe(
map((res: any[]) => {
return {
name: res[0].name,
type: res[0].type,
asset: assetId
};
}));
});
// return combined result of each assetId request
return forkJoin(data);
}
I also tried the following, but I don't get any data when doing this:
getData(assetIds: Array<string>): Observable<any> {
const data = assetIds.map(assetId => {
// for each assetId
const path = this.serverUrl + '?' + 'assetid=' + assetId;
return this.httpClient.get(path).pipe(
map((res: any[]) => {
const resultArray = [];
res.forEach(element => {
const row = {
name: res[element].name,
type: res[element].type,
asset: assetId
};
resultArray.push(row);
});
return resultArray;
}));
});
// return combined result of each assetId request
return forkJoin(data);
}
Your second aproach seems fine. I believe the problem is that you are using the rxjs operator forkJoin.
As RXJS docs say, this operator emits value when
When all observables complete, emit the last emitted value from each.
You basically have 2 options, change operator forkJoin to zip
After all observables emit, emit values as an array
Or add the take(1) operator after the map on pipe. Take operator will complete the observable after 1 value is emmited, permitting forkJoin to emit its values
You can use map also on your result array. Something like this:
const data = assetIds.map(assetId => {
// for each assetId
const path = this.serverUrl + '?' + 'assetid=' + assetId;
return this.httpClient.get(path).pipe(
map((res: any[]) => res.map(item => {
return {
name: item.name,
type: item.type,
asset: assetId
}
})));
});
// return combined result of each assetId request
return forkJoin(data);
}
Thanks for your replies. I tried everything but I guess the problem was that I used "element" as index within the array. The following code now works just fine:
return this.httpClient.get(path)
.pipe(
map((datasets: AssetFilesTableItem[]) => {
const result: AssetFilesTableItem[] = [];
let i = 0;
datasets.forEach(element => {
const dataset: AssetFilesTableItem = {
name: datasets[i].name,
type: datasets[i].type,
size: datasets[i].size,
timestamp: datasets[i].timestamp,
created: datasets[i].created,
updated: datasets[i].updated,
asset: assetId
};
result.push(dataset);
i++;
});
return result;
}));

data returned by YouTube API seems immutable?

onSearch = async () => {
const query = qs.stringify({ ...API_QUERY_PARAMS, q: this.state.searchString });
const url = `https://www.googleapis.com/youtube/v3/search?${query}`
const { data } = await axios.get(url);
data.items.forEach(async vid => {
let id = vid.id.videoId; //Individual video ID
const individualQuery = qs.stringify({ ...INDIVIDUAL_API_QUERY_PARAMS, id });
const individualURL = `https://www.googleapis.com/youtube/v3/videos?${individualQuery}`;
const { data } = await axios.get(individualURL);
//data.items[0].statistics does give me the object that I want
vid['statistics'] = data.items[0].statistics
})
this.setState({ videos: data.items });
console.log(this.state.videos);
}
Basically the above onSearch method will call YouTube API and return me a list of videos, in data.items
For each and every video/item, they are lacking of statistics and so I'm firing another call to retrieve the data, the data successfully returned as data.items[0].statistics, I was thinking then to append into individual item as a property.
No exception being thrown, however I don't see the newly created statistics property too. The idea is like below in a very much simpler form.
let items = [
{id: '123', title: 'John'},
{id: '123', title:'sammy'}
]
items.forEach(x=> {
x['statistics'] = { propA: 'A', propB: 'B'};
})
console.log(items);
Putting an async function inside a forEach won't pause the outer thread until all iterations have been completed - you need Promise.all to map each asynchronous iteration to a Promise and wait for each Promise to be resolved before continuing instead:
const query = qs.stringify({ ...API_QUERY_PARAMS, q: this.state.searchString });
const url = `https://www.googleapis.com/youtube/v3/search?${query}`
const { data } = await axios.get(url);
await Promise.all(data.items.map(async (vid) => {
let id = vid.id.videoId; //Individual video ID
const individualQuery = qs.stringify({ ...INDIVIDUAL_API_QUERY_PARAMS, id });
const individualURL = `https://www.googleapis.com/youtube/v3/videos?${individualQuery}`;
const { data } = await axios.get(individualURL);
vid.statistics = data.items[0].statistics
}))
this.setState({ videos: data.items });

Categories