Not able to set item with the key in the final object - javascript

The value of videoProgress is not returned in the final object document, i want videoProgress to be returned along with the documentList item, but i am not getting it, i have set item.videoProgress too but not getting it in response:
const document = await Promise.all(documentList.map(async (item) => {
const videoCount = await studentProgressModel.find({ gradeId: item.gradeId, userId : item._id, type: 'VIDEO'}).countDocuments();
const totalVideoCount = await videoModel.find({ gradeId: item.gradeId, mediumId: item.mediumId, status: 'ACTIVE' }).countDocuments();
let videoPercentage = 0;
if(totalVideoCount > 0) {
videoPercentage = (100 * videoCount) / totalVideoCount;
}
item.videoProgress = videoPercentage;
console.log('item', item);
return item;
}));

The issue here is not what anyone in the comments above is discussing, the issue is that you are not actually returning a promise from your documentList.map, but instead are just writing an async function there. Array.map will not wait for any asynchronous behavior, so as soon as it hits an await it returns an empty result.
This can be fixed by instead returning promises in your map:
const document = await Promise.all(documentList.map((item) => { // Removed async here, map is synchronous
// Here we *immediately* return a promise so that map can return that to the promise.all
return new Promise(async (resolve) => {
// Now we can do all our async stuff and promise.all will wait for it :)
const videoCount = await studentProgressModel.find({ gradeId: item.gradeId, userId : item._id, type: 'VIDEO'}).countDocuments();
const totalVideoCount = await videoModel.find({ gradeId: item.gradeId, mediumId: item.mediumId, status: 'ACTIVE' }).countDocuments();
let videoPercentage = 0;
if(totalVideoCount > 0) {
videoPercentage = (100 * videoCount) / totalVideoCount;
}
item.videoProgress = videoPercentage;
console.log('item', item);
return resolve(item); // have to use resolve here with our result to resolve the promise.
});
}));
There are other ways to do this that don't involve an explicit new-promise wrapper, but I figured it made the promise behavior more obvious
Edit: Here's an example which can be run directly here in your browser, I have only replaced the queries with async random number generators.
function randomInteger() {
return new Promise((resolve) => {
setTimeout(() => {
return resolve(Math.floor(Math.random() * 100));
}, 1);
});
}
async function main() {
let documentList = [{}, {}, {}];
const document = await Promise.all(documentList.map((item) => {
return new Promise(async (resolve) => {
const videoCount = await randomInteger();
const totalVideoCount = await randomInteger();
let videoPercentage = 0;
if(totalVideoCount > 0) {
videoPercentage = (100 * videoCount) / totalVideoCount;
}
item.videoProgress = videoPercentage;
console.log('item', item);
return resolve(item);
});
}));
console.log('Document results:', document);
}
main();

Related

Promise.all() to await the return of an object property

Inside an async function i have a loop and inside this loop i need to use await to resolve a promise from another async function.
async function smallestCities(states) {
const citiesInState = [];
for (const state of states) {
const length = await lengthOfState(state.Sigla);
const stateObject = {
state: state.Sigla,
cities: length,
};
citiesInState.push(stateObject);
}
citiesInState.sort((a, b) => {
if (a.cities > b.cities) return 1;
if (a.cities < b.cities) return -1;
return 0;
});
return citiesInState.filter((_, index) => index < 5).reverse();
}
It's work fine, but eslint says to disallow await inside of loops and use Promise.all() to resolve all promises.
The problem is that my promises are in an object property:
How can i figure out to use Promise.all() with properties of an object?
Chain a .then onto the lengthOfState call to make the whole Promise resolve to the object you need, inside the Promise.all:
const citiesInState = await Promise.all(
states.map(
state => lengthOfState(state.Sigla).then(cities => ({ state: state.Sigla, cities }))
)
);
const NEW_LAND = 'newLand'
const ACTUAL_FINLAND = 'actualFinland'
const PIRKKAS_LAND = 'pirkkasLand'
const STATE_CITY_MAP = {
[NEW_LAND]: ['HELSINKI', 'VANTAA', 'KORSO'],
[ACTUAL_FINLAND]: ['TURKU'],
[PIRKKAS_LAND]: ['WHITE RAPIDS', 'NOKIA'],
}
const mockGetCities = (stateName) => new Promise((res) => {
setTimeout(() => { res([stateName, STATE_CITY_MAP[stateName]]) }, 0)
})
const compareStatesByCityQty = (a, b) => {
if (a[1].length > b[1].length) return 1
if (a[1].length < b[1].length) return -1
return 0
}
const getSmallestStates = async (stateNames, cityQty) => {
const cities = await Promise.all(stateNames.map(mockGetCities))
return cities
.sort(compareStatesByCityQty)
.reverse()
.slice(0, cityQty)
}
;(async () => {
const stateNames = [NEW_LAND, ACTUAL_FINLAND, PIRKKAS_LAND]
const smallestStates = await getSmallestStates(stateNames, 2)
console.log(smallestStates)
})()

React script stop working after changing API call

I have a script which calls API from React and then triggers email notification function.
I was changing one part of it to call whole array of parameters instead of calling one parameter after another.
Here is part before change(working one). Console log shows correct response and I receive email notification as well.
const getApiData = () => {
const apiCall = (symbol) => {
return `https://min-api.cryptocompare.com/data/pricemulti?fsyms=${symbol}&tsyms=USD&api_key=API-KEY-HERE`
}
const MAX_CHARACKTERS = 300
let bucketArray = ['']
for (let i=0; i < assets.length - 1; i += 1) {
const symbol = `${bucketArray[bucketArray.length - 1]},${assets[i]}`
if (i === 0) {
bucketArray[0] = assets[i]
continue
}
if (symbol.length < MAX_CHARACKTERS) {
bucketArray[bucketArray.length - 1] = symbol
} else {
bucketArray[bucketArray.length] = assets[i]
}
}
const getData = () => {
Promise.all(
bucketArray.map(req => {
return axios(apiCall(req))
.then(({ data }) => data)
})
).then((data) => setDataApi(data))
}
getData()
};
Here is problematic one.
const getApiData = () => {
const getString = symbol =>
`https://min-api.cryptocompare.com/data/pricemulti?fsyms=${symbol}&tsyms=USD&api_key=API-KEY-HERE`;
function getAxious(id) {
const url = getString(id);
return axios.get(url);
}
const BUCKET_SIZE = 150;
const bucketArray = assets.reduce(
(arr, rec) => {
if (arr[arr.length - 1].length < BUCKET_SIZE) {
arr[arr.length - 1] = [...arr[arr.length - 1], rec];
return arr;
}
return [...arr, [rec]];
},
[[]]
);
bucketArray
.reduce((acc, rec) => {
return acc.then(results => {
return Promise.all(
rec.map(item =>
getAxious(item).then(({ data }) => {
return {
Symbol: item,
Open: data
};
})
)
).then(x => {
return [...x, ...results];
});
});
},
Promise.resolve([]))
.then(res => {
setDataApi(res);
});
};
Here in console I receive empty array - [] no errors showed, but email notification also stops from working.
I'm changing the code since I need to call whole array from API in one call. Before I was calling one symbol after another.
What I did wrong that console doesn't show the correct response?
EDIT1
Here is bucketArray value
const assets = ['ADA','KAVA','DOGE'];
I was not able to understand completely, but I think you want to collect all the results together and set it to the data using setDataApi.
Check the below code and let me know if it helps:
async function getApiData() {
const getString = (arr) =>
`https://min-api.cryptocompare.com/data/pricemulti?fsyms=${arr.join(
","
)}&tsyms=USD&api_key=API_KEY`;
function getAxious(arr) {
const url = getString(arr);
return axios.get(url);
}
const BUCKET_SIZE = 150;
const bucketArray = assets.reduce(
(arr, rec) => {
if (arr[arr.length - 1].length < BUCKET_SIZE) {
arr[arr.length - 1] = [...arr[arr.length - 1], rec];
return arr;
}
return [...arr, [rec]];
},
[[]]
);
const res = await getAxious(bucketArray);
console.log("res", res);
return res;
// after this you can set setDataApi(res);
}
// keep this useEffect sepearate
const [timer, setTimer] = useState(null);
useEffect(() => {
async function getApiDatahandler() {
const res = await getApiData();
console.log(res);
const timerId = setTimeout(() => {
getApiDatahandler();
}, 1000 * 60);
setTimer(timerId);
setDataApi(res)
// set the data setDataApi(res);
}
getApiDatahandler();
return () => {
window.clearTimeout(timer);
};
}, []);
// useEffect(() => {
// const timerId = setTimeout(() => {
// getApiData();
// }, 1000 * 60);
// }, [])
Checkout this codepen for a possible solution.
https://codepen.io/bcaure/pen/RwapqZW?editors=1011
In short, I don't know how to fix your code because it's quite a callback hell.
// Mock API and data
const bucketArray = [[{item: 'item1'}], [{item: 'item2'}], [{item: 'item3'}]];
const getAxious = item => {
return new Promise((resolve, reject) => resolve({data: 'API data'}));
}
// Return promise that combines API data + input item
const recToPromise = rec => rec.map(item => {
return new Promise((resolve, reject) => getAxious(item)
.then(data => resolve({item, data})));
});
// Flatten array
const recPromisesFlatten = bucketArray.flatMap(recToPromise);
Promise.all(recPromisesFlatten)
.then(res => {
const flattenRes = res.flatMap(({item, data}) => ({ Symbol: item, Open: data }));
console.log(JSON.Stringify(flattenRes))
});
What I'm suggesting to debug errors:
build your promise array first
then run Promise.all
then combine your data
Bonus: you can see flatMap instead of reduce for better readability.

How to make async/wait inside of for loop?

I'm using async await inside of for loop as below.
for (let i = 0; i < result.length; i += 1) {
try{
const client = await axios.get(
`${process.env.user}/client/${result[i].id}`
);
} catch(error){
console.log(error)
}
if (client.data.success === true) {
result[i].Name = rider.data.client.Name;
result[i].PhoneNumber = rider.data.client.Number;
}
}
But I want to make this using 'new Promise' and 'promiss.all' to make it asynclously.
But I don'k know how to make this correctly doing error handle well.
Could you recommend some advice for this? Thank you for reading it.
This can be a basic solution, i think
let playList = []
for (let i = 0; i < result.length; i += 1) {
playList.push(axios.get(
`${process.env.user}/client/${result[i].id}`
).then(res => {
if (res.data.success === true) {
result[i].Name = rider.data.client.Name;
result[i].PhoneNumber = rider.data.client.Number;
}
}).catch(ex => console.log(ex)));
}
await Promise.all(playList)
This can also be done by using a foreach loop.
The for/foreach loop can be simplified by using a map function.
Js map function is equivalent of c# select linq function.
The fat arrow in js map function is not bound to return a value unlike c# select inner function which must return a value.
await Promise.all(result.map(async r => {
let client;
try {
client = await axios.get(`${process.env.user}/client/${r.id}`);
} catch (error) {
console.log(error)
}
if (client.data.success === true) {
r.Name = rider.data.client.Name;
r.PhoneNumber = rider.data.client.Number;
}
}));
Try this
var promises = result.map(r => axios.get(`${process.env.user}/client/${r.id}`);
Promise.all(promises).then(function(values) {
console.log('All promises done');
});
The idea is that if you are awaiting something, that is promise, you can await it, or call it to get promise
Example:
function Foo()
{
return new Promise(...); // Promise of int for example
}
you can do
var p = Foo(); //you will get promise
Or
var v = await Foo(); // you will get int value when promise resolved
This is how you do it with async/await + Promise.all:
const myResult = await Promise.all(result.map(({ id }) => {
return axios.get(`${process.env.user}/client/${id}`);
}));
// deal with the result of those requests
const parsed = myResult.map(data => /* your code here */);
Here is an example using Array.map to call your function along with Promise.all. I wrapped the axios request in a function so if one of your request fails, it wont stop every other requests. If you don't mind stopping when you got an issue, look at others answers to your question.
function fakeRequest() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: {
success: true,
client: {
Name: 'Todd',
Number: 5,
},
},
});
}, 300);
});
}
(async() => {
const result = [{}, {}, {}];
await Promise.all(result.map(async(x, xi) => {
try {
const client = await fakeRequest();
if (client.data.success === true) {
result[xi].Name = client.data.client.Name;
result[xi].PhoneNumber = client.data.client.Number;
}
} catch (err) {
console.log(err)
}
}));
console.log(result);
})();

React Native .then seems to be running out of order

I currently have the following method in a react native class, however I think this would apply to JS in general but I might be wrong:
dbGetTemplateOptions = () => {
let dataArray = [];
let subcategories = this.state.subcategories;
subcategories.forEach(item => {
let getSubValues = new Promise(function(resolve, reject) {
resolve(item);
})
getSubValues.then((item) => this.dbGetValues(item.subCatId))
getSubValues.then((value) => console.log(2))
});
}
According to my limited js knowledge of promises, in the above I'm resolving a promise and running getSubValues.then() which would mean that each of the .then methods run AFTER the method returns.
In the above code I call the method dbGetValues(item.subCatId)
Which is this:
async dbGetValues(subCatId) {
let subValues = [];
let result = await db.transaction(tx => {
tx.executeSql(
'SELECT * FROM dr_template_relational '
+ ' INNER JOIN dr_report_categorie_values on dr_report_categorie_values.id = dr_template_relational.value_id'
+ ' WHERE dr_template_relational.subcategory_id = ' + subCatId + ' AND dr_template_relational.template_id = ' + this.state.currentTemplateId,
[],
(trans, result) => {
const sqLiteResults = result.rows._array;
sqLiteResults.forEach(el => {
subValues.push({ subCategoryId: subCatId, values: el.value_id, name: el.name, narrative: el.narrative });
})
});
},
(err) => console.error(err),
() => {
console.log(1);
return subValues;
}
);
return result;
}
Notice my console.log(2) is after the then which is calling the method.
Inside the method also notice I have console.log(1). I would expect these to run in order since I'm waiting for it to finish before the next then runs. I realize I'm incorrect because the console.log is actually.
2
2
1
1
dbGetTemplateOptions = () => {
let dataArray = [];
let subcategories = this.state.subcategories;
subcategories.forEach(item => {
let getSubValues = new Promise(function(resolve, reject) {
resolve(item);
})
getSubValues.then((item) => this.dbGetValues(item.subCatId))
getSubValues.then((value) => console.log(2))
});
}
You're resolving the promise before actually calling the asynchronous dbGetValues function. This is why the then triggers before the callbacks of dbGetValues do.
It's hard to know what changes to make without more context of what you're tying to do, but I think you might actually want something like:
dbGetTemplateOptions = () => {
let dataArray = [];
let subcategories = this.state.subcategories;
subcategories.forEach(item => {
let getSubValues = new Promise(async (resolve, reject) => {
const result = await this.dbGetValues(item.subCatId)
resolve(result);
})
getSubValues.then((value) => console.log(2))
});
}
Or to try to simplify even more:
dbGetTemplateOptions = () => {
let dataArray = [];
let subcategories = this.state.subcategories;
subcategories.forEach(async (item) => {
const result = await this.dbGetValues(item.subCatId)
// Do something with result here
console.log(2)
});
}
Obviously this is based on assumptions of what you're doing

How to resolve a promise in an async function?

I have a lifecycle method componentDidMount that calls upon a recursive async method and I want the recursive function to return a promise after all the data has been fetched.
async componentDidMount() {
let response = await fetch(`${STORY_URL}${this.props.match.params.id}.json`);
let result = await response.json();
totalComments = result.descendants;
await this.fetchComments(result.kids, MARGIN);
this.setState({
by: result.by,
time: calculateTimeDifference(result.time)
});
}
and the function being called is
async fetchComments(comment, margin) {
return new Promise((resolve, reject) => {
comment.map(async commentId => {
let response = await fetch(`${STORY_URL}${commentId}.json`);
let result = await response.json();
comments.push({
by: result.by,
margin: margin
});
if (comments.length === totalComments + 1) resolve();
if (result.kids !== undefined) this.fetchComments(result.kids, margin * 2);
});
});
}
but the resolve method is not returning back to the componentDidMount before the setState. I checked by console logging. I don't know what I am doing wrong
You are overcomplicating things. Use Promise.all:
async fetchComments(comments, margin) {
// Collect all the results here, otgerwise we would have to flatMap the promises which is more complicated
const result = [];
// Make sure that all comments were processed before returning
await Promise.all( comments.map(async commentId => {
const response = await fetch(`${STORY_URL}${commentId}.json`);
const { kids, by } = await response.json();
if(kids) {
// Get all children and append them to the results, right after the children
const children = await fetchComments(kids, margin * 2);
result.push({ by, margin }, ...children);
} else {
// Otherwise just append this node
result.push({ by, margin });
}
}));
return result;
}
If the order matters, you have to flatten the result of Promise.all:
async fetchComments(comments, margin) {
const result = await Promise.all(comments.map( async commentID => {
const response = await fetch(STORY_URL + commentID + ".json");
const { by, kids } = await response.json();
const result = [{ by, margin }];
if(kids) result.push(... await fetchComments(kids, margin * 2));
return result;
}));
// Flatten the results
return [].concat(...result);
}

Categories