I want to fire a vuex action in created() and when I receive a data, then fire new asynchronous method that fetches more data from server. When data is available I will use them in a component. Unfortunatelly I got stuck with Observer returned from Promise. I tried to change data to computed() without luck. I tried to await but it did not help either. The other computed property item works fine. I know that the Observer is Vue's way for reactivity but I do not know how to fix it.
<SeriesBarChart v-if="! inProgress" :series="series" /> // initial attempt
<SeriesBarChart v-if="! inProgress" :series="groups" /> // computed property attempt
data: () => ({
series: [{}, {}],
inProgress: true,
}),
created() {
this.$store.dispatch('GET_POLL', { slug: this.slug }).then(() => {
this.runQueries(this.item._id, ['vehicles=car&vehicles=bike', 'region=PRG']); // await here did not help
});
},
computed: {
item() {
return this.$store.getters.POLL;
},
groups() {
return this.series;
},
},
methods: {
async runQueries(id, queries) {
this.inProgress = true;
const promises = [];
for (let i = 0; i < queries.length; i += 1) {
promises.push(this.$store.dispatch('GET_POLL_VOTES', { id, query: queries[i] }));
}
Promise.all(promises).then((values) => {
for (let i = 0; i < values.length; i += 1) {
this.series[i] = values[i].data.data;
}
});
this.inProgress = false;
}
Because Yom has not posted an answer and he even deleted his helpful comment, I will post my answer for future googlers. The reason why Vue provided the Observer object was a statement this.inProgress = false; outside of the then block. Following code works as expected:
async runQueries(id, queries) {
this.inProgress = true;
const promises = [];
for (let i = 0; i < queries.length; i += 1) {
promises.push(this.$store.dispatch('GET_POLL_VOTES', { id, query: queries[i] }));
}
Promise.all(promises).then((values) => {
for (let i = 0; i < values.length; i += 1) {
this.series[i] = values[i].data.data;
}
this.inProgress = false;
});
}
Related
This question already has answers here:
Promise.all is returning an array of undefined and resolves before being done
(3 answers)
Closed 25 days ago.
I'm trying to wrap my head around Promises, but my code doesn't work the way it should. My goal is to show that the table has loaded after all the promises are complete, but instead the code in then ("table loaded" and [undefined, undefined] 'res') get executed before. What is the way to make it work, waiting untill the table was filled?
My code
async function updateTable(accno, entity, start = null, funds) {
$table.bootstrapTable('removeAll')
prms = []
return Promise.all(
accno.map(
acc => {
let conf = {
method: 'get',
url: 'url',
params: {
account_number: acc,
start: start,
entity: entity,
}
}
axios(conf).then(function(response) {
let data = response.data
data_combined = []
for (let j = 0; j < data.return.length; j++) {
let row = data.return[j]
let temp = {
id: data.id,
// assigining more data here
}
data_combined.push(temp)
}
//another request deleted to simplify
fund_array = []
tableLoadAnimation('id-table-load', 'remove-all')
superset = [...superset, ...data_combined, ...fund_array]
$table.bootstrapTable('load', superset)
}).catch(function(error) {
console.log(error);
})
})
).then((a) => {
console.log('table loaded');
return a
}).then((res) => {
console.log(res, "res") // returns [undefined, undefined] 'res'
return res
})
}
Since axios returns promise, I had to return axios in map
async function updateTable(accno, entity, start = null, funds) {
$table.bootstrapTable('removeAll')
prms = []
prms = Promise.all(
accno.map(
acc => {
let conf = {
method: 'get',
url: 'url',
params: {
account_number: acc,
start: start,
entity: entity,
}
}
return axios(conf).then(function(response) {
let data = response.data
data_combined = []
for (let j = 0; j < data.return.length; j++) {
let row = data.return[j]
let temp = {
id: data.id,
// assigining more data here
}
data_combined.push(temp)
}
//another request deleted to simplify
fund_array = []
tableLoadAnimation('id-table-load', 'remove-all')
superset = [...superset, ...data_combined, ...fund_array]
$table.bootstrapTable('load', superset)
}).catch(function(error) {
console.log(error);
})
})
).then((a) => {
console.log('table loaded');
return a
})
}
I am working with an API that gives me data with a limit per request (here 25). Therefore, I have to recursively make promises with fetch. However, while most of my logic works, when I try to return from the function it will return an empty array. The image below will make it more clear.
const url = (conf_name) => {
return (
"https://api.elsevier.com/content/search/scopus?view=COMPLETE&cursor=*&query=CONFNAME(" +
conf_name +
")%20AND%20DOCTYPE(cp)%20AND%20SRCTYPE(p)&field=dc:creator,dc:title,dc:description,affilname,affiliation-city,affiliation-country,authkeywords,prism:doi,prism:doi,prism:coverDate"
);
};
const savePapers = (json, conf_name) => {
let temp = new Array();
for (let i = 0; i < json["search-results"]["entry"].length; i++) {
temp[i] = {
title: json["search-results"]["entry"][i]["dc:title"],
author: json["search-results"]["entry"][i]["dc:creator"],
publication_date: json["search-results"]["entry"][i]["prism:coverDate"],
doi: json["search-results"]["entry"][i]["prism:doi"],
abstract: json["search-results"]["entry"][i]["dc:description"],
author_keywords: json["search-results"]["entry"][i]["authkeywords"],
proceeding: conf_name,
};
}
return temp;
};
async function getPapers(final, url, conf_name) {
let total_amount_of_papers;
let next;
let position = 2;
try {
let response = await fetch(url, options);
let json = await response.json();
total_amount_of_papers = json["search-results"]["opensearch:totalResults"];
if (json["search-results"]["link"][position]["#ref"] == "prev")
next = json["search-results"]["link"][position + 1]["#href"];
next = json["search-results"]["link"][position]["#href"];
final = final.concat(savePapers(json, conf_name));
if (final.length === 50) {
console.log("hey",final.length);
return final;
}
await getPapers(final, next, conf_name);
} catch (error) {
console.log(error);
}
}
const createNewConf = async (conferences) => {
let final = new Array();
try {
var temp = new Conference({
acronym: conferences.acronym,
name: conferences.fullname,
area: conferences.area,
subarea: conferences.subarea,
location: conferences.location,
url: conferences.url,
description: conferences.description,
papers: await getPapers(final, url(conferences.acronym),conferences.acronym),
});
console.log(temp.papers.length);
} catch (error) {
console.log(error);
}
return temp;
};
describe("Saving records", function () {
it("Saved records to the database", async function (done) {
var conferences = [];
try {
for (var i = 0; i <= 1; i++) {
conferences[i] = await createNewConf(json_conferences[i]);
conferences[i].save().then(function () {
assert(conferences[i].isNew === True);
done();
});
}
mongoose.connection.close();
} catch (error) {
console.log(error);
}
});
});
Below you can see the length of my final array after passing the if to stop fetching more. and the second number is what I receive in the initial call
Console
Maybe anyone has an idea of the undefined behavior that occurs during return.
Your help is much appreciated.
I have an array of subscribeToMore from Apollo query that I want to use. I was inspired by this article, expect I want to use a functional component:
useEffect(() => {
const unsubscribe =
[subscribeToMore({
// subscriptionData...
}), subscribeToMore({
// subscriptionData...
})]
if (unsubscribe.length > 0) {
for (i = 0; i < unsubscribe.length; i++) {
return () => unsubscribe[i]()
}
}
}, [subscribeToMore])
However, I'm getting:
In unsubscribe[i](), unsubscribe[i] is undefined
useEffect just has one return function. You cannot call it multiple times. Instead inside the return function you can check for the condition and unsubscribe if there are subscriptions to be cleared
useEffect(() => {
const unsubscribe =
[subscribeToMore({
// subscriptionData...
}), subscribeToMore({
// subscriptionData...
})]
return () => {
if (unsubscribe.length > 0) {
for (i = 0; i < unsubscribe.length; i++) {
unsubscribe[i]()
}
}
}
}, [subscribeToMore])
I have this function, where I retrieve an array of objects, I then have a for loop to loop through the objects, and append an index or ind attribute to it:
module.exports.getCustomers = async (req, res) => {
let customers = await Customers.find({}, { "_id": 1 });
for (var i = 0; i < customers.length; i++) {
customers[i].ind = i;
}
console.log(customers)
}
but when I run this, the data is returned as
[
{ _id: ... },
{ _id: ... },
...
]
instead of:
[
{_id: ..., ind: 0},
{_id: ..., ind: 1},
...
]
Please how do I fix this
change your for and turn it into a map
module.exports.getCustomers = async (req, res) => {
let customers = await Customers.find({}, { "_id": 1 });
let mappedCustomers = customers.map((customer, index) => {
customer['ind'] = index;
return customer;
});
console.log(mappedCustomers);
return mappedCustomers;
}
or instead returning the customer, you can create a completly new customer.
let mappedCustomers = customers.map((customer, index) => {
return {...customer, ind: index};
});
It looks like your objects are freezed, idk what library you are using to fetch those items from your data source, but you can read about object freezing here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
Try copying the values over to the individual target objects with Object.assign.
module.exports.getCustomers = async(req, res) => {
let customers = await Customers.find({}, {
"_id": 1
});
for (var i = 0; i < customers.length; i++) {
Object.assign(customers[i], {
ind: i
});
}
console.log(customers);
}
I finally solved it. I think mongoose was messing with it. But adding ._doc seems to have fixed it
for (let i = 0; i < customers.length; i++) {
let customer = customers[i],
customer._doc = {
...customer._doc,
index: i
};
}
I'm trying to make a forecast app with React and Flux. I fetch the data from Yahoo Weather API, and put the data to my store with a callback in jsonp request.Then in the View, I get the data (in componentDidMount())from store as a state and pass some properties of it to child components.
The data(this.state.store), which is a Object, has two properties, called condition and forecast.The problem is that if I want to pass the this.state.store.condition(or forecast) to the child, it says TypeError: Cannot read property 'condition' of undefined. But if I just try to access this.state.store(for example, console.log(this.state.store)), there is no error.
Also, if I try to access this.state.store.condition in a try-catch statement, and log the error when there is one, I do access the condition successfully with the console printed TypeError above mentioned.
Here is my codes:
store:
const CHANGE_EVENT = 'change';
let _app = {};
// create a city
function create(city, data) {
_app[city.toUpperCase()] = {
condition: data.condition,
forecast: data.forecast,
};
}
const AppStore = Object.assign({}, EventEmitter.prototype, {
getAll() {
return _app;
},
emitChange() {
this.emit(CHANGE_EVENT);
},
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
});
// register callback
AppDispatcher.register((action) => {
switch (action.actionType) {
case AppConstants.CREATE_CITY: {
create(action.city, action.data);
AppStore.emitChange();
break;
}
// other cases
default:
// noop
}
});
actions:
function callback(city, data) {
console.log(data);
const action = {
actionType: AppConstants.CREATE_CITY,
city,
data,
};
AppDispatcher.dispatch(action);
}
const AppActions = {
create(city) {
getDataFromAPI(city, callback);
},
};
utils:
function getDataFromAPI(query, callback) {
let data;
const url = `https://query.yahooapis.com/v1/public/yql?q=select * from weather.forecast where u='c' AND woeid in (select woeid from geo.places(1) where text="${query}")&format=json`;
superagent
.get(url)
.use(jsonp)
.end((err, res) => {
console.log(res.body.query.results.channel.item);
data = res.body.query.results.channel.item;
callback(query, data);
});
}
views:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
store: Store.getAll(),
currentCity: 'BEIJING',
};
this.onChange = this.onChange.bind(this);
this.getCurrentCity = this.getCurrentCity.bind(this);
}
componentWillMount() {
AppActions.create('BEIJING');
}
componentDidMount() {
Store.addChangeListener(this.onChange);
}
onChange() {
this.setState({ store: Store.getAll() });
}
getCurrentCity(city) {
this.setState({ currentCity: city.toUpperCase() });
}
componentWillUnmout() {
Store.removeChangeListener(this.onChange);
}
render() {
// For now, I have to do all of these to pass the condition to the child component
let condition;
let forecast;
let text;
let temp;
let currentWeatherCode;
let forecastWeatherCode = [];
let currentWeatherClassName;
let forecastWeatherClassName = [];
let date;
let forecastDate = [];
console.log(this.state.store[this.state.currentCity]);<--NO ERROR
// console.log(this.state.store[this.state.currentCity])<--UNDEFINED
// console.log(this.state.store[this.state.currentCity].condition);<--CANNOT READ PROPERTY
^
|
ERROR ON THIS 2 STATEMENTS
try {
condition = this.state.store[this.state.currentCity].condition;
forecast = this.state.store[this.state.currentCity].forecast;
text = condition.text.toUpperCase();
temp = condition.temp;
currentWeatherCode = condition.code;
currentWeatherClassName = setWeatherIcon(currentWeatherCode);
date = condition.date;
for (let i = 0; i < 6; i++) {
forecastWeatherCode.push(forecast[i].code);
forecastWeatherClassName.push(setWeatherIcon(forecastWeatherCode[i]));
forecastDate.push(forecast[i].date);
}
} catch (err) {
console.log(err);<--STILL ERROR, BUT I DO ACCESS THE PROP CONDITION IN THIS WAY
}
return (
<div>
<Today
city={this.state.currentCity}
weatherStatus={text}
tempreture={temp}
currentWeatherClassName={currentWeatherClassName}
date={date}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('#app'));
It seems to me that you are trying to access the this.state.store[this.state.currentCity] property before it is fetched from the remote API.
You could add some sort of indication that the data is still being fetched like this.
render() {
// For now, I have to do all of these to pass the condition to the child component
let condition;
let forecast;
let text;
let temp;
let currentWeatherCode;
let forecastWeatherCode = [];
let currentWeatherClassName;
let forecastWeatherClassName = [];
let date;
let forecastDate = [];
console.log(this.state.store[this.state.currentCity]);<--NO ERROR
if (!this.state.store.hasOwnProperty(this.state.currentCity)) {
return <div>Loading...</div>;
}
... the rest of your original code
}
When it is done loading the setState() method is invoked and render() is called again. The second time it will fall trough the if and run your code.