How to use an async reducer to build an array of promises? - javascript

Here is a function to build db queries:
const buildDbQueries = async elements => elements.reduce(
async (acc, element) => {
// wait for the previous reducer iteration
const { firstDbQueries, secondDbQueries } = await acc
const asyncStuff = await someApi(element)
// leave if the API does not return anything
if (!asyncStuff) return { firstDbQueries, secondDbQueries }
// async db query, returns a Promise
const firstDbQuery = insertSomethingToDb({
id: asyncStuff.id,
name: asyncStuff.name
})
// another async db query, returns a Promise
// have to run after the first one
const secondDbQuery = insertAnotherthingToDb({
id: element.id,
name: element.name,
somethingId: asyncStuff.id
})
return {
firstDbQueries: [...firstDbQueries, firstDbQuery],
secondDbQueries: [...secondDbQueries, secondDbQuery]
}
},
// initial value of the accumulator is a resolved promise
Promise.resolve({
firstDbQueries: [],
secondDbQueries: []
})
)
This function returns promises which should not be executed until they are resolved.
Now we use that function
const myFunc = async elements => {
const { firstDbQueries, secondDbQueries } = await buildDbQueries(elements)
// we don't want any query to run before this point
await Promise.all(firstDbQueries)
console.log('Done with the first queries')
await Promise.all(secondDbQueries)
console.log('Done with the second queries')
}
The problems are:
the queries are executed before we call Promise.all.
the firstDbQueries queries are not executed before the secondDbQueries causing errors.
EDIT
As suggested in a comment, I tried not to use reduce, but a for … of loop.
const buildDbQueries = async elements => {
const firstDbQueries = []
const secondDbQueries = []
for (const element of elements) {
const asyncStuff = await someApi(element)
// leave if the API does not return anything
if (!asyncStuff) continue
// async db query, returns a Promise
const firstDbQuery = insertSomethingToDb({
id: asyncStuff.id,
name: asyncStuff.name
})
// another async db query, returns a Promise
// have to run after the first one
const secondDbQuery = insertAnotherthingToDb({
id: element.id,
name: element.name,
somethingId: asyncStuff.id
})
firstDbQueries.push(firstDbQuery)
secondDbQueries.push(secondDbQuery)
}
return { firstDbQueries, secondDbQueries }
}
This still produces the exact same problems as the previous version with reduce.

Don't use an async reducer. Especially not to build an array of promises. Or an array of things to run later. This is wrong on so many levels.
I guess you are looking for something like
function buildDbQueries(elements) {
return elements.map(element =>
async () => {
const asyncStuff = await someApi(element)
// leave if the api doesn't return anything
if (!asyncStuff) return;
await insertSomethingToDb({
id: asyncStuff.id,
name: asyncStuff.name
});
return () =>
insertAnotherthingToDb({
id: element.id,
name: element.name,
somethingId: asyncStuff.id
})
;
}
);
}
async function myFunc(elements) {
const firstQueries = buildDbQueries(elements)
// we don't want any query to run before this point
const secondQueries = await Promise.all(firstQueries.map(query => query()));
// this call actually runs the query ^^^^^^^
console.log('Done with the first queries');
await Promise.all(secondQueries.map(query => query()));
// this call actually runs the query ^^^^^^^
console.log('Done with the second queries')
}

Related

Typescript promise not resolving correctly before moving on in async code

I am trying to populate the 'documents' object which is just a Documentation array. I first build out a list of Promise and store the values in 'promises' to then call Promise.all on to fire things all off at once. Then for each promise, I try and grab the text value from each response in resolvePromise(), create a document from the value, and push it to documents.
type Documentation = { name: string; source: string; rawText: string };
const documents: Documentation[] = [];
async function resolvePromise(entry: Response, documents: Documentation[]) {
const document = { name: '', source: '', rawText: '' };
document.rawText = await entry.text(); // entry.text(): Promise<string>
documents.push(document);
console.log('documents length is now: ' + documents.length);
}
async function createDocumentsObject() {
const promises: Promise<Response>[] = [];
// code to populate promises list
HELP.forEach((value: ConfigItem[], key: string) => {
value.forEach((configElem: ConfigItem) => {
if (!configElem.subsection) {
const promise = getRequest(configElem.source);
promises.push(promise);
}
});
});
console.log('promises: ');
console.log(promises); // const promises: Promise<Response>[]
await Promise.all(promises).then(async values => {
values.forEach(async entry => {
if (!entry.ok) {
return Promise.reject();
} else {
return await resolvePromise(entry, documents);
}
});
});
console.log('docs');
console.log(documents);
}
In the print statement below, you can see the promises variable is populated with the promises correctly. However, the call to console.log(documents); runs before the calls to resolvePromise(). I tried using await entry.text(); to get the string value from entry.text() before moving on, but that is not working. I am trying to populate the documents object immediately for use right after in the code. I am new to TS so noob friendly explanations are appreciated!
The problem is here:
values.forEach(async entry => {...})
forEach will not resolve the promises returned by the async function.
I would change it to
return Promise.all( values.map( async entry => {...} ) )

FindOne inside map returns no results

I'm trying to do a search using FindOne inside map but it never finds the data by Product Id. I don't understand the reason. Im using express on nodejs.
This is my code:
const calc = (details) => {
let grandSubtotal = 0;
details.map( async detail => {
const {verifyProduct} = await Product.find({ _id: detail._id});
console.log(detail._id);
console.log(verifyProduct); // UNDEFINED
...
Should be:
const result = await Promise.all(details.map( async (detail) => { … } ));
when you do it like you done you will get a pending promise object that never going to be resolved, I don’t know if you want to return some results, if no just do await Promise.all
Also this should be:
const calc = async (details) => { … }
Mongoose find returns a list of results. findOne returns an object.
The code is doing the equivalent of:
const {verifyProduct} = []
Use findOne to get an object to destructure, and test the result before use.
details.map( async (detail) => {
const res = await Product.findOne({ _id: detail._id });
if (!res) {
//throw new Error('No id '.detail._id)
console.log('No id', detail._id)
}
const { verifyProduct } = res
console.log(detail._id);
console.log(verifyProduct);
}
Also (as #antokhio noted), if you want to use the returned result array of the details.map you will need to await those as well.
You don't need await here
Product.find(({ _id }) => _id === detail._id );

How to turn multiples promises dependent on each other's response into await and make sure all requests fire

I have an array of objects that has 3 items inside it. I'm iterating over it and during each loop i need to make 3 API calls that all depend on each other's response. So in total there should be 9 API requests completed.
const arrayOfObjects = [
{
first: 'thing',
second: 'thing',
third: 'thing'
},
{
fourth: 'thing',
fifth: 'thing',
sixth: 'thing'
},
{
seventh: 'thing',
eight: 'thing',
ninth: 'thing'
},
]
const makeModuleAndBatteryPromises = () => {
arrayOfObjects.map((obj) => {
createModules(obj.first).then((response) => {
createBattery(response, obj.second).then(res => {
assignAssets(res, obj.third).then(res => 'assignment done!');
});
});
});
}
makeModuleAndBatteryPromises();
So looking at above code, it seems like i truly only have control over the first 3 API Calls in the 1st loop of arrayOfObjects. As i have assignAssets(res).then(res => 'assignment done!'); which will allow me to do some operation like refresh page or redirect once the 3rd promise is resolved in the first loop.
However i need to do some operation on the 9th/final promise. Here is what i tried trying to make it async await.
const makeModuleAndBatteryPromises = async () => {
const promises = arrayOfObjects.map(async obj => {
const firstResponse = await createModules(obj.first);
const secondResponse = await createModules(firstResponse, obj.second);
await assignAssets(secondResponse);
});
await Promise.all(promises)
.then(res => //do some action after all 9 promises resolved)
}
makeModuleAndBatteryPromises()
Not quite achieving what i was expecting it to, can some1 please tell me what i'm missing?
if I understand correctly, you want the resolved value of the final assignAssets?
You've possibly confused yourself with a mix of async/await and .then
const makeModuleAndBatteryPromises = async() => {
const promises = arrayOfObjects.map(async obj => {
const firstResponse = await createModules(obj.first);
const secondResponse = await createModules(firstResponse, obj.second);
return assignAssets(secondResponse);
});
const res = await Promise.all(promises);
const finalValue = res.at(-1); // or res[res.length-1];
// do things with finalValue
}
makeModuleAndBatteryPromises()

Promises being executed when they shouldn't be

I have several db mutations that I would like to execute all at once, instead of synchronously. The problem that I'm running into, is that when I try to push these promises into an array, they execute.
What am I doing wrong here? I've also tried pushing anonymous functions, like this,
promises.push(
async () => await someDbMutation1({ someForeignKey: "10" }),
)
but they aren't execute during Promise.all.
import * as React from "react";
import "./styles.css";
const someDbMutation1 = async ({ someForeignKey }) => {
return await new Promise((resolve) => {
console.log("should not enter");
return setTimeout(() => {
resolve("aa");
}, 2000);
});
};
const someDbMutation2 = async ({ someParameter }) =>
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 2000)
);
export default function App() {
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => {
init();
}, []);
const init = React.useCallback(async () => {
const promises = [
someDbMutation1({ someForeignKey: "10" }),
someDbMutation2({ someParameter: "abc" })
];
// await Promise.all(promises);
setLoaded(true);
}, []);
return <div className="App">{loaded && <div>done</div>}</div>;
}
I would expect these promises in the promises array to be executed during a call to Promise.all, but clearly that's not the case here. I've noticed this only recently, when I passed null as a value to a foreign key, at which point the key constraint in my db picked it up and threw an error.
Now I'm worried, because I frequently use a promises array and loop over db objects and push mutation queries into promises -- this means, that each request is executed twice! I'm not sure what I'm missing here.
For the first part of your question where you say that it's not executing:
promises.push(
async () => await someDbMutation1({ someForeignKey: "10" }),
)
it's because you are pushing an anonymous function not a promise - They are two different things. Based on your array name, I think expected behavior would be for you to do this instead:
promises.push(
someDbMutation1({ someForeignKey: "10" })
)
If you want all promises to be executed at a single point in time then you could do this instead:
queries.push(
async () => await someDbMutation1({ someForeignKey: "10" }),
)
/ ** -- some point later -- ** /
const promises = queries.map(q => q()) // Execute queries
const results = await Promise.all(promises) // Wait for queries to finish
In addition, you have a misunderstanding on how Promise.all works here:
I would expect these promises in the promises array to be executed during a call to Promise.all
Promise.all doesn't execute the promises, it waits for the promises to resolve. There is a reference here.
So in this part:
const promises = [
someDbMutation1({ someForeignKey: "10" }),
someDbMutation2({ someParameter: "abc" })
];
You are actually executing the functions so that if you were to console.log the promises array it would look something like this:
[
Promise (unresolved),
Promise (unresolved)
];
And then after await Promise.all(), the promises array would look like this:
[
Promise (resolved: value),
Promise (resolved: value)
];
Issue 1: Promises must be awaited in the block that actually awaits for them.
const someDbMutation1 = async ({ someForeignKey }) => {
return new Promise((resolve) => {
console.log("should not enter");
return setTimeout(() => {
resolve("aa");
}, 2000);
});
};
const someDbMutation2 = async ({ someParameter }) =>
await new Promise((resolve) =>
setTimeout(() => {
resolve();
}, 2000)
);
The problem is you are executing the promises. You should instead add them into an array as anonymous functions that call your function with the parameters you want. So this should look like this. :
const init = React.useCallback(async () => {
const promises = [
async () => someDbMutation1({ someForeignKey: "10" }),
async () => someDbMutation2({ someParameter: "abc" })
];
await Promise.all(promises);
setLoaded(true);
}, []);
I hope this is the answer you are looking for.

Promise Map keeps returning null

Been on this for over 24 hours, everything seems to work as I want but the promise keeps returning null.
[
null,
null
]
here are my codes:
let vettedBatch = currentBatch.Items.map((current) => {
getUser(current.userId).then((res) => {
// return resolve(JSON.parse(res.body));
let body = JSON.parse(res.body);
if (body.hasOwnProperty("Item")) {
return body;
} else {
//if user does not exist on the users table based on ID, lets take his transaction out of the fail-safe table
console.log(
`user with id number ${current.userId} with transaction id ${current.txId} do not exist or must have been disabled`
);
User.deleteWithdrawalTx(current.txId).then(() => {
console.log(
`transaction id ${current.txId} delete for unknown user with userId ${current.userId}`
);
});
}
});
});
You need to use Promise.all:
const data = [];
const promises = currentBatch.Items.map(async current => {
return await getUser(current.userId)
});
Promise.all(promises)
.then(res => {
res.map(item => {
let { body } = JSON.parse(item);
if (body.hasOwnProperty("Item")) {
data.push(body);
} else {
console.log('message');
}
})
})
Instead of mixing async/await and Promise syntax, I would suggest you to stick to one.
Here would be your code written fully in async/await syntax:
const getData = async () => { // async function instead of new Promise()...
return Promise.all(currentBatch.Items.map(async (current) => { // make map async and await it with Promise.all()
const res = await getUser(current.userId); // await instead of .then()
let body = JSON.parse(res.body);
if (body.hasOwnProperty("Item")) {
return body;
} else {
console.log(`user with id number ${current.userId} with transaction id ${current.txId} do not exist or must have been disabled`);
await User.deleteWithdrawalTx(current.txId); // await instead of .then()
console.log(`transaction id ${current.txId} delete for unknown user with userId ${current.userId}`);
// You should return something here too, but I dont know what you want to return, so...
}
}));
}
let vettedBatch = await getData(); // await the async function
Your problem is actually a deviation of this question: How do I return the response from an asynchronous call?. Should be fixed in my answer, but I still suggest you to check out the linked thread.

Categories

Resources