问题
I have a Dataset Structure like this:
_id: SomeMongoID,
value: "a",
counter: 1
}
So initially my database table is empty.
Now I have an array where value is like:
const array = ["a", "a", "a"]
What I want is initially the first time I do the search, so it will empty result so at that case in insert the query, now next time it get the entry so simply increment the counter.
For that, I wrote the code:
const testFunction = async(array) => {
try {
await Promise.all(
array.map(async x => {
const data = await CollectionName.findOne({value: x}).exec();
// Every time data will return null
if (data) {
//In this case only counter will have to increase
// But this block not run
} else {
//So by this first value will store
const value = new Value({
value: x,
counter: 1
});
await value.save()
}
})
)
} catch (error) {
console.log(error)
}
}
const array = ["a", "a", "a"]
testFunction(array);
Problem is it will create the 3 entry not single. map function will not wait, I checked through manual debugging using console.log(). Any help or suggestion is really appreciated.
回答1:
You don't have to use map
in here. You can just use a for ... of
iteration which will await the results.
const testFunction = async(array) => {
try {
for (const x of array) {
const data = await CollectionName.findOne({value: x}).exec();
// Every time data will return null
if (data) {
//In this case only counter will have to increase
// But this block not run
} else {
//So by this first value will store
const value = new Value({
value: x,
counter: 1
});
await value.save()
}
}
} catch (error) {
console.log(error)
}
}
const array = ["a", "a", "a"]
testFunction(array);
Explanation: map
does not await even if the pass function is async
. Instead it collects the return values of the function you pass into it. async
functions always return Promise
. Then the Promise.all
awaits all of them. But the iteration through the data will not await anything. It will return promise as soon as it evaluates the right side of the first await
keyword. But for ... of
works because it does not use a callback function. Instead it evaluates each loop and correctly awaits where you want it to do so. The execution is then very close as if all the code inside was synchronous.
回答2:
To save time and load data in parallel, first process the values we have ourselves. We create a data structure that has already counted the same values before even making a single call to the database. Then we only call the database for a unique key in our data structure. This reduces the number of calls in your example from 3 to 1. I my example i added two "b"
values to the test data. So the number of calls will be 2 instead of 5.
The database is then queried for the unique keys. If an entry is found, the counter is increased by the number of value
occurrences inside the test array. If it is not found a new entry is made with the counter set to the number of found occurences.
const testFunction = async (array) => {
try {
// Create a statistics by counting each value fist
// Creates a structure like { a: 3, b: 2}
const countsByValues = array.reduce((acc, value) => {
const newCount = acc[value] ? acc[value] + 1 : 1;
return {
...acc,
value: newCount
};
}, {});
await Promise.all(
// Use object entries to get array of [["a", 3], ["b", 2]]
Object.entries(countsByValues).map(async ([x, count]) => {
const data = await CollectionName.findOne({value: x}).exec();
if (data) {
//In this case only counter will have to increase by the count
data.counter += count;
//save data - insert your code saving code here
await data.save();
} else {
//So by this first value will store
const value = new Value({
value: x,
// new values will be the total count
counter: count
});
await value.save()
}
})
)
} catch (error) {
console.log(error)
}
}
const array = ["a", "a", "b", "b", "a"]
testFunction(array);
来源:https://stackoverflow.com/questions/58504988/async-await-not-work-properly-inside-map-function