I have my data going into a API in a loop as below. How can I ensure that the next iteration is called when the first is finished? Image my API takes a second for each element
saveMyData(){
this.elements.forEach(element => {
CALL_MY_API.read(element)
.then(response => {
// comes here in 1 second
// do something with response
}
.catch(error => {
this.showError(error)
})
})
}
This starts a bunch of asynchronous operations in parallel. I assume that's not what you want, and you want to wait for one to actually finish before starting the next one.
It's easiest if you can use async/await syntax:
async saveMyData() {
for (const element of this.elements) {
try {
const response = await CALL_MY_API.read(element)
// do something with response
} catch (error) {
this.showError(error)
}
}
}
If you can't, you'll need to chain the promises manually:
saveMyData() {
// Start with an immediately resolved promise.
let promise = new Promise((resolve, reject) => resolve())
// Add each element to the chain.
this.elements.forEach(element => {
promise = promise.then(() => {
return CALL_MY_API.read(element)
.then(response => {
// do something with response
})
.catch(error => {
this.showError(error)
})
})
})
}
Related
I have requirements to search given "key" in API returned response. I have to invoke same API multiple times with different inputs.
Upon getting the response from API , i need to search KEY in API response. If the KEY is not present then invoke the same API again with different input and search ...continue .
Basically , until i complete one iteration of searching the KEY in API response, the execution should wait . How can achieve this ? Please suggest.
I have tried with below approach and execution not waiting until it search keys in API response.
for (i=0;i< departments.length;i++)
{
getInformation(departments[i]).
then((response) => {
//verify whether given key present in response
})
.catch((err)) => {
//log error
});
}// end of for loop
}
Note: I would like to continue the Search operation with next keys even if their is any exception in one of the API call.
Thanks
You've to use recursivity
let departments = [xxx]
let myResult = []
const search = (pool, resolve, reject) => {
if (pool.length === 0) {
return resolve()
}
let [department, ...rest] = pool
getInformation(department)
.then(data => {
myResult.push('something')
})
.catch(e => {})
.finally(() => {
search(rest, resolve, reject)
})
}
const searchInDeps = new Promise((resolve, reject) => {
search(departments, resolve, reject)
})
searchInDeps
.then(() => {
// do something with myResult
})
.catch(e => {})
One other way as you need to loop, you can run each promises, and wait for the result of all.
let searches = []
for (i=0;i< departments.length;i++) {
searches.push(new Promise((resolve, reject) => {
getInformation(departments[i])
.then(data => {
resolve('something')
})
.catch(e => resolve('something'))
})
}
Promise.all(searches)
.then(results => {
// array of results
})
.catch(error => {})
I have a service to get a list from server. But in this list I need to call another service to return the logo img, the service return ok, but my list remains empty. What i'm did wrong ?
I tried to use async/await in both services
I tried to use a separate function to get the logos later, but my html don't change.
async getOpportunitiesByPage(_searchQueryAdvanced: any = 'active:true') {
this.listaOportunidades = await this._opportunities
.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced)
.toPromise()
.then(result => {
this.totalSize = result['totalElements'];
return result['content'].map(async (opportunities: any) => {
opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']);
console.log(opportunities.logoDesktopUrl);
return { opportunities };
});
});
this.getTasks(this.totalSize);
}
No errors, just my html don't change.
in my
console.log(opportunities.logoDesktopUrl);
return undefined
but in the end return filled.
info:
Angular 7
server amazon aws.
await is used to wait for promise.
You should return promise from getBrand if you want to wait for it in getOpportunitiesByPage.
Change the getBrand function as following.
getBrand(brandsUuid): Observable<string> {
this.brandService.getById(brandsUuid).pipe(map(res => {
console.log(res.logoDesktopUrl); return res.logoDesktopUrl;
}))
}
Change opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']); to opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']).toPromise();
Please make sure you imported map from rxjs/operators.
At first,when you await, you should not use then.
At second, async/await runs only with Promises.
async getOpportunitiesByPage(_searchQueryAdvanced: any = 'active:true') {
const result = await this._opportunities
.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced)
.toPromise();
this.totalSize = result['totalElements'];
this.listaOportunidades = result['content'].map(async (opportunities: any) => {
opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']);
console.log(opportunities.logoDesktopUrl);
return opportunities;
});
this.getTasks(this.totalSize);
}
getBrand(brandsUuid) {
return new Promise((resolve, reject) => {
this.brandService.getById(brandsUuid).subscribe(res => {
console.log(res.logoDesktopUrl);
return resolve(res.logoDesktopUrl);
}, err => {
return reject(err);
});
});
}
But, because rxjs is a used in Angular, you should use it instead of async/await :
getOpportunitiesByPage: void(_searchQueryAdanced: any = 'active:true') {
this._opportunities.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced).pipe(
tap(result => {
// we do that here because the original result will be "lost" after the next 'flatMap' operation
this.totalSize = result['totalElements'];
}),
// first, we create an array of observables then flatten it with flatMap
flatMap(result => result['content'].map(opportunities => this.getBrand(opportunities['brandsUuid']).pipe(
// merge logoDesktopUrl into opportunities object
map(logoDesktopUrl => ({...opportunities, ...{logoDesktopUrl}}))
)
),
// then we make each observable of flattened array complete
mergeAll(),
// then we wait for each observable to complete and push each result in an array
toArray()
).subscribe(
opportunitiesWithLogoUrl => {
this.listaOportunidades = opportunitiesWithLogoUrl;
this.getTasks(this.totalSize);
}, err => console.log(err)
);
}
getBrand(brandsUuid): Observable<string> {
return this.brandService.getById(brandsUuid).pipe(
map(res => res.logoDesktopUrl)
);
}
Here is a working example on stackblittz
There might be a simpler way to do it but it runs :-)
I am executing multiple promises with the following snippet:
await Promise.all([promise1, promise2, promise3]);
What I would like to achieve is to rollback the effects of the successful promises on the case of a failure from Promise.all().
In more specific terms, this means that the above will do some file encryptions, but if one fails, I would like to delete the other two (or one) files that were encrypted successfully so as to have consistent and clean file groups.
From what I've read this means that I would need two steps:
1. Catching the errors for each promise so that Promise.all() won't throw an error.
2. The puzzling part: Having another Promise.all() sort of:
await Promise.all([rollbackPromise1, rollbackPromise2, rollbackPromise3]);
This one seems to be the tricky part: Should I execute all the rollbacks independent of the promise that failed? This means that I should do another catch for every error such that the Promise.all() waits for every rollback to finish.
Is this the best way to do this, I find it pretty inefficient and ugly in terms of code.
You could create your own function implementing the asynchronous call of the functions and performing a rollback if required.
// Function that'll perform a promise.all and rollback if required
async function allWithRollback(promises) {
// using the map we are going to wrap the promise inside of a new one
return Promise.all(promises.map(([
func,
rollbackFunc,
], xi) => ((async() => {
try {
await func;
console.log('One Function succeed', xi);
} catch (err) {
console.log('One Function failed, require rollback', xi);
await rollbackFunc();
}
})())));
}
// Call the custom Promise.all
allWithRollback([
[
// First param is the promise
okPromise(),
// Second param is the rollback function to execute
() => {},
],
[okPromise(), () => {}],
[errPromise(), rollback1],
[errPromise(), rollback2],
[okPromise(), () => {}],
]);
// ---------
async function okPromise() {
return true;
}
async function errPromise() {
throw new Error('no one read this');
}
async function rollback1() {
console.log('Performed the rollback1');
}
async function rollback2() {
console.log('Performed the rollback2');
}
You can create a naive solution as follows:
const errorHandlers = []
function enc1 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('str')
}, 1000)
errorHandlers.push(() => {
console.log('handler 1')
})
})
}
function enc2 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('str')
}, 2000)
errorHandlers.push(() => {
console.log('handler 2')
})
})
}
function enc3 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('str')
}, 3000)
errorHandlers.push(() => {
console.log('handler 3')
})
})
}
Promise.all([enc1(), enc2(), enc3()]).then(() => {
console.log('all resovled')
}).catch((e) => {
errorHandlers.forEach(handler => handler(e))
})
It'd give you option to handle the 'global' error in each promise. Before creating promise all, you can reset the errorHandlers to prevent multiple errorHandler execution
I have a node module that exports a promise and resolves a database connection. When it resolves I use the connection to query records which is another async operation. Can I do both of these async actions with 1 await?
In this case the querying async call is dependent on the async promise resolving to a db connection.
Module
module.exports = {
db: new Promise((acc, rej) => {
if (!db.authenticated) {
sequelize.authenticate()
.then((res) => {
db.authenticated = true;
acc(db);
})
.catch((err) => {
rej(err)
});
} else {
acc(db);
}
})
};
usage
const db = require('../db/db.js').db;
const existingUser = await db.Person.findOne({where : {email : body.email}});
In response to my comment using await Promise.all([first(), second()]);:
The promise.All() method will return a single promise that finally resolves when all the promises pass as an iterable or when the iterable does not contain any promises. It will reject with the reason of the first promise that rejects.
Example
async function one() {
return new Promise(resolve => {
resolve('One')
})
}
async function two() {
return new Promise(resolve => {
resolve('Two')
})
}
async function run() {
return await Promise.all([one(), two()]); // One await
}
run().then((response) => {
// Access Individually
console.log(response[0]); // One
console.log(response[1]); // Two
// Access Together
console.log(response);
})
And to respond to your recent comment. To pass the value from one promise to the other, if the second function is dependent on that parameter. We might do something like this.
Example 2
async function first() {
return new Promise(resolve => {
resolve('First') // Resolve 'first'
})
}
async function second(response) {
return new Promise(resolve => {
resolve(response); // first() ran, then we appended '& second', then resolve our second response
})
}
async function run() {
// Wait for first() response, then call second() with response + append 'second'
return await first().then((response) => second(response + ' & second'))
}
run().then((response) => {
// Output: first & second
console.log(response)
})
Documentation: promise.All() - MDN
What I want to do is this:
Loop over a collection of data, for each data element make a call to an API, wait that the promise fail or resolve, pause for 30sec... then do this again for the next data element until there is nothing to iterate over in the collection ... finally display a 'done' message.
So far this is the code I wrote, gathering ideas in other SO questions, and this is not working the way I'd like.
populateDB();
// these 2 helper functions were found on SO
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function sleep(fn, ...args) {
await timeout(30000);
return fn(...args);
}
// this is the main function that makes the api calls
function populateDB() {
for (let stock of universe.universe) {
sleep(() => {
// actual API call
return alpha.data
.daily(stock)
.then(data => {
// write data to the db when promise resolves
db.get("stocks")
.push({ [stock]: polishData(data) })
.write();
})
.catch(err => console.log(err));
});
}
console.log("Done!");
}
All the promised are still chaining one after another there is no pause. I don't think I understand Promises enough to debug this... what would be the code that works the way I'd like it to ?
Use async/await in your populateDB function:
async function populateDB() {
for (let stock of universe.universe) {
await sleep(() => {
// actual API call
return alpha.data
.daily(stock)
.then(data => {
// write data to the db when promise resolves
db.get("stocks")
.push({ [stock]: polishData(data) })
.write();
})
.catch(err => console.log(err));
});
}
console.log("Done!");
}