Angular 7 adding data to result of http request - javascript

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;
}));

Related

Retrieve variable used in request from response

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};
}));
}

React JS multiple API calls, data undefined or unexpected reserved word 'await' mapping through the data:

I'm creating a JS function that will make a call to an API, loop through the returned data and perform another call to retrieve more information about the initial data (for example where the first call return an ID, the second call would return the name/address/number the ID corresponds to). Positioning the async and await keywords though, have proven to be way more challenging than I imagined:
useEffect(() => {
const getAppointments = async () => {
try {
const { data } = await fetchContext.authAxios.get('/appointments/' + auth.authState.id);
const updatedData = await data.map(value => {
const { data } = fetchContext.authAxios.get('/customerID/' + value.customerID);
return {
...value, // de-structuring
customerID: data
}
}
);
setAppointments(updatedData);
} catch (err) {
console.log(err);
}
};
getAppointments();
}, [fetchContext]);
Everything get displayed besides the customerID, that results undefined. I tried to position and add the async/await keywords in different places, nothing works. What am I missing?
map returns an array, not a promise. You need to get an array of promises and then solve it (also, if your way worked, it would be inefficient waitting for a request to then start the next one.)
const promises = data.map(async (value) => {
const { data } = await fetchContext.authAxios.get('/customerID/' + value.customerID);
return {
...value,
customerID: data
};
});
const updatedData = await Promise.all(promises);

Multiple Promises: Get back the input that was used to create them

I have an async function getContextUrl(id: string): Promise<string> that will return an url: string for a given id: string. Because the function is async, the url is wrapped in a Promise.
I now want to map a resolved url to the original id that was used. So far I have the following:
const urlMap: {[key: string]: string} = {}; // map to store url->id as key->value
const ids: string = ['foo', 'bar']; // some IDs
const contextUrlPromises: Array<Promise<string>> = ids.map((id: string) => getContextUrl(id));
Promise.all(contextUrlPromises).then((urls: string[]) => {
// what do I do here? How can I map back to the `id` that belongs to this `url`?
})
// my getUrl function
async function getContextUrl(id: string): Promise<string> {
// driver.getUrl is actually the async function from WebdriverIO, but that shouldn't be relevant for my problem
return driver.getUrl();
}
My problem is: once the urls are resolved, because of their asynchronous nature, I don't know which url belongs to which id. As you can see, my approach is to populate a key/value object urlMap with the values. But maybe there's a better way...
Bonus: I actually only need the first resolved url which contains a certain string, so there is room for further optimization because the calls to getContextUrl() can stop, as soon as such an url has been resolved. Something like this:
const contextPromises: Array<Promise<{ id: string, url: string}>> = ids.map((id: string) => getContextUrl(id).then((context: { id: string, url: string}) => {
if (context.url.includes("foo")){
return context.id;
}
});
Promise.race(contextPromises).then((firstId: string) => {
// do something with the first ID whose URL contains "foo"
})
The data returned by Promise.all are in the same order as the data used to called it.
In the following example, we are going to use the index of the url to find the related id.
Playground
// map to store url->id as key->value
const urlMap: {
[key: string]: string;
} = {};
// some IDs
const ids: string[] = ['foo', 'bar'];
// my getUrl function
function getContextUrl(id: string): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 0);
});
}
const contextUrlPromises: Promise<string>[] = ids.map((id: string) => getContextUrl(id));
(async() => {
const urls: string[] = await Promise.all(contextUrlPromises);
urls.forEach((x, xi) => {
console.log(`The urls ${x} belongs to the id ${ids[xi]}`);
});
})();
In JavaScript :
// map to store url->id as key->value
const urlMap = {};
// some IDs
const ids = ['foo', 'bar'];
// my getUrl function
function getContextUrl(id) {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 0);
});
}
const contextUrlPromises = ids.map(id => getContextUrl(id));
(async() => {
const urls = await Promise.all(contextUrlPromises);
urls.forEach((x, xi) => {
console.log(`The urls ${x} belongs to the id ${ids[xi]}`);
});
})();
In the return statement of getContextUrl() you can return an object containing both the id and the url. In the example below I have formatted it similar to urlMap. This way you can add it to the object without any modification.
let urlMap: {[key: string]: string} = {}; // map to store url->id as key->value
const ids: string = ['foo', 'bar']; // some IDs
const contextUrlPromises: Array<Promise<string>> = ids.map((id: string) => getContextUrl(id));
Promise.all(contextUrlPromises).then((urls: string[]) => {
urlMap = {...urlMap, ...urls};
})
// my getUrl function
async function getContextUrl(id: string): Promise<string> {
// driver.getUrl is actually the async function from WebdriverIO, but that shouldn't be relevant for my problem
return {[id]: driver.getUrl()};
}

Insert Multiple Items Into DynamoDB Using Promise.All

I'm trying to insert multiple items into a DynamoDB table using Promise.all() by reusing a function that inserts a single item and returns a promise. The problem I'm having is the array of returned data objects is empty. That data object is returned correctly when I insert a single item.
I based my implementation on this AWS Post.
My implementation is below:
function addMessage(message) {
const timestamp = Math.floor(new Date().getTime() / 1000);
const params = {
TableName: process.env.DYNAMODB_TABLE,
Item: {
esn: message.esn,
utcMsgTimestamp: parseInt(message.utcMsgTimestamp),
payload: message.payload,
createdAt: timestamp
}
};
console.log(params);
return dynamoDb.put(params).promise();
}
function addMessages(messages) {
// Make all of the addMessage calls immediately, this will return a rejected promise if any fail
return Promise.all(
messages.map(message => {
return addMessage(message);
})
);
}
My Jest unit test case is here:
it.only("add multiple messages", () => {
const dynamodb = require("../dynamodb");
expect.assertions(0);
const messages = [
{
esn: "0-700001",
utcMsgTimestamp: "1034268516",
payload: "0xC0560D72DA4AB2445A"
},
{
esn: "0-99991",
utcMsgTimestamp: "1034268521",
payload: "0xA14AA1DBDB818F9759"
}
];
return dynamodb.addMessages(messages).then(dataArray => {
dataArray.forEach(element => {
console.log(element);
});
// expect(true).toBeTruthy();
});
Any help would be greatly appreciated.

Angular Firestore: What is the correct syntax for a collection query that uses a where clause?

I have working code that queries Firestore and returns an Observable of type ImageUploadWId[]. I would like to return a Promise instead. This is because
my data isn't changing often;
I am performing deletes based on the data that comes in.
SnapshotChanges() returns an array of actions. The first action contains an incomplete array of size 1. The last action contains an array of size 4. The last array is complete; it has all the ImageUploadWIds from the one groupId.
I want just one array that has the ImageUploadWIds from only the designated groupId. Using a Promise is giving me the ImageUploadWIds from all the groupIds. I believe the where clause isn't working.
Here is my working code, which only prints imageUploadgroupIds from one groupId, but returns more than one array. The last array is the only one I need. Edit (added log statements):
getImageInfosWIds(bathroomGroupId: string): Observable<ImageUploadWId[]> {
return this.afs.collection(this.bathroomImagesLocation,
ref => ref.where('groupId', '==', bathroomGroupId)).snapshotChanges()
.map(actions => {
return actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
const imageUpload = new ImageUploadWId();
imageUpload.set(id, <ImageUpload>data);
return imageUpload;
})
})
}
onDeleteGroup(groupId: string) {
this.bathroomGroupService.getImageInfosWIds(groupId)
.subscribe((imageUploads) => {
console.log("imageUploads.length: " + imageUploads.length);
for (let imageUpload of imageUploads) {
console.log("(groupId, filename): (" + imageUpload.groupId + ", " + imageUpload.filename + ")");
}
})
}
Here is my code trying to use a Promise. This prints imageUploads from both of my groups instead of only from the given bathroomGroupId.
getImageInfosWIds(bathroomGroupId: string): Promise<QuerySnapshot> {
return this.afs.collection(this.bathroomImagesLocation,
ref => ref.where("groupId", "==", bathroomGroupId))
.ref
.get();
}
onDeleteGroup(groupId: string) {
this.bathroomGroupService.getImageInfosWIds(groupId)
.then( (querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log("edit-bathrooms: " + doc.data().groupId);
})
},
() => {console.log("Error in edit-bathrooms. promise unfullfilled " +
"in onDeleteGroup( " + groupId + " )")});
}
Edit: This is the final code that worked:
onDeleteGroup(groupId: string) {
this.bathroomGroupService.getImageInfosWIdsObservable(groupId)
.pipe(take(1)).subscribe(
data => {
console.log(data[0].groupId);
});
}
getImageInfosWIdsObservable(bathroomGroupId: string) {
return this.afs.collection(this.bathroomImagesLocation,
ref => ref.where('groupId', '==', bathroomGroupId)).snapshotChanges() //rxjs 5 -> 6 library
.pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data();
const documentId = a.payload.doc.id;
const imageUpload = new ImageUploadWId();
imageUpload.set(documentId, <ImageUpload>data);
return imageUpload;
}))
);
}
Just one final word. If you're still getting an extra array, you may have subscribed to the same Firestore collection in another part of your code and not unsubscribed. That was part of my problem.
AngularFire2 wraps the Firebase reference, so calling .ref.get() creates a new reference and ignores the query function you supplied.
Luckily, RxJS makes it easy to convert an Observable to a Promise. You just need to pipe in first() or take(1) to force the Observable to complete (otherwise the promise will never resolve because Firebase provides an endless realtime stream).
return this.afs.collection(...)
.valueChanges()
.pipe(first())
.toPromise()
It should be,
return this._afs.collection(this.bathroomImagesLocation, x => x
.where('groupId', '==',bathroomGroupId));

Categories