Using a forEach loop to overwrite values - javascript

Alright so I have a code like so
giveaways.forEach( x => {
if (x.extraData) {
const guild = client.guilds.cache.get(x.extraData.server)
const channel = guild.channels.cache
.filter((channel) => channel.type === 'text')
.first()
channel.createInvite().then( inv => { // returns promise
embed.addField(`Join Requirement Giveaway:`, `**[This Server](${inv})**`)
})
} else {
embed.addField(`Normal Giveaway:`,`**[${x.prize}](https://discord.com/channels/${x.guildID}/${x.channelID}/${x.messageID})**`)
}
})
Where the commented part returns a promise , I am using a forEach loop and I am aware that we are unable to overwrite values in an asynchronus one therefore my embed returns the portion of the loop without the promise, I am looking for a possible solution to this.

You can use Promise.all with map
Like this
Promise.all(giveaways.map((x) => new Promise((resolve, reject) => {
// ~~ doing something
somePromises.then((inv) => {
embed.addField(/*~~~*/)
resolve();
})
})))
.then(() => {
// next things..
});

Related

Order of promise wrong

I would like to return a list of promises with Promise.all, but the result is always empty.
Here is my theoric code:
function myFunction() {
const pSqlLines = sql.openDatabase().then(mdb => {
const query = `SELECT data FROM database`
return mdb.prepare(query).all()
})
const globalPromise = new Promise( resolve => {
const promises = []
pSqlLines.then( sqlLines => {
sqlLines.forEach(line => {
promiseA()
.then(res1 => { if (res1 == 1) return promiseB() })
.then(res2 => { if (res2 == 1) return promiseC() })
.then(res3 => { if (res3 == 1) promises.push( new Promise(resolve => resolve(line) ) ) })
}) // The promises chaining is correct as the 'promises' array is correctly fulfilled, but later
resolve( Promise.all(promises) ); // It is always called before the foreach loop. Why?
})
})
return globalPromise.then( result => console.log(result) )
}
Someone can help me please?
Thanks a lot.
You should not need to create a new promise with new Promise when you already have a promise to work with -- in your case: pSqlLines. Wrapping pSqlLines inside a new Promise wrapper is an example of the promise constructor antipattern.
Secondly, this code:
new Promise(resolve => resolve(line) )
...can be replaced with:
Promise.resolve(line)
...and since it is to serve as the return value for a then callback, it can be just:
line
As to your question: you are indeed calling Promise.all(promises) at a moment that the promises array is still empty, as the forEach loop has only executed Promise() and its then chain, but none of the asynchronous callbacks that are passed to those then calls have executed yet. You didn't await the resolution of these promises.
I get from your code that you want to exclude some line values depending on conditions. In that case the promise will resolve to undefined. I suppose you would want to exclude those undefined values, and so maybe a filter(Boolean) is appropriate.
Here is a theoretical solution for your theoretical code:
function myFunction() {
const pSqlLines = sql.openDatabase().then(mdb => {
const query = `SELECT data FROM database`;
return mdb.prepare(query).all();
});
return pSqlLines.then( sqlLines => {
const promises = sqlLines.map(line => {
return promiseA()
.then(res1 => { if (res1 == 1) return promiseB(); })
.then(res2 => { if (res2 == 1) return promiseC(); })
.then(res3 => { if (res3 == 1) return line; });
}));
return Promise.all(promises);
}).then(result => result.filter(Boolean));
}

Generate an array of promises to run sequentially

I'm trying to generate an array of Promises to run sequentially. I've seen lots of tips on this but can't get it to run in my use case.
export default function generateIcons(){
return new Promise((resolve, reject) => {
const containers = document.querySelectorAll('.html2CanvasTarget')
const promises = containers.map(child => processIcon(child))
promises.reduce((p, fn) => p.then(fn), Promise.resolve())
resolve()
})
}
function processIcon(child){
return new Promise((resolve, reject) => html2canvas(child).
then(canvas => uploadFromCanvas(canvas,
child.childNodes[0].className.split(' ')[1] + '.png'))
.then(resolve).catch(reject))
}
Any tips? This just rejects and I can't see why
Looks like you want to resolve the main promise when the canvases are available for all the child elements. You can use Promise.All() for this.
It should also be noted that the querySelectorAll doesn't return anything you can call the .map on. You will have to create an array from what the querySelectorAll returns - which is a NodeList.
export default function generateIcons(){
return new Promise((resolve, reject) => {
const containers = document.querySelectorAll('.html2CanvasTarget');
const promises = Array.from(containers).map(child => processIcon(child))
Promises.All(promises).then(() => resolve());
})
}
containers is a NodeList, and NodeLists don't have a .map method, which is why your code is throwing an error.
Because processIcon already returns a Promise, there's no need to use the Promise constructor again. html2canvas already returns a Promise too, so there's no need for any Promise constructor anywhere (see What is the explicit promise construction antipattern and how do I avoid it?)
If possible, just await each call of it in a for loop. Because uploadFromCanvas returns a Promise too, and you want to wait for it, return it (or await it) as well:
export default async function generateIcons() {
const containers = document.querySelectorAll('.html2CanvasTarget');
for (const container of containers) {
await processIcon(container);
}
}
function processIcon(child) {
return html2canvas(child, {backgroundColor:null})
.then(canvas => uploadFromCanvas(canvas, child.className.split(' ')[1] + '.png'))
.catch(console.log);
}
As per your question, you can use Q module module for that
You need to take an empty array and push promises into it, and just pass this array in Q method (Q.allSettled), Take a look with an example
const Q = require('q');
const promiseHolder = [];
for (let i = 0; i < 10; i += 1) {
promiseHolder.push('Your Promises');
}
Q.allSettled(promises)
.then((results) => {
results.forEach((result) => {
if (result.state === 'fulfilled') {
const value = result.value;
return value;
}
const reason = result.reason;
throw reason;
});
});
In Q.allSettled() The method you always get the result in .then(). There are 2 states. One for success and one for failure.
Success => state === 'fulfilled', value: 'Whatever your promise return'
Failure => state === 'rejected', reason: 'Whatever your promise thrown'
In this case, you have a number of successful and unsuccessful promises.
There is the second approach which is Promise.all() do the same but the issue is whenever any of promise rejected further promise never called.
const promiseHolder = [];
for (let i = 0; i < 10; i += 1) {
promiseHolder.push('Your Promises');
}
Promise.all(promiseHolder)
.then((results) => {
return results;
})
.catch((err) => {
throw err;
});
In the second approach ( Promise.all()), It consists of all your promises pushed from for loop. If any of promise rejected no more promise called and suddenly you got the state of promise rejection in Promise.all().
Promise.all(promiseHolder)
.then((results) => {
return results;
})
.catch((err) => {
console.log('Promise will reject here', err);
throw err;
});
I hope it helps, Happy Coding :)

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

Chained promises in Promise.all without wrapper Promise

Is it possible for Promise.all to return the last value of the chain without a wrapper promise?
Without using await, it doesn't work in my context
Without wrapper example :
function sum1(x){
return new Promise(resolve => {
setTimeout(t => resolve(x+1),3000)
})
}
const p1 = sum1(1);
p1
.then(sum1)
.then(sum1)
Promise.all([p1])
.then(v => console.log(v[0]));
It logs 2 instead of the expected 4.
But if I use a wrapper it works :
function sum1(x){
return new Promise(resolve => {
setTimeout(t => resolve(x+1),3000)
})
}
function sum3(x){
return sum1(x)
.then(sum1)
.then(sum1)
}
const p2 = sum3(1);
Promise.all([p2])
.then(v => console.log(v[0]));
But in my context it gets complicated if I need to create and name a wrapper function for every chain of promises...
Is this possible?
You can store the value returned by p1.then(sum1).then(sum1) and call Promise.all on this value. It waits for the resolution of the promise chain not only the first one. Here is an example:
function sum1(x) {
return new Promise(resolve => {
setTimeout(t => resolve(x + 1), 10);
});
}
const p1 = sum1(1);
const p2 = p1.then(sum1).then(sum1);
Promise.all([p1]).then(v => console.log('P1', v[0]));
Promise.all([p2]).then(v => console.log('P2', v[0]));
Actually all I had to do was call the chain in the variable declaration, so it references the last called promise
function sum1(x){
return new Promise(resolve => {
setTimeout(t => resolve(x+1),3000)
})
}
//The change is here
const p1 = sum1(1)
.then(sum1)
.then(sum1)
Promise.all([p1])
.then(v => console.log(v[0]));
Explanation: the problem with your code was that you store in const p1 = sum1(1); only first part of chain, and in Promise.all([p1]) you get result only from this first part (one solution is just to store all chain in p1 like this: p1=sum1(1).then(sum1).then(sum1). However in your case, you don't need to use Promie.all at all (because in your example there is only one promise p1/2 ):
function sum1(x){
return new Promise(resolve => {
setTimeout(t => resolve(x+1),300)
})
}
// Promise.all([sum1(1).then(sum1).then(sum1)]).then(r => console.log(r)); // this works too
sum1(1).then(sum1).then(sum1).then(r => console.log(r));
How about create a new function to do your task, it seems like promise.all does not fit to your case
const runInWaterfall = (promises) => new Promise((resolve, reject) => {
const result = promises.reduce((acc, curr, index) => {
if(!acc) {
return curr();
}
return acc.then(curr);
}, null);
result.then(resolve).catch(reject);
})
and your task can be rewritten as following
runInWaterfall([() => Promise.resolve(1), sum1, sum1, sum1]).then(result => console.log(result))

JavaScript Promises: conventional way to pass arguments

How do i pass additional arguments to next "step" of promise?
new Promise((resolve, reject) => {
const a = // do stuff and return a string
return Promise.all([
// execute another promise,
// execute yet another promise
])
})
.then(([resultFromPromise_1, resultFromPromise_2]) => {
// how do i pass `const a` here?
})
I can add something like new Promise(resolve => resolve(a)) into Promise.all array, but this looks ugly. Is there better way to pass data in such cases?
I can add something like new Promise(resolve => resolve(a)) into Promise.all array, but this looks ugly. Is there better way to pass data in such cases?
Yes: Use then. If you already have a promise, using new Promise is never needed. then creates a promise, which waits for the resolution of the one you called it on, and then gets resolved with what you return from the then callback or gets rejected if you throw an exception. One of the keys of promises is how using then (and catch) transforms things at each link in the chain.
In that specific case, you'd use then on the original promise and use its callback to transform the result using a (although if you want to wait until they're all done, you can do that too; covered later).
Side note: The new Promise line at the beginning of the code of your question shouldn't be there, you don't return a promise out of the promise executor (the callback you pass to new Promise).
Example:
const a = "some string";
Promise.all([
getPromise("one").then(result => result + " - " + a), // ***
getPromise("two")
])
.then(results => {
console.log(results);
});
function getPromise(str) {
// (Could use Promise.resolve here; emphasizing asynchronousness)
return new Promise(resolve => {
setTimeout(() => {
resolve(str);
}, 250);
});
}
Alternately, if you really only want to use a when all of the promises you're passing to Promise.all have resolved, you can do that, too:
const a = "some string";
Promise.all([
getPromise("one"),
getPromise("two")
])
.then(([result1, result2]) => {
return [result1 + " - " + a, result2]; // ***
})
.then(results => {
console.log(results);
});
function getPromise(str) {
// (Could use Promise.resolve here; emphasizing asynchronousness)
return new Promise(resolve => {
setTimeout(() => {
resolve(str);
}, 250);
});
}
First off, your first promise has an error, you're not resolving it. You should do something like this:
new Promise((resolve, reject) => {
const a = 1;
resolve(Promise.all([
...
]))
})
And as for your question, instead of new Promise(resolve => resolve(a)) you can just pass a directly to the all array. ie:
new Promise((resolve, reject) => {
const a = 1;
resolve(Promise.all([
Promise.resolve("a"),
Promise.resolve("b"),
a,
]))
})
.then(([resultFromPromise_1, resultFromPromise_2, a]) => {
console.log(a);
})

Categories