I have the following code where I iterate through a list with names and use each name to fetch in this case the respective house. This works fine, but I want to include all promises to a Promise.all() - how can I dynamically add a Promise for each name to a Promise.all()? What's good practice here?
list = [
'Name1',
'Name2',
'Name3'
];
this.houses = new BehaviorSubject<any>([]);
const promises = this.greatHouses.map(name => {
let house;
return this.fetchHouse(name).then((result: any[]) => {
house = result[0];
return result[0];
}).then((result: any) => {
return this.fetchLord(result.currentLord);
}).then((result: any) => {
const currentLord = Characters.default.find(char => char.characterName === result.name);
return [{...house, currentLord}];
});
});
Promise.all(promises).then(values => {
console.log(values);
});
fetchHouse(name: string) {
return this.http.get(`${this.housesUrl}`, {
params: {
name
}
}).toPromise();
}
fetchLord(url: string) {
return this.http.get(url).toPromise();
}
Don't use Promises at all. Learn to work with Observables if you're using Angular and its HttpClient.
greatHouses = [
'Name1',
'Name2',
'Name3'
];
// Construct and array of Observables that fetch your data
const houses$ = this.greatHouses.map(name =>
this.fetchHouse(name).pipe(
// map result to house
map((result: any[]) => result[0]),
// map house to an Observable that fetches the lord and return the required data
switchMap((house: any) => this.fetchLord(house.currentLord).pipe(
// map lord to house with currentLord
map((lord: any) => {
const currentLord = Characters.default.find(char => char.characterName === lord.name);
return { ...house, currentLord };
})
))
)
);
// Combine an array of completing Observables with forkJoin to one Observable
forkJoin(houses$).subscribe(houses => console.log('houses', houses))
// Return Observables and handle errors with `catchError` directly where they occur.
fetchHouse(name: string) {
return this.http.get(`${this.housesUrl}`, {
params: {
name
}
}).pipe(
catchError(error => {
console.error('fetchHouse failed', error);
return of(null);
})
);
}
fetchLord(url: string) {
return this.http.get(url).pipe(
catchError(error => {
console.error('fetchLord failed', error);
return of(null);
})
);
}
Related
I making an api call using Promise.all as below:
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
});
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
The above code is working fine.
Now I want to make another api call after the successful completion of this.serviceB.retry(oretry).
The second api is this.serviceB.createDbEntry(sentry) and sentry looks as below:
const sretry: SDInterface = {
hostName,
Id: this.Id.slice(0, this.Id.length),
reason: this.reason
};
And, I am doing it as below
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
const sretry: SDInterface = {
hostName,
Id: this.Id.slice(0, this.Id.length),
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
this.serviceB.createDbEntry(sentry).subscribe(resolve);
});
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
The above code is giving an error:
error: "SequelizeValidationError: string violation: Id cannot be an array or an object"
It is looks like it is not calling the second api for every Id
You may want to take a look a forkJoin
import { Observable, forkJoin } from 'rxjs';
And then
ngOnInit() {
let one = this.http.get('some/api/1') //some observable;
let two = this.http.get('some/api/2') // another observable;
forkJoin([one, tow]).subscribe(response => {
// results[0] is our one call
// results[1] is our second call
let var1 = response[1];
let var2 = response[0];
}/*, error => { in case error handler } */);
}
Wouldn't it be better to use Promise.all() once more?
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
});
})
.then(() => {
return Promise.all(this.Id.slice(0, this.Id.length).map(id => {
return new Promise((resolve, reject) => {
const sretry: SDInterface = {
hostName,
Id: id,
reason: this.reason
};
this.serviceB.createDbEntry(sentry).subscribe(resolve);
});
})
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
And using toPromise() will make the code more concise.
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
return this.serviceB.retry(oretry).toPromise();
})
.then(() => {
return Promise.all(this.Id.slice(0, this.Id.length).map(id => {
const sretry: SDInterface = {
hostName,
Id: id,
reason: this.reason
};
this.serviceB.createDbEntry(sentry).toPromise();
})
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
Use combineLatest, in Angular we use RxJs not promises.
combineLatest(
[this.http.get('call1'), this.http.get('call2')]
).subscribe(([result1, result2]) => {
// do stuff with result1 and result2
});
promise.all takes input in an array and gives response in an array,
Create 2 functions each with your asynchronous logic returning a promise,
Say funcA and funcB, then use below to invoke them parellely
Promise.all([funcA(this.hostName), funcB(this.id)])
.then(respones => {
console.log(responses[0]); //return value for funcA
console.log(responses[1]); //return value for funcB
})
.catch(err => console.log(err));
I am assuming your logic of functions are correct, I just copy-pasted from your question and gave them structure
const funcA = (hostName) => {
hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
});
});
});
}
const funcB = (Id) => {
Id.slice(0, this.Id.length).map(id => {
return new Promise((resolve, reject) => {
const sretry: SDInterface = {
hostName,
Id: id,
reason: this.reason
};
this.serviceB.createDbEntry(sentry).subscribe(resolve);
});
})
}
I can console.log and see the array I created but as soon as I attempt to access it, I get undefined.
async componentDidMount() {
// fetch goal data for display
let response = await fetchWithToken("http://localhost:8080/api/getGoals");
let goalData = await response.json();
goalData = await goalData.filter(skill => skill.Skill === "CS_en");
// get info from people API with distinct list rather than every row
let people = new Set([]);
goalData
.filter(element => element.UpdatedBy !== null)
.forEach(element => {
people.add(element.UpdatedBy);
});
people = Array.from(people);
// call peopleAPI
const peopleObj = await peopleAPI(people);
console.log("peopleObj :", peopleObj);
console.log("peopleObj[0] :", peopleObj[0]);
}
Here is the peopleAPI where I'm calling another api and getting a list of user info.
const peopleAPI = people => {
return new Promise(function(resolve, reject) {
// get people API info
const peopleObj = [];
const apiPromises = [];
if (people) {
people.forEach(empid => {
const apiPromise = fetch(
`https://someApiCall/${empid}`
)
.then(res => res.json())
.then(res => {
peopleObj.push({
empid: res.id,
name: res.name.preferred ? res.name.preferred : res.name.full
});
})
.then(() => apiPromises.push(apiPromise));
});
// once all promises have been resolved, return a promise with the peopleObj
Promise.all(apiPromises).then(() => {
resolve(peopleObj);
});
}
});
};
export default peopleAPI;
Results of console.logs
Don't use push inside fetch.then, just return its value, and then push it to apiPromises`
const peopleAPI = people => {`
return new Promise(function(resolve, reject) {
// get people API info
const apiPromises = [];
if (people) {
people.forEach(empid => {
const apiPromise = fetch(`https://someApiCall/${empid}`)
.then(res => res.json())
.then(res => {
return {
empid: res.id,
name: res.name.preferred ? res.name.preferred : res.name.full
}
});
apiPromises.push(apiPromise)
});
Promise.all(apiPromises).then((data) => {
resolve(data);
});
}
});
};
export default peopleAPI;
Or even simpler and readable
const peopleAPI = people => {`
const apiPromises = people.map(empid => {
return fetch(`https://someApiCall/${empid}`)
.then(res => res.json())
.then(res => ({
empid: res.id,
name: res.name.preferred ? res.name.preferred : res.name.full
}));
});
return Promise.all(apiPromises)
};
I get an Observable<Group[]> from my Firebase collection.
In this Group class is an id which I wanna use to retrieve another dataset array from Firebase, which would be messages for each unique group Observable<Message[]>.(each group has its own chat: Message[])
And it want to return an observable which hold an array of a new Type:
return { ...group, messages: Message[] } as GroupWithMessages
the final goal should be Observable<GroupWithMessages[]>
getGroupWithChat(): Observable<GroupWithMessages[]> {
const groupColl = this.getGroups(); // Observable<Group[]>
const messages = groupColl.pipe(
map(groups => {
return groups.map(meet => {
const messages = this.getMessagesFor(group.uid);
return { messages:messages, ...group} as GroupWithMessages
});
})
);
return messages;
}
}
and here the Message function
getMessagesFor(id: string): Observable<Message[]> {
return this.afs.collection<Message>(`meets/${id} /messages`).valueChanges();
}
sadly that doesnt work because when i create the new Obj I cannot bind messages:messages because messages ist vom typ Observable<Message[]>
I hope that cleares things
UPDATE:
my main problem now comes down to this:
getGroupsWithMessages() {
this.getJoinedGroups()
.pipe(
mergeMap(groups =>
from(groups).pipe(
mergeMap(group => {
return this.getMessagesFor(group.uid).pipe(
map(messages => {
return { ...group, messages } as GroupIdMess;
})
);
}),
tap(x => console.log('reaching here: ', x)),
toArray(),
tap(x => console.log('not reaching here = completed: ', x))
)
),
tap(x => console.log('not reaching here: ', x))
)
.subscribe(x => console.log('not reaching here: ', x));
}
when i call that function my console.log is as follows:
Not sure if I follow what you're doing here but the logic look like you'd want:
getGroupWithChat() {
return this.getGroups.pipe(map(groups=> {
return groups.map(group => this.getMessagesFor(group.uid));
})).subscribe(); // trigger "hot" observable
}
Let me know if I can help further after you clarify.
UPDATE:
So it looks like you need to get the UID of the group before making the call to get the GroupMessages[]?
get Group: Observable
call getMessagesFor(Group.uid)
this example gets groups result$ then
concatMap uses groups result$ to make the messages query
this.getGroups().pipe(
concatMap((group: Group) => this.getMessagesFor(group.uid))
).subscribe((messages: GroupWithMessages[]) => {
console.log(messages);
});
You may still want to map them together but it seems like you know how to do that. concatMap waits for the first to finish, then makes the second call which you need.
Is this closer?
Use forkJoin to wait for messages to be received for all groups. Then map the result of forkJoin to an array of GroupWithMessages like this -
getGroupWithChat(): Observable<GroupWithMessages[]> {
return this.getGroups()
.pipe(
switchMap(groups => {
const messagesForAllGroups$ = groups.map(group => this.getMessagesFor(group.uid));
return forkJoin(messagesForAllGroups$)
.pipe(
map(joined => {
//joined has response like -
//[messagesArrayForGroup0, messagesArrayForGroup1, messagesArrayForGroup2....];
const messagesByGroup = Array<GroupWithMessages>();
groups.forEach((group, index) => {
//assuming that GroupWithMessages has group and messages properties.
const gm = new GroupWithMessages();
gm.group = group;
gm.messages = joined[index];
messagesByGroup.push(gm);
});
return messagesByGroup;
})
)
})
)
}
I usually do that by splitting Observable<any[]> to Observable<any> and then mergeMap the results to inner Observable.
Something like this should work:
getMessagesFor(id: string): Observable<number> {
return of(1);
}
getGroups(): Observable<string[]> {
return of(["1", "2"]);
}
getGroupWithChat() {
this.getGroups().pipe(
mergeMap(groups => from(groups)), // Split the stream into individual group elements instead of an array
mergeMap(group => {
return this.getMessagesFor(group).pipe(
map(messages => {
return Object.assign(group, messages);
})
);
})
);
}
Edit:
Consider BehaviorSubject. It doesn't complete at all:
const behSub: BehaviorSubject<number[]> = new BehaviorSubject([1, 2, 3]);
setTimeout(() => {
behSub.next([4, 5, 6]);
}, 5000);
behSub
.pipe(
mergeMap(arr =>
from(arr).pipe(
tap(), // Do something with individual items, like mergeMap to messages
toArray() // Go back to array
)
)
)
.subscribe(console.log, null, () => {
console.log('Complete');
});
I am trying to write a Sequelize migration script win which I am trying to update my database but it is having many asynchronous operations (database queries and then updating database with particular id)
Here is my code
return db.organizationEntries
.findAll()
.then((entries) => {
return entries.forEach(entry => {
console.log(entry);
db.organizationEntries
.findAll({
attributes: [
[
db.sequelize.fn(
'MAX',
db.sequelize.col('organizationEntries.serial_number')
),
'maximum_serial_no'
]
],
where: {
organizationId: entry.organizationId
}
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no);
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 };
console.log(data)
//problem
db.organizationEntries.update(data, {
where: {
id: entry.id
}
})
.then((result) => {
console.log(result);
})
});
// promises.push(promise);
});
// return Promise.all(promises);
})
Actually what I am trying to do is I am trying to take the list of all orgEntries from the database and then I am finding maximum serial number for that organization_id and then updating that particular orgEntry and like this all these operations in a loop
Now the problem is coming all the things are going in order but after finding max_serial_no it is not updating the database and I am not able to resolve what I should do to make that asynchronous call work in this order
I think you can solve this in two ways:
Simultaneously Promises
In a following code I removed forEach in favor of Promise.all() and map()
The map() method create (and return) a new array with the results of calling a provided function on every element in the calling array.
Example:
let numbers = [1, 2, 3]
let doubledNumbers = numbers.map(n => n * 2)
// doubledNumbers [2, 4, 6]
The Promise.all() method take an array of Promises as argument and returns a single Promise that will be resolved when all promises will be resolved or rejected if one promise failed
Example:
let promise1 = findUserById(5)
let promise2 = findUserFriends(5)
Promise.all([promise1, promise2])
.then(values => {
// values: [user object, list of user friends]
})
Result:
db.organizationEntries.findAll()
.then(entries => {
return Promise.all(entries.map(entry => {
console.log(entry)
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
}))
})
.then(result => {
// result: Array of updated organizationEntries
console.log(result)
})
Step by step Promises with reduce() method
The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. (from MDN web docs)
Example:
let items = [{ name: 'pencil', price: 2 }, { name: 'book', price: 10 }]
let total = items.reduce((total, item) => total += item.price, 0)
// total: 12
Result:
db.organizationEntries.findAll()
.then(entries => {
return entries.reduce((previousPromise, entry) => {
console.log(entry)
return previousPromise
.then(_ => {
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
.then(updatedEntry => {
console.log(updatedEntry)
})
}, Promise.resolve())
})
.then(result => {
// result: Last updated organization entry
console.log('finished')
})
Need help related to promises. Please refer to details below, topic is more theoretical, I don't understand what flow should I use:
We have async function getDataFromUri(), which returns data, which gets filtered and saved to arr of objects, lets name it res;
For each campaign(object) inside of array res I want send async request which will store products images of campaign to object. As result I should have ONE array res where ALL filtered data (campaign name, campaign images) is stored;
I need smth like:
[{
name: "Somename",
images: ['uri', 'uri', 'uri', 'uri', 'uri', 'uri']
},{
name: "Somename",
images: ['uri', 'uri', 'uri', 'uri', 'uri', 'uri']
}
]
Do some other actions with RES array.
This function gets campaigns:
function getDataFromUri(uri) {
return new Promise((resolve, reject) => {
request.get(uri, (err, res, body) => {
if(err || res.statusCode !== 200 ) {
reject(handleErr(err));
} else {
resolve(body);
}
});
});
}
This function gets images of campaign:
function getProductsOfCampaign(id) {
var productsImagesLinks = [];
return new Promise((resolve, reject) => {
getDataFromUri(`SOME_URI/${id}.json`)
.then((json) => {
var productsList = JSON.parse(json).products;
resolve (productsList.map((product) => product.imgSrc));
}).catch((e) => {
throw new Error(e);
})
});
}
Here I met problem:
getDataFromUri(someLink) //Get campaings;
.then((result) => {
//NOT WORKING FOREACH
result.forEach((item, i) => {
item.images = getProductsOfCampaign(item.id);
})
return result;
})
.then((result) => {
//Do something else to array with images;
});
How can I force next after forEach .then() expression to wait all images URLs to be saved?
I tried Promise.all(), but seems have lack of knowledge on how to implement it correct way.
I will really appreciate if you help me resolve this case. Thank you.
Observe that:
item in forEach is a copy.
getProductsOfCampaign returns a Promise.
The web is a best-effort service.
Do this:
getDataFromUri(someLink) // Get campaigns
.then(result => {
var promises = result.map(item =>
getProductsOfCampaign(item.id)
.then(products => {
item.images = products;
return item;
})
// 3: Best-effort service
.catch(() => {})
);
return Promise.all(promises);
}).then(items => {
console.log(items);
// Do something else to array of items with images
});
Other readers can test for correctness with this:
function getDataFromUri(someLink) {
return new Promise((resolve) => {
setTimeout(resolve, 1000, [{id: 1}, {id: 2}]);
})
}
function getProductsOfCampaign(id) {
return new Promise((resolve) => {
setTimeout(resolve, 1000, id * id);
})
}
var someLink = '';
Thanks to Benjamin Gruenbaum for suggesting that .catch(() => {}) can be used with Promise.all for a best-effort service.
let campaigns = null;
getDataFromUri(someLink) //Get campaings;
.then((result) => {
campaigns = result;
let pImages = []
result.forEach((item, i) => {
pImages.push(getProductsOfCampaign(item.id));
});
return Promise.all(pImages);
})
.then((images) => {
campaigns.forEach((campaign, index) => {
campaign.images = images[index];
});
// ... Do something else to array with images;
});