JavaScript: Returning array from recursive function - javascript

I have made a class which builds some data from api:
const http = require("http");
class VideoService {
constructor() {
this.items = [];
}
fetchVideos(token = "") {
const url = `https://www.example.com`;
http.getJSON(url).then((results) => {
results.items.forEach((item, index) => {
const vid = item.snippet.resourceId.videoId;
this.items.push({
title: item.title,
date: item.publishedAt
});
console.log(this.items.length); // here length inreases, works here
});
if (typeof results.nextPageToken !== "undefined") {
return this.fetchVideos(results.nextPageToken);
}
});
}
getVideos() {
this.fetchVideos();
console.log(this.items.length); // this returns 0 instead of all items fetched
return this.items;
}
}
module.exports = VideoService;
In another file, I am using it like this:
const videoService = require("../shared/videoService");
const videos = (new videoService()).getVideos();
console.log(videos);
The last console.log call always returns empty array instead of all data collected in items property of the above class.
Can anybody tell what I am missing here?

This happens because in your function fetchVideos(), you are making an http call which will be processed asynchronously. You can try to process it this way.
fetchVideos(token = "") {
const url = `https://www.example.com`;
return http.getJSON(url).then((results) => {
results.items.forEach((item, index) => {
const vid = item.snippet.resourceId.videoId;
this.items.push({
title: item.title,
date: item.publishedAt
});
console.log(this.items.length); // here length inreases, works here
});
if (typeof results.nextPageToken !== "undefined") {
return this.fetchVideos(results.nextPageToken);
}
else return new Promise((resolve, reject)=>{
resolve();
});
});
}
getVideos() {
return this.fetchVideos().then(function(){
console.log(this.items.length); // this returns 0 instead of all items fetched
return this.items;
});
}
I suggest reading about promises and asynchronicity in javascript. Check this link:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Related

How can I retry a function with try/catch in react js?

I have a function with try catch inside. I call to the API about response. This response returns me a Result object with some data. But sometimes I'd like to retry the function if I have no name and have bundle_id.
I'd like to retry this functions max 3 times. If no result, I'd like to throw an error.
I have sth like this, but it doesn't work for me.
const getECommerceProduct = async ({
item,
getParent = false,
variantInfo,
}) => {
if (!item.id) return null;
let retryCounter = 0;
try {
const response = await getEvaService(Core.GetProductDetail, {
ID: item.id,
});
const itemResponse = response?.Result;
if (!itemResponse) return null;
// Retry the function if the item has no name and has a bundle product.
// The bundle product will give back more information about the product.
const retry = !!itemResponse.bundle_id && !itemResponse.product_name;
if (retry && !getParent && retryCounter <= 3) {
retryCounter += 1;
return getECommerceProduct({
item: {
id: itemResponse.bundle_id,
quantity: item.quantity,
variantInfo,
},
getParent: true,
});
}
return transformProductToECommerce(itemResponse, item);
} catch (e) {
console.error(e);
return null;
}
};
Could you help me?
You could use a state to track the attempt count.
[retry_count, setRetryCount] = useState(0);
useEffect(() => {
if (retry_count < 3) {
// .... put fetch code here
}
}, [retry_count])
Increment the retry_count every time by putting it in the catch block within your fetch function
React.useEffect(() => {
const getECommerceProduct = async ({ item, getParent = false, variantInfo }) => {
if (!item.id) return null;
const response = await getEvaService(Core.GetProductDetail, {
ID: item.id,
});
const itemResponse = response?.Result;
if (!itemResponse) return null;
return transformProductToECommerce(itemResponse, item);
};
if (retry_count < 3) {
getECommerceProduct().catch((e) => {
console.error(e);
setRetryCount(retry_count + 1);
return null;
});
}
}, [retry_count]);
With the useEffect, this means that every time the retry_count is incremented (in this case an error happens), the function within the useEffect call will be performed.

Promise.all does not await for then

First of all I am a newbie in nodejs, please be indulgent :)
I have a problem with some async functions. In my case, a first API call is made and then a sub call is made on every item returned. this is my code after the first call is made :
const countryStatesCalls: Promise<any>[] = [];
countries.forEach((country, index) => {
countries[index] = convertToCountry(country);
const countryStatesCall = (countryId: number) => {
return new Promise((resolve, reject) => {
const countryStatesList = getStateCountries(countryId);
if (countryStatesList) {
if (Object.prototype.toString.call(countryStatesList) !== '[object Array]') {
resolve([]);
} else {
resolve(countryStatesList);
}
} else {
reject([]);
}
});
};
countryStatesCalls.push(
countryStatesCall(countries[index].id).then(countryStates => {
countries[index].states = countryStates;
}).catch(countryStatesType => {
countries[index].states = countryStatesType;
})
);
});
await Promise.all(countryStatesCalls).then(_ => {
res.json(countries)
});
the following code
res.json(countries)
is supposed to be executed after all promises execution has been finished.
EDIT : the getStateCountries async function :
const getStateCountries = async (countryId: number, lang?: string) => {
const listState = await odoo.searchRead(
'res.country.state',
{
filters: [
{
field: 'country_id',
operator: '=',
value: countryId,
},
],
fields: fieldState,
},
{ lang }
);
if (!listState.length) {
return [];
} else {
listState.forEach((state, index) => {
listState[index] = convertToState(state);
});
return listState;
}
};
the main function is an express controller
I think my mistake may be obvious but can you help me please, I read many topics and I cannot see what am I doing wrong.
Thank you in advance
[SOLUTION]
As pointed out by comments, I was doing it the wrong way around, my call on promise called another promise: the solution was to write :
const countryStatesCalls: Promise<any>[] = [];
for (let index = 0; index < countries.length; index++) {
countries[index] = convertToCountry(countries[index]);
if (withSates) {
countryStatesCalls.push(
getStateCountries(countries[index].id)
);
} else {
countries[index].states = [];
}
}
if (withSates) {
await Promise.all(countryStatesCalls).then((countriesStates) => {
countries.forEach((country, index) => {
country.states = [];
for (const countryStates of countriesStates) {
if (countryStates.length
&& countryStates[0].country_id.code === country.id) {
countries[index].states = countryStates;
}
}
});
res.json(countries);
});
} else {
res.json(countries);
}
thank you everyone
I can't exactly tell why it doesn't work, but this looks a bit like the Promise constructor antipattern. The getStateCountries function is async and already creates a promise, so you need to await it or chain .then() to it. The new Promise is not necessary.
Also I'd recommend to avoid forEach+push, rather just use map, and get rid of the countryStatesCall helper:
const countryStatesCalls: Promise<any>[] = countries.map((country, index) => {
countries[index] = convertToCountry(country);
return getStateCountries(countries[index].id).then(countryStatesList => {
if (countryStatesList) {
if (Object.prototype.toString.call(countryStatesList) !== '[object Array]') {
return [];
} else {
return countryStatesList;
}
} else {
throw new Error("getStateCountries() resolved to nothing");
}
}).then(countryStates => {
countries[index].states = countryStates;
});
});
await Promise.all(countryStatesCalls).then(_ => {
res.json(countries)
});

React typescript Promise with webservice and depending another function in loop

On button click I am looping through record id's pass that 1 by 1 to webservice which will return XML data where in I will get ID of another record which I will pass to another webservice which will return result success. After finish this I want to show message success
Function defination
const GetRunningWorkflowTasksForCurrentUserForListItemRequest = (
absoluteUrl: string,
itemId: number,
listName: string,
callback
) => {
const soapURL = `${absoluteUrl}/example.asmx?op=GetListItem`
const soapRequest = `SOAP Request`
getWFData(soapURL, soapRequest, callback) // Get XML with call back function parameter where we will process data
}
Second Function Call
const getWFData = (soapURL: string, soapRequest: string, callback) => {
const xmlhttp = new XMLHttpRequest()
xmlhttp.open("POST", soapURL, true)
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState === 4) {
if (xmlhttp.status === 200) {
callback(xmlhttp.responseText)
}
}
}
xmlhttp.setRequestHeader("Content-Type", "text/xml")
xmlhttp.send(soapRequest)
}
First Function Call with loop
const approveSelected = (ids: number[]) => {
ids.forEach((val, idx) => {
const absoluteUrl = props.context.pageContext.web.absoluteUrl
// First function
GetRunningWorkflowTasksForCurrentUserForListItemRequest(
absoluteUrl,
val,
"Temp",
GetRunningWorkflowTasksForCurrentUserForListItemResponse //XML Response
)
})
}
Third Function where we got XML response
const GetRunningWorkflowTasksForCurrentUserForListItemResponse = (response: any) => {
const parser = require("fast-xml-parser")
const absoluteUrl = props.context.pageContext.web.absoluteUrl
if (parser.validate(response) === true) {
const jSONObj = parser.parse(response)
const spTaskId =
jSONObj["soap:Envelope"]["soap:Body"].GetRunningWorkflowTasksForCurrentUserForListItemResponse
.GetRunningWorkflowTasksForCurrentUserForListItemResult.UserTask.SharePointTaskId
processFlexiTaskRequest2(
absoluteUrl,
"Approve",
spTaskId,
"Workflow Tasks",
processFlexiTaskResponse2Response, //XML Response function
""
)
}
}
Forth and Final call for inside loop
const processFlexiTaskResponse2Response = (response: any) => {
const parser = require("fast-xml-parser")
if (parser.validate(response) === true) {
const jSONObj = parser.parse(response)
const result =
jSONObj["soap:Envelope"]["soap:Body"].ProcessFlexiTaskResponse2Response.ProcessFlexiTaskResponse2Result
}
}
I am really confuse, How can I make chain with promise and show confirm once loop finish. Please help
Two key steps are required to make this work with promises:
Convert each id to a promise that either resolves to the respective response or alternatively to the result of processFlexiTaskResponse2Response
Use Promise.all() to combine all these promises to one promise that resolves when all of the per-id promises are resolved.
This is the most relevant part
const approveSelected = (ids: number[]) => {
const promises = ids.map((val, idx) => {
const absoluteUrl = props.context.pageContext.web.absoluteUrl
// First function
return GetRunningWorkflowTasksForCurrentUserForListItemRequest(
absoluteUrl,
val,
"Temp"
).then(response => {
// transform response via
// GetRunningWorkflowTasksForCurrentUserForListItemResponse
// and
// processFlexiTaskResponse2Response
});
});
Promise.all(promises).then(results => {
// the code here executes once all results are there.
});
}
You have to change GetRunningWorkflowTasksForCurrentUserForListItemResponse and
processFlexiTaskResponse2Response to simply return their respective result so you can chain them.
To make GetRunningWorkflowTasksForCurrentUserForListItemRequest return a promise you can change it like this:
const GetRunningWorkflowTasksForCurrentUserForListItemRequest = (
absoluteUrl: string,
itemId: number,
listName: string,
callback
) => new Promise((resolve, reject) => {
const soapURL = `${absoluteUrl}/example.asmx?op=GetListItem`
const soapRequest = `SOAP Request`
getWFData(soapURL, soapRequest, resolve);
});

loop synchronously through array while calling asynchronous function

My problem: i have an array with users and an user_hash and need to find the matching user. I came up with the following solution:
//results is the array user objects
//safety_first.getUserHash is a function that calculates a user_hash, it retrieves the user-record based on the id and then combines values of some fields and a key and turns this into a hash.
if (results.length > 0)
{
var i = 0;
function checkUserhash(user_id, user_hash, callback) {
safety_first.getUserHash(user_id, function(check_user_hash) {
if (user_hash == check_user_hash)
{
callback(user_id);
}
else
{
if ((i+1) < results.length)
{
i++;
checkUserhash(results[i].id, user_hash, callback);
}
else
{
callback(false);
}
}
});
}
checkUserhash(results[i].id, user_hash, function(user_id) {
if (user_id)
{
console.log("MATCH: "+user_id);
}
else
{
console.log("NO MATCH");
}
});
}
I first tried to do this in a for-loop but cause it calls the checkUserhash asychronously i could not break the loop when the match was found.
I'm looking for other possible solutions, please share your thoughts.
regards, Pieter
You can map over your user array to create an array of promises. Use Promise.all to wait for those promises to resolve and then iterate over the responses to check whether the hash matches the id.
In this example I've used async/await. I've also mocked up a hashing function routine so you can see it in action. Simply change the resolve from 'id' to 'id + 1' and rerun the demo to see the Match become No match.
Hope this is some use.
const safety_first = {
getUserHash(id) {
return new Promise(resolve => {
setTimeout(() => resolve(id), 1000);
});
}
}
async function checkUserHashes(userList, promises) {
try {
// await the promises to all resolve
// `Promise.all` preserves the order...
const res = await Promise.all(promises);
// ...which makes it useful if you need to check the returned
// results against the promise-producing array
userList.forEach(({ id }, i) => {
if (id === res[i]) console.log(`${id}|${res[i]} - Match`);
if (id !== res[i]) console.log(`${id}|${res[i]} - No match`);
});
} catch (e) {
console.log(e);
}
};
const userList = [{ id: 1, userHash: 1 }, { id: 2, userHash: 2 }];
// `map` over the fields and return a
// validation promise for each
const promises = userList.map(({ id }) => {
return safety_first.getUserHash(id);
});
// Pass in the original userList, and the generated promises
checkUserHashes(userList, promises);
Update: if you want to break out of the loop when a match has been found that's a little easier:
const safety_first = {
getUserHash(id) {
return new Promise(resolve => {
// This will match the second object (index 1)
setTimeout(() => resolve(id === 1 ? id: id + 1), 1000);
});
}
}
async function checkUserHashes(userList) {
// iterate over the array of objects
for (let [index, obj] of userList.entries()) {
// await for that one hash check
const res = await safety_first.getUserHash(obj.id);
// if it matches return the id
if (obj.id === res) return obj;
}
// otherwise return null if there are no matches
return null;
}
const userList = [{ id: 0, userHash: 1 }, { id: 1, userHash: 2 }];
(async () => {
const id = await checkUserHashes(userList);
console.log(id);
})();

ReactJS how to wait for all API calls to be ended in componentDidMount of simple component

I'm using latest react and very basic app which calls 3rd party service API which actually is not well designed in meaning of following.
I have to execute one call which return list and then have to iterate and call other end point to get data for item from list and then again in data have new list for which I have to call 3rd API end point.
After I receive all data I combined it to one items array and place it in state in componentDidMount function but this final step works only if I surround it with setTimeout.
Is there some elegant way to do that?
I'm using fetch and really pure react components, have my own simple service from where I call API, here is some code parts...
items[tag].sensors = [];
API.getObjects(sessionData, userDetails, tag).then(links => {
Object.keys(links.link).forEach(link => {
API.getObjects(sessionData, userDetails, link).then(objLink => {
Object.keys(objLink.link).forEach(function (key) {
let obj = objLink.link[key];
if (obj && obj.type === 'sensor') {
API.getSensorNames(sessionData, key).then(response => {
const sensor = response.sensor;
// some sensor calculations....
items[tag].sensors.push(sensor);
});
}
});
});
});
});
// this part only works if it's surrounded with timeout
setTimeout(function() {
let processedItems = [];
for (var key in items) {
if (items.hasOwnProperty(key)) {
processedItems.push(items[key]);
}
}
self.setState({
items: processedItems
});
}, 1000);
Thanks in advance.
Simply, You can use Promise to wait until you get values from the API call, therefore you will put your code in function like this
function prepareItems() {
items[tag].sensors = [];
return new Promise((resolve, reject) => {
API.getObjects(sessionData, userDetails, tag).then(links => {
Object.keys(links.link).forEach(link => {
API.getObjects(sessionData, userDetails, link).then(objLink => {
Object.keys(objLink.link).forEach(function(key) {
let obj = objLink.link[key];
if (obj && obj.type === "sensor") {
API.getSensorNames(sessionData, key).then(response => {
const sensor = response.sensor;
// some sensor calculations....
items[tag].sensors.push(sensor);
// whenever you set resolve it will end the promise
//and pass the result it to the then function
resolve(items)
});
}
});
});
});
});
});
}
and use then to get the result from the prepareItems function after its resolved
prepareItems().then(items => {
//Do what ever you want with prepared item
})
What about using async/await operators.
These operators allows you to wait until the response is ready.
You can use this kind of helper function.
getItems = async (...) => {
...
items[tag].sensors = []
const links = await API.getObjects(sessionData, userDetails, tag)
Object.keys(links.link).forEach(async (link) => {
const objLink = await API.getObjects(sessionData, userDetails, link)
Object.keys(objLink.link).forEach(async (key) => {
let obj = objLink.link[key]
if (obj && obj.type === 'sensor') {
const response = await API.getSensorNames(sessionData, key)
const sensor = response.sensor
items[tag].sensors.push(sensor)
}
})
})
this.setState({ items })
}
Also you can see this great documentation.

Categories