Axios Data in Array logs undefined [duplicate] - arrays

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
Simplified:
Use Axios to get data
Place data in an array and end function with return array
The array is passed to function
console log data from the array
why is it returning undefined?
Long Story:
I am re-coding to the Single responsibility principle,
so the function calls and returns weather data,
Later on, create a single function that adds data as elements to the hmtlDom.
I need to be able to select specific variables from the API data,
I'm just learning JSON and I was sure I was doing it wrong, so I simplified the results,
I'm working in Typescript, so it might be a typing issue.
reading through documentation hasn't helped, Last Resort is here.
export function getWeatherData(api : string) {
var weatherData: any = []
axios.get(api)
.then(function (response) {
weatherData.push(response.data.city.name)
})
.catch(function(error){
console.log(error)
})
return weatherData
}
enter image description here console.log(weatherData)
function main() {
var city = getCity()
var api = getApi(city)
let weatherData = getWeatherData(api)
console.log(weatherData)
clearDivs()
addDivs(howManyReadings)
addDataToDivs(weatherData)
}
export function addDataToDivs(weatherData: any) {
// let li = document.getElementsByClassName("weatherInnerContainer")
// let nI = li.length
// for (var i = 0; i < nI; i++) {
console.log(weatherData[0])
// li[i].appendChild(weatherData['city']['name'])
// li[i].appendChild(weatherData['list'][i.toString()]['main']['temp'])
// li[i].appendChild(weatherData['list'][i.toString()]['dt_txt'])
// li[i].appendChild(weatherData['list'][i.toString()]['weather']['0']['description'])
// let nElement = document.createElement('img')
// let iconValue = (weatherData['list'][i.toString()]['weather']['0']['icon']).toString()
// let iconLink = 'https://openweathermap.org/img/wn/' + iconValue + '#2x.png'
// nElement.src = iconLink
// li[i].appendChild(nElement)
// }
}
Console returns: undefined

axios.get is asynchronous function, which happens 'at a some time', while the function you made is synchronous. This means, that execution of getWeatherData() is immediate, and it does not wait for the results of axios.get.
You can solve this by using promises or callbacks, whichever you prefer. Promise based solution would look something like this:
export function getWeatherData(api : string) {
return axios.get(api)
.then(function (response) {
return response.data.city.name
})
.catch(function(error){
console.log(error)
})
}
function main() {
var city = getCity()
var api = getApi(city)
getWeatherData(api).then(weatherData => {
console.log(weatherData)
clearDivs()
addDivs(howManyReadings)
addDataToDivs(weatherData)
}
}

Related

how to return the value inside PromiseResult

I will try to keep my problem as simple as possible, I have this function that I created:
get_total_by_status(status: string){
const total = imports.index(status).then((d) => {
return d.total
})
return total
}
and I'm calling this function like this:
var status_published = this.get_total_by_status("pending payment")
but it's not working, I have put a console.log(total) inside the function, and I got this:
Promise {<pending>}
[[Prototype]]:Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 202
how can I return the 202 value??
I know that there are tons of questions similar to this one, and basically, all of them tell me to add async() at the function, and a await on the moment I call the function, I have tried this without success, and I have no idea what I'm missing here, I'm a python programer with 0 knowledge in react
Edit:
Tried this approach, still can't return the value
get_total_by_status(status: string, onSuccess) {
imports.index(status).then(
(d) => {
onSuccess(d.total);
}
);
}
# STUFFF
const status_published = this.get_total_by_status("published",
(response) => {
return response //also tried status_published = response
})
if I place a console.log(response) I do indeed can log the 202 that I was expecting, but status_published keeps being null
There are two options.
Option 1. Wrap your get_total_by_status call in an async function and await your get_total_by_status to get resolved value as follows:
async function get_total_by_status(status) {
const total = imports.index(status).then(
(d) => {
return d.total;
}
);
return total;
}
async function getPromiseValue() {
let status_published = await get_total_by_status("My status");
console.log(status_published);
}
getPromiseValue(); // prints "My status"
Option 2. Another option would be pass callback to your get_total_by_status function as an argument and call that callback in .then() block as follows:
function get_total_by_status(status, onSuccess) {
// you don't need to store any return value in this case
imports.index(status).then(
(d) => {
onSuccess(d.total);
}
);
}
get_total_by_status("My status", (response) => {
console.log(response); // prints "My status"
});
Let me know if you have any kind of query or doubt regarding above code snippets :)

How to make an asynchronous return a promise chain within render() with componentDidMount()?

I have a button within my return in my render function that calls a function that initiates a promise chain which the result updates a state variable. However, the value is not passed through.
I tried some attempts on componentDidMount() but haven't had any luck
Here is the button within my render function
<button onClick={this.addIPFSItem}
className="btn btn-info btn-sm m-1">NewFile</button>
This calls the following function
addIPFSItem(){
var searchAddress = "0x9Cf0dc46F259542A966032c01DD30B8D1c310e05";
const contract = require('truffle-contract')
const simpleStorage = contract(SimpleStorageContract)
simpleStorage.setProvider(this.state.web3.currentProvider)
this.state.web3.eth.getAccounts((error, accounts) => {
simpleStorage.deployed().then((instance) => {
this.simpleStorageInstance = instance
return this.simpleStorageInstance.getLength(searchAddress);
}).then((accountLength) => {
var items = []
const ipfsPrefix = "https://ipfs.io/ipfs/";
var i;
for (i = 0; i < accountLength; i++) {
var currHash = this.simpleStorageInstance.getBook(searchAddress, i,
{from: searchAddress});
var currURL = ipfsPrefix + this.currHash;
//Here I am printing the counter values, and it prints the correct
//amount
console.log('itemhash ', i)
items.push(currHash)
}
//I do not get the value of items[1] in the console, but an undefined
//promise
console.log('address URL ', items[1])
//the state of ipfsHash is not updated correctly
return this.setState({ipfsHash: items[1]});
})
})
}
Essentially, I am executing a promise chain (by connecting to web3) and retrieving data. I think the essential problem is that I am calling an async function within the render(). I'm not sure how to fix this with componentDidMount()
console.log('address URL ', items[1]) should give something like address URL 0x9Cf0dc46F259542A966032c01DD30B8D1c310e05. However, I instead get address URL Promise{<pending>}.
Your this.simpleStorageInstance.getBook is a promise, that means it is executed asynchronously.
To get it's result you either have to use .then or the new syntax async/await. With the following function your items array will be filled with the correct data :
You will have to put the async keyword before your parent function name if you choose this solution
for (i = 0; i < accountLength; i++) {
items.push(await this.simpleStorageInstance.getBook(searchAddress, i, { from: searchAddress }))
}
An even shorter syntax would imply using your raw array of accounts and map it instead of using its length :
const items = myAccounts.map(async () => await this.simpleStorageInstance.getBook(searchAddress, i, { from: searchAddress }))

AngularJS: promise in a loop

I am unable to do the promise looping.
I make a service call to get list of providers, then for each provider, I make another service call to get a customer.
A provider has 1 or more customers. So eventual list of customer is to be decorated and displayed.
In other format I am trying to achieve:
*serviceA.getProvider(){
foreach(providers){
foreach(provider.customerID){
serviceB.getCustomer(customerId)
}
}
}
.then(
foreach(Customer){
updateTheCustomer;
addUpdatedCustomerToAList
}
displayUpdatedCustomreList();
)*
I have written following code, that isn't working
doTheJob(model: Object) {
let A = [];
let B = [];
let fetchP = function(obj) {
obj.Service1.fetchAllP().then(function (response) {
let P = cloneDeep(response.data);
_.forEach(P, function(prov) {
_.forEach(prov.CIds, function(Id) {
A.push(Id);
});
});
_.forEach(A, function(CId) {
return obj.Service2.getById(CId);//what works is if this statement was: return obj.Service2.getById(A[0]);
//So, clearly, returning promise inside loop isn't working
});
})
.then(function(response) {
B.push(response.data); //This response is undefined
angular.forEach(B, function (value) {
obj.updateAdr(value)
});
obj.dispay(B);
});
};
fetchP(this);
}
forEach don't stop when you use return inside of it, try to use a plain loop instead, why you don't just loop with for ?
_.forEach(A, function(CId) {
return obj.Service2.getById(CId);
}
as stated by #Ze Rubeus if you return inside a callback within a for loop that value will be lost, since it's not returned to the caller.
probably you wanted something like this
return Promise.all(A.map(function(CId){
//collect each promise inside an array that will then be resolved
return obj.Service2.getById(CId);
})

Why this setState is not a function in ComponenDidMount?

I am trying to fetch ordered data from Firebase and set it to state highscoreArray but it gives error "undefined is not a function (evaluating 'this.setState({ highscoreArray:sortedHighscores })')
componentDidMount() {
const reference = database.ref("highscores");
// Pushing sorted data to highscoreArray.
reference.orderByChild("highscore").limitToLast(3).on("value", function (snapshot) {
sortedHighscores = [];
snapshot.forEach(function (child) {
sortedHighscores.push({
"username": child.val().username,
"score": child.val().highscore
});
});
sortedHighscores = sortedHighscores.reverse();
console.log("sortedh", sortedHighscores); // fetch success
this.setState({highscoreArray: sortedHighscores}); // gives error
});
}
One of the major advantages of arrow functions is that it does not have it's own this value. It's this is lexically bound to the enclosing scope.
class Logger {
dumpData(data) {
var _this = this;
// this dumps data to a file and get the name of the file via a callback
dump(data, function (outputFile) {
_this.latestLog = outputFile;
});
}
}
// using arrow functions
class Logger {
dumpData(data) {
dump(data, outputFile => this.latestLog = outputFile);
}
}
1.this not accessible within loop so use variable let that = this the use that wherever you need this in this function.
componentDidMount() {
const reference = database.ref("highscores");
let that = this // here your variable declaration
// Pushing sorted data to highscoreArray.
reference.orderByChild("highscore").limitToLast(3).on("value", function (snapshot) {
sortedHighscores = [];
snapshot.forEach(function (child) {
sortedHighscores.push({
"username": child.val().username,
"score": child.val().highscore
});
});
sortedHighscores = sortedHighscores.reverse();
console.log("sortedh", sortedHighscores); // fetch success
that.setState({highscoreArray: sortedHighscores}); // gives error
});
}
Hope this will help you :) happy coding!
Inside the function callback the this has a different context. Either use an arrow function, or store a reference outside:
Arrow:
reference.orderByChild("highscore").limitToLast(3).on("value", (snapshot) => { ... });

dealing with an array of objects with promises

I am trying to make a node express app where I fetch data from different url's making a call to node-fetch to pull the body of some pages and other information about certain url endpoints. I want to then render a html table to display this data through an array of information. I am having trouble with the call to render the information as all the functions are asynchronous making it difficult to make sure all the promise calls have been resolved before making my call to render the page. I have been looking into using bluebird and other promise calls of .finally() and .all() but they don't seem to work on my data as it is not an array of promise calls, but an array of objects. Each object was 4 promise calls to fetch data relating to a column of my table all in one row. Is there a function or specific way to render the page after all promises are resolved?
var express = require('express');
var fetch = require('node-fetch');
fetch.Promise = require('bluebird');
var router = express.Router();
const client = require('../platform-support-tools');
function makeArray() {
var registry = client.getDirectory();
var data_arr = [];
for (var i = 0; i < registry.length; i++) {
var firstUp = 0;
for (var j = 0; i < registry[i]; j++) {
if (registry[i][j]['status'] == 'UP') {
firstUp = j;
break;
}
}
var object = registry[i][firstUp];
data_arr.push({
'name': object['app'],
'status': object['status'],
'swagUrl': object['homePageUrl'] + 'swagger-ui.html',
'swag': getSwag(object),
'version': getVersion(object['statusPageUrl']),
'timestamp': getTimestamp(object['statusPageUrl']),
'description': getDescription(object['healthCheckUrl'])
});
}
return data_arr;
}
function getSwag(object_in) {
var homeUrl = object_in['homePageUrl'];
if (homeUrl[homeUrl.length - 1] != '/'){
homeUrl += '/';
}
var datum = fetch(homeUrl + 'swagger-ui.html')
.then(function (res) {
return res.ok;
}).catch(function (err) {
return 'none';
});
return datum;
}
function getVersion(url_in) {
var version = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['version'];
}).catch(function (error) {
return 'none';
});
return version;
}
function getTimestamp(url_in) {
var timestamp = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['timestamp'];
}).then(function (res) {
return body['version'];
}).catch(function (error) {
return 'none';
});
return timestamp;
}
function getDescription(url_in) {
var des = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['description'];
}).catch(function (error) {
return 'none';
});
return des;
}
/* GET home page. */
router.get('/', function (req, res, next) {
var data_arr = makeArray();
Promise.all(data_arr)
.then(function (response) {
//sorting by app name alphabetically
response.sort(function (a, b) {
return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0);
});
res.render('registry', {title: 'Service Registry', arr: response})
}).catch(function (err) {
console.log('There was an error loading the page: '+err);
});
});
To wait on all those promises, you will have to put them into an array so you can use Promise.all() on them. You can do that like this:
let promises = [];
for (item of data_arr) {
promises.push(item.swag);
promises.push(item.version);
promises.push(item.timestamp);
promises.push(item.description);
}
Promise.all(promises).then(function(results) {
// all promises done here
})
If you want the values from all those promises, back into the object that's a bit more work.
let promises = [];
for (item of data_arr) {
promises.push(item.swag);
promises.push(item.version);
promises.push(item.timestamp);
promises.push(item.description);
}
Promise.all(promises).then(function(results) {
// replace promises with their resolved values
let index = 0;
for (let i = 0; i < results.length; i += 4) {
data_arr[index].swag = results[i];
data_arr[index].version = results[i + 1];
data_arr[index].timestamp = results[i + 2];
data_arr[index].description = results[i + 3];
++index;
});
return data_arr;
}).then(function(data_arr) {
// process results here in the array of objects
});
If you had to do this more often that just this once, you could remove the hard coding of property names and could iterate all the properties, collect the property names that contain promises and automatically process just those.
And, here's a more general version that takes an array of objects where some properties on the objects are promises. This implementation modifies the promise properties on the objects in place (it does not copy the array of the objects).
function promiseAllProps(arrayOfObjects) {
let datum = [];
let promises = [];
arrayOfObjects.forEach(function(obj, index) {
Object.keys(obj).forEach(function(prop) {
let val = obj[prop];
// if it smells like a promise, lets track it
if (val && val.then) {
promises.push(val);
// and keep track of where it came from
datum.push({obj: obj, prop: prop});
}
});
});
return Promise.all(promises).then(function(results) {
// now put all the results back in original arrayOfObjects in place of the promises
// so now instead of promises, the actaul values are there
results.forEach(function(val, index) {
// get the info for this index
let info = datum[index];
// use that info to know which object and which property this value belongs to
info.obj[info.prop] = val;
});
// make resolved value be our original (now modified) array of objects
return arrayOfObjects;
});
}
You would use this like this:
// data_arr is array of objects where some properties are promises
promiseAllProps(data_arr).then(function(r) {
// r is a modified data_arr where all promises in the
// array of objects were replaced with their resolved values
}).catch(function(err) {
// handle error
});
Using the Bluebird promise library, you can make use of both Promise.map() and Promise.props() and the above function would simply be this:
function promiseAllProps(arrayOfObjects) {
return Promise.map(arrayOfObjects, function(obj) {
return Promise.props(obj);
});
}
Promise.props() iterates an object to find all properties that have promises as values and uses Promise.all() to await all those promises and it returns a new object with all the original properties, but the promises replaced by the resolved values. Since we have an array of objects, we use Promise.map() to iterate and await the whole array of those.

Resources