javascript map promises inside them - javascript

I am having this code:
signers.map(async (signer, tx) => {
const address = await signer.getAddress();
console.log(address);
return { account: address, amount: 1 }
})
console.log("awesome");
The problem is that awesome gets printed first. How do I make sure that until all the promises are resolved inside map, it doesn't come to awesome. The cleanest way would be much appreciated.

All async functions return a promise that resolves when the body of the function has completed or rejects when an exception occurs.
And, .map() is not async-aware at all. It doesn't pause the loop waiting for the promise. So, you are running all your asynchronous operations in-flight at the same time and signers.map() returns an array of promises. If you want to know when they are all done and get the results of all those promises, you need to use Promise.all() on that array of promises.
let promises = signers.map(async (signer, tx) => {
const address = await signer.getAddress();
console.log(address);
return { account: address, amount: 1 }
});
Promise.all(promises).then(results => {
console.log("awesome");
console.log(results);
}).catch(err => {
console.log(err);
});
If, what you really want to do is to run your asynchronous operations serially, one after the other, then use a regular for loop in a containing async function because a for loop is async-aware and will pause the loop waiting for the await.
let addresses = [];
for (let signer of signers) {
const address = await signer.getAddress();
console.log(address);
addresses.push({ account: address, amount: 1 });
}
console.log(addresses);

Note that there is nothing stopping you from awaiting Promise.all since it's just a promise of results;
const promises = signers.map(async (signer, tx) => {
const address = await signer.getAddress();
console.log(address);
return { account: address, amount: 1 }
});
const results = await Promise.all(promises);
console.log("awesome");

Related

Complete one promise array first, then complete another promise array JavaScript

I have a problem with promises and have no idea how resolve this:
My idea is to have two methods that are a "dispatcher". In testFetchFaceCharacter() call all promises that I need to resolved first. Need all data from state: {Body:{}, TopA:{}, eyes:{}} When testFetchCharacter() finished, immediately start testFetchTopCharacter(), only if all previous promises executed successfully.
However, at this point (with this code) have a errors. The promises aren't executed "Synchronously". still retrieved "asynchronously". Which "should not happen". Since it "reduce" (from what I read in several articles) avoided that behavior.
const buildCharacter = (state) => {
try {
testFetchFaceCharacter(state);
testFetchTopCharacter(state);
} catch (e) {
console.error(e + "En buildCharacter");
}
const testFetchCharacter = (state) => {
const promises = [
fetchCustom(state.Body.id, state.Body.file),
fetchCustom(state.TopA.id, state.TopA.file),
fetchCustom(state.eyes.id, state.eyes.file),
fetchCustom(state.mouth.id, state.mouth.file),
fetchCustom(state.nose.id, state.nose.file),
fetchCustom(state.eyebrow.id, state.eyebrow.file),
fetchCustom(state.Clothing.id, state.Clothing.file),
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
const testFetchTopCharacter = (state) => {
const promises = [
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
Y try this:
Execute -> Body
Execute -> TopA
Execute -> [eyes, mouth, nose, Clothing, eyebrow] //No matter the order
then
Execute [beard, hat, hair, glass] //not matter the order
First of all, there is a mistake in your code. You need to understand that as soon as you called a function, you triggered a logic that does something, even if you don't listen to the promise right away, the logic is executing.
So what happened, is that you launched all actions in "parallel" when you are doing function calls in the promises array.
Solution A
You need to "postpone" the actual call of a function until the previous function was successful, you can either do it manually, e.g.
const testFetchTopCharacter = async (state) => {
await fetchCustom(state.beard.id, state.beard.file),
await fetchCustom(state.hat.id, state.hat.file),
await fetchCustom(state.hair.id, state.hair.file),
await fetchCustom(state.glass.id, state.glass.file)
}
Solution B
If you want to use reducer you need to use callback in that array, so that when promise is completed you call the next callback in the chain.
const testFetchTopCharacter = (state) => {
const promises = [
() => fetchCustom(state.beard.id, state.beard.file),
() => fetchCustom(state.hat.id, state.hat.file),
() => fetchCustom(state.hair.id, state.hair.file),
() => fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce((promise, callback) => promise.then(callback), Promise.resolve());
}
Solution C
If an order doesn't matter to you just do Promise.all
const testFetchTopCharacter = (state) => {
return Promise.all([
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
]);
}

Make sure map finishes before return response [duplicate]

This question already has answers here:
Async function returning promise, instead of value
(3 answers)
Closed 2 years ago.
As my first real MERN project, I'm building a message board. I'm currently working on a node route to request the board names with their associated post count but I've hit an issue. Instead of getting the values that I need, I'm recieving info telling me there is a promise pending, which seems odd as I'm using async/await. Here's the function:
exports.postsPerBoard = async (req, res) => {
try {
const boards = await Board.find();
const postCount = boards.map(async (boardI) => {
const posts = await Post.find({ board: boardI.slug });
return [boardI.slug, posts.length];
});
console.log(postCount);
res.send(postCount);
} catch (err) {
console.error(err.message);
res.status(500).send('server error');
}
};
and here is the result of the console log:
[0] [
[0] Promise { <pending> },
[0] Promise { <pending> },
[0] Promise { <pending> },
[0] Promise { <pending> },
[0] Promise { <pending> }
[0] ]
const postCount = boards.map(async (boardI) => {
const posts = await Post.find({ board: boardI.slug });
return [boardI.slug, posts.length];
});
Since this is an async function, it will return a promise. map calls the function for each element of the array, gets the promises they return, and creates a new array containing those promises.
If you would like to wait for that array of promises to each finish, use Promise.all to combine them into a single promise, and then await the result.
const promises = boards.map(async (boardI) => {
const posts = await Post.find({ board: boardI.slug });
return [boardI.slug, posts.length];
});
const postCount = await Promise.all(promises);
console.log(postCount);

Await is not waiting for api post to finish, just happens simultaneously

I have a problème which I can't seem to figure out. I need this await call to happen only when it finishes one by one in the loop, but it seems like its just calling it twice in the same time quickly, which is not making the api post work correctly.
I've tried to use async, await, but the await doesn't seem to like it being in two loops.
Has anyone got a better solution?
async pushToCreate() {
const toCreate = this.json.toCreate
let counter = 0
// toCreate Sample = { en: [{}, {}] }, { fr: [{}, {}] }
Object.keys(toCreate).forEach((item) => {
toCreate[item].forEach((el) => {
const lang = item
const dataContent = el
await this.wp.products().param('lang', lang).create({
title: dataContent.dgn,
fields: dataContent,
status: 'publish'
}).then(function( response ) {
counter++
}).catch(function(err) {
console.log('error in create', err)
})
console.log('await finished')
})
})
// should console log when above is finished
console.log('END')
}
If you want to await for promises forEach will not help you. To make it work you should use:
for/for..in/for..of loops for the sequential processing and await for each Promise to become fulfilled on each iteration
map for the parallel processing which will return an array of promises and after that you can await for them to become fulfilled with Promise.all()
If you use async/await it looks more clear to use try/catch instead of then/catch chaining
Try this out if you need sequential processing:
...
let lang;
let dataContent;
for (const item in toCreate) {
lang = item;
for (let i = 0; i < toCreate[lang].length; i++) {
dataContent = toCreate[lang][i];
try {
await this.wp.products().param('lang', lang).create({
title: dataContent.dgn,
fields: dataContent,
status: 'publish',
});
counter++;
} catch (err) {
console.log('error in create', err);
}
console.log('await finished');
}
}
...
or this if you need parallel processing:
...
let lang;
let dataContent;
await Promise.all(
Object.keys(toCreate).map(async item => {
lang = item;
await Promise.all(
toCreate[lang].map(async el => {
dataContent = el;
try {
await this.wp.products().param('lang', lang).create({
title: dataContent.dgn,
fields: dataContent,
status: 'publish',
});
counter++;
} catch (err) {
console.log('error in create', err);
}
console.log('await finished');
}),
);
}),
);
...
Here is a good explanation of the sequence and parallel asynchronous calls handling using async/await: Using async/await with a forEach loop
I believe there are a few issues with your code.
The first is that await only works when a function returns a promise. Functions that return promises initially return a value that indicates that a promise is pending. If the function you have on the right side of await does not return a promise, it will not wait.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
The second is that you are also using the .then() method, which triggers it's callback argument when the promise is fulfilled. I do not think you can use both await and .then().https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
If you want to have a similar format as the .then() .catch() pattern, you could use a try/catch block with the async/await.
async function f() {
try {
let response = await fetch('/example');
let user = await response.json();
} catch(err) {
// catches errors both in fetch and response.json
alert(err);
}
}
https://javascript.info/async-await

Defer an async service call until promise is resolved

I want to create an array of Twilio API calls to be resolved later in Promise.all.
TwilioClient.messages.create(payload) is getting called immediately inside the returned promise (A). How can I defer until the Promise.all chain resolves it (B)?
let twilioTexts = [];
if (twilioNumbers) {
twilioTexts = twilioNumbers.map(phoneNumber => getTwilioText(mailTokens, phoneNumber));
// (A) ==> Twilio is sending messages here upon assigning to variable `twilioTexts`.
}
await Promise.all([
// do other stuff
...twilioTexts, // (B) ==> instead, Twilio should make all calls here
]);
...
function getTwilioText(mailTokens, phoneNumber) {
return new Promise((resolve, reject) => {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
TwilioClient.messages.create(payload);
});
}
Edit:
await Promise.all([
// do other async stuff in parallel
// check if twilioNumbers then spread map into Promise.all array
twilioNumbers && ...twilioNumbers.map(phoneNumber => getTwilioText(mailTokens, phoneNumber)),
]);
function getTwilioText(mailTokens, phoneNumber) {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
return TwilioClient.messages.create(payload);
}
Your second example is almost there, but Promise.all works on an array of promises. The twilioNumbers &&... line produces an array of promises, so you've got an array of promises inside another array, which won't work.
So you can use the spread operator:
await Promise.all([
// do other stuff
...(twilioNumbers
? twilioNumbers.map(phoneNumber => getTwilioText(mailTokens, phoneNumber))
: []
),
]);
...
function getTwilioText(mailTokens, phoneNumber) {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
return TwilioClient.messages.create(payload);
}
At this point, it's a bit verbose, so you might want to factor that part out:
await Promise.all([
// do other stuff
getTwilioTexts(twilioNumbers, mailTokens),
]);
...
// returns a promise for an array
async function getTwilioTexts(numbers, mailTokens) {
return numbers
? Promise.all(numbers.map(num => getTwilioText(mailTokens, num)))
: [];
}
function getTwilioText(mailTokens, phoneNumber) {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
return TwilioClient.messages.create(payload);
}
You seem to be looking for
const otherPromises = // do other stuff, *triggered first*
const twilioTexts = (twilioNumbers || []).map(phoneNumber => getTwilioText(mailTokens, phoneNumber));
await Promise.all([
...otherPromises,
...twilioTexts,
]);
Ultimately, it shouldn't matter though as you want to run the other stuff concurrently with sending texts anyway.

JavaScript: Promise.All() and Async / Await and Map()

I'm trying to understand why the following two code blocks yield different results.
Code Block one works as expected and returns an array of the providers looked up from the database. On the other hand, Code Block two returns an array of functions. I feel like I'm missing something simple here in my understanding of Promise.all() and async / await.
The differences in the code blocks are:
Block 1: Array of promise functions is created and then wrapped in async functions using the map operator.
Block 2: Array of promises functions are created as async functions. Therefore, the map operator isn't called.
In case you aren't familiar with the Sequelize library, the findOne() method that gets called returns a promise.
Also worth mentioning, I know that I could use a single find query with and "name in" where clause to get the same results without creating an array of promises for multiple select queries. I'm doing this simply as a learning exercise on async / await and Promise.all().
CODE BLOCK 1: Using map() inside Promise.all()
private async createProfilePromises(profiles){
let profileProviderFindPromises = [];
//Build the Profile Providers Promises Array.
profiles.forEach(profile => {
profileProviderFindPromises.push(
() => {
return BaseRoute.db.models.ProfileProvider.findOne({
where: {
name: {[BaseRoute.Op.eq]: profile.profileProvider}
}
})}
);
});
//Map and Execute the Promises
let providers = await Promise.all(profileProviderFindPromises.map(async (myPromise) =>{
try{
return await myPromise();
}catch(err){
return err.toString();
}
}));
//Log the Results
console.log(providers);
}
CODE BLOCK 2: Adding the async function without the use of map()
private async createProfilePromises(profiles){
let profileProviderFindPromises = [];
//Build the Profile Providers Promises Array.
profiles.forEach(profile => {
profileProviderFindPromises.push(
async () => {
try{
return await BaseRoute.db.models.ProfileProvider.findOne({
where: {
name: {[BaseRoute.Op.eq]: profile.profileProvider}
}
});
}catch(e){
return e.toString();
}
}
);
});
//Execute the Promises
let providers = await Promise.all(profileProviderFindPromises);
//Log the Results
console.log(providers);
}
Your code basically boils down to:
const array = [1, 2, 3];
function fn() { return 1; }
array.map(fn); // [1, 1, 1]
array.push(fn);
console.log(array); // [1, 2, 3, fn]
You push a function (whether that is async or not does not matter), instead you want to push the result of calling the function:
array.push(fn());
or in your case:
array.push((async () => { /*...*/ })());
How I'd write your code:
return Promise.all(profiles.map(async profile => {
try{
return await BaseRoute.db.models.ProfileProvider.findOne({
where: {
name: { [BaseRoute.Op.eq]: profile.profileProvider }
}
});
} catch(e) {
// seriously: does that make sense? :
return e.toString();
}
}));

Categories