setTimeout on promises inside a for loop - javascript

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

Related

For loop to call API function and wait for it

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

I'm trying to use async/await to get a service, but the second service returns don't fill my variables

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 :-)

Promise.all rollback successful promises' actions on failure

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

Can I await 2 async actions with 1 await?

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's the promise chaining equivalent of awaiting multiple async functions?

I'm studying the usage of promsies and async/await.
I've wrote the following code, which does the following:
It gets some database's data (using Knex.js),
Handles that data,
Assigns the handled data into a specified property.
These 3 steps are done multiple times (In the following code, it's done twice), and are always awaited:
async function run() {
return await getData();
}
async function getData() {
let handledData = {};
handledData.res1 = await knex.select('column1').from('table1').where('column1', '1')
.then(data => handleData(data))
.catch(handleError);
handledData.res2 = await knex.select('column1').from('table1').where('column1', '2')
.then(data => handleData(data, handledData))
.catch(handleError);
return handledData;
}
async function handleData(data) {
let res = [];
data.forEach(item => {
res.push(item.column1);
});
return res;
}
function handleError (error) {
console.log(error);
}
Now, I'm trying to write the promise-chaining equivalent of getData, and this is what I came up with:
async function getData() {
let handledData = {};
let promise = new Promise(function(resolve, error){ resolve(); });
promise
.then(function () {
return knex.select('column1').from('table1').where('column1', '1')
.then(data => handleData(data))
.catch(handleError);
})
.then(function(handled){
handledData.res1 = handled;
return knex.select('column1').from('table1').where('column1', '2')
.then(data => handleData(data))
.catch(handleError);
})
.then(function(handled){
handledData.res2 = handled;
return handledData;
})
.catch(handleError);
return promise;
}
But this doesn't quite work. What happens is that after the first then returns, the await inside run ends its awaiting, which causes run to return - and only then the second then is executed.
How can I make the promise-chaining version work as the multiple-await version does?
(and please, feel free to point out any misunderstaings I made of promises/async-await)
If possible, I'd recommend using Promise.all instead, it'll make your script run faster in addition to making the logic clearer:
const getData = Promise.all([
knex.select('column1').from('table1').where('column1', '1')
// Simply pass the function name as a parameter to the `.then`:
.then(handleData)
.catch(handleError),
knex.select('column1').from('table1').where('column1', '2')
.then(handleData)
.catch(handleError)
])
.then(([res1, res1]) => ({ res1, res2 }));
knex.select().then() returns a promise, so you don't need to wrap it in another promise you just need to set up the chain of then()s and return the whole thing. The result will be that getData returns the promise from the last then. You can return the value you want from that then() which will make it available to the caller. For example:
function run() {
getData()
.then(handledData => console.log(handledData) /* do something with data */)
}
function getData() {
let handledData = {};
// need to return this promise to callers can access it
return knex.select('column1').from('table1').where('column1', '1')
.then(data => handledData.res1 = handleData(data))
.then(() => knex.select('column1').from('table1').where('column1', '2'))
.then(data => {
handledData.res2 = handleData(data)
return handledData
})
.catch(handleError);
}
You could also set this up to pass the handledData object thought the chain, but you don't need to in this case.
The function handleData() is synchronous, so you don't need to make it an async function.

Categories