I've got a nativescript vue application, and on a certain page I need to fetch some data (every 3 seconds) from an api using axios. The data is returned in xml, and I convert it to json using xml2js. I'm using async/await for both these functions. Something is blocking the UI thread, because whenever this function runs scrolling in my app freezes, along with any animations.
Does anyone know what here is blocking the DOM?
<template>
//ui code here
</template>
<script>
import { mapGetters } from 'vuex'
import axios from 'axios'
import xml2js from 'nativescript-xml2js'
export default {
name: 'component1',
data () {
return {
pending: true,
error: false,
results: null,
refreshInterval: null
}
},
computed: {
...mapGetters({
token: 'main/token'
})
},
methods: {
async requestData() {
const headers = {
Cookie: 'USER_ID=' + this.token
}
const url = 'url goes here'
const parser = new xml2js.Parser({
async: true
})
try {
const response = await axios.get(url, { headers: headers })
const newData = await new Promise((resolve, reject) => parser.parseString(response.data, (err, result) => {
if (err) reject(err)
else resolve(result)
}))
this.results = newData['results']
this.error = false
this.pending = false
} catch (e) {
this.data = null
this.error = e
this.pending = false
}
this.pending = false
}
},
created() {
setTimeout(() => {
this.requestData()
},500)
this.refreshInterval = setInterval(() => {
this.requestData()
},3000)
},
beforeDestroy () {
clearInterval(this.refreshInterval)
}
}
</script>
EDIT:
I tried implementing workers to offload xml2js to another thread, but still having the same issue. This is how my code looks now:
home.vue:
<template>
<template/>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'component1',
data () {
return {
dataLoaded: true,
error: false,
results: null,
refreshInterval: null
}
},
computed: {
...mapGetters({
token: 'main/token'
})
},
methods: {
requestData() {
console.log('fetching....')
this.$backendService
.api()
.then(xml => {
return this.$backendService.x2jworker(xml)
})
.then(json => {
if( this.results !== json['results'] ) {
this.results = json['results']
}
this.dataLoaded = true
this.error = false
})
.catch((error) => {
this.dataLoaded = true
this.error = true
})
}
},
created() {
setTimeout(() => {
this.requestData()
},500)
this.refreshInterval = setInterval(() => {
this.requestData()
},3000)
},
beforeDestroy () {
clearInterval(this.refreshInterval)
}
}
</script>
backend-service.js:
import axios from 'axios';
import xml2js from 'nativescript-xml2js'
import { WorkerService } from "../worker.service"
export default class BackendService {
api() {
return new Promise((resolve, reject) => {
const url = 'url'
axios.get(url)
.then(response => {
resolve(response.data)
})
.catch((error) => {
if (error) {
console.log('uh oh')
reject(error)
}
})
})
}
x2jworker(xml) {
return new Promise((resolve, reject) => {
var workerService = new WorkerService()
var jsWorker = workerService.initJsWorker()
jsWorker.onmessage = m => {
resolve(m.data)
}
jsWorker.postMessage(xml)
jsWorker.onerror = e => {
console.log(e)
jsWorker.terminate()
reject(e)
}
})
}
}
worker/javascript.worker.js:
import 'tns-core-modules/globals'
import xml2js from 'nativescript-xml2js'
global.onmessage = function (msg) {
console.log("Inside JS worker...")
var parser = new xml2js.Parser({
async: true
})
parser.parseString(msg.data, function (err, result) {
if (err) {
console.log(err)
global.close()
} else {
global.postMessage(result)
}
})
}
worker-service.js:
const workers = []
export class WorkerService {
constructor() {
}
initJsWorker() {
if (this.jsWorker) {
return this.jsWorker
}
const JsWorker = require("nativescript-worker-loader!./workers/javascript.worker.js")
this.jsWorker = new JsWorker()
workers.push(this.jsWorker)
return this.jsWorker
}
}
if ((module).hot) {
(module).hot.dispose(() => {
workers.forEach(w => {
w.terminate()
})
})
}
Related
I'm using vue-js and i encountered this problem where i call a get all api and then store it in an array. The logic works perfectly fine in the first component, however when i copied literally the exact same code and pasted it in a second component the call for some reason returns an empty array instead of the array of users. Here is the component
import UsersInServiceCenterService from "../services/UsersInServiceCenterService";
import UserService from "../services/UserService";
export default {
name: "service center",
data() {
return {
currentServiceCenter: null,
message: '',
listOfSpecificServiceCenter1: [],
listOfSpecifiedUsers1: [],
id:"",
};
},
methods: {
getServiceCenter(id) {
ServiceCenterService.get(id)
.then((response) => {
this.currentServiceCenter = response.data;
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
},
updateServiceCenter() {
ServiceCenterService.update(this.currentServiceCenter.id, this.currentServiceCenter)
.then((response) => {
console.log(response.data);
this.message = "The service center was updated successfully!";
})
.catch((e) => {
console.log(e);
});
},
deleteServiceCenter() {
ServiceCenterService.delete(this.currentServiceCenter.id)
.then((response) => {
console.log(response.data);
this.$router.push({ name: "service centers" });
})
.catch((e) => {
console.log(e);
});
},
retrieveAllUsersByServiceCenterId(currentServiceCenter){
UsersInServiceCenterService.getAllUsersByServiceCenterId(currentServiceCenter.id)
.then((response) => {
this.listOfSpecificServiceCenter1 = response.data;
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
},
retrieve(){
this.retrieveAllUsersByServiceCenterId(this.currentServiceCenter);
if(this.listOfSpecificServiceCenter1.length!=0){
for(var i =0 ; i<this.listOfSpecificServiceCenter1.length;i++){
UserService.get(this.listOfSpecificServiceCenter1[i].userId)
.then((response) => {
this.listOfSpecifiedUsers1.push(response.data);
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
}
}
this.isEmpty = !this.isEmpty;
},
},
mounted() {
this.message = "";
this.getServiceCenter(this.$route.params.id);
},
};
</script>
getServiceCenter(id) works fine and it returns the service center. However both of the retrieve() and the retrieveAllUsersByServiceCenterId(currentServiceCenter) do not work at all and they return the empty array. Here is the first component which works totally fine with literally the exact same functions.
import UserService from "../services/UserService";
import UsersInServiceCenterService from "../services/UsersInServiceCenterService";
export default {
name: "servicecenters-list",
data() {
return {
serviceCenters: [],
users:[],
currentServiceCenter: null,
currentUser: null,
currentIndex: -1,
currentUserIndex: -1,
id: "",
searchValue: "",
isEmpty: false,
rows: [],
listOfSpecificServiceCenter:[],
listOfSpecifiedUsers:[],
};
},
methods: {
retrieveServiceCenters() {
ServiceCenterService.getAllServiceCenters()
.then((response) => {
this.serviceCenters = response.data;
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
},
retrieveUsers() {
UserService.getAllUsers()
.then((response) => {
this.users = response.data;
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
},
refreshList() {
this.retrieveServiceCenters();
//this.retrieveUsers();
this.currentServiceCenter = null;
this.currentIndex = -1;
//this.currentUser = null;
//this.currentUserIndex = -1;
},
setActiveServiceCenter(servicecenter, index) {
this.currentServiceCenter = servicecenter;
this.currentIndex = index;
},
setActiveUser(user,userIndex){
this.currentUser= user;
this.currentUserIndex = userIndex;
},
// showList(currentServiceCenter){
// for(var i =0;i<currentServiceCenter.users.length;i++){
// }
// },
retrieve(){
this.retrieveAllUsersByServiceCenterId(this.currentServiceCenter);
if(this.listOfSpecificServiceCenter.length!=0){
for(var i =0 ; i<this.listOfSpecificServiceCenter.length;i++){
UserService.get(this.listOfSpecificServiceCenter[i].userId)
.then((response) => {
this.listOfSpecifiedUsers.push(response.data);
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
}
}
this.isEmpty = !this.isEmpty;
},
retrieveAllUsersByServiceCenterId(currentServiceCenter){
UsersInServiceCenterService.getAllUsersByServiceCenterId(currentServiceCenter.id)
.then((response) => {
this.listOfSpecificServiceCenter = response.data;
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
},
addUserToServiceCenter(currentServiceCenter, currentUser) {
var data = {
serviceCenterId: parseInt(this.currentServiceCenter.id),
userId: parseInt(this.currentUser.id),
};
UsersInServiceCenterService.add(data)
.then((response) => {
//this.currentServiceCenter.id = response.data.id; //take care here
console.log(response.data);
this.submitted = true;
})
.catch((e) => {
console.log(e);
});
this.show=false
if(currentServiceCenter.users == Array.isArray(currentServiceCenter.users))
//currentServiceCenter.users = Array.isArray(currentServiceCenters.users) : [...currentServiceCenter.users, currentUser] : [currentUser]
currentServiceCenter.users.push(currentUser);
else{
currentServiceCenter.users = []
currentServiceCenter.users.push(currentUser);
}
},
removeAllServiceCenters() {
ServiceCenterService.deleteAllServiceCenters()
.then((response) => {
console.log(response.data);
this.refreshList();
})
.catch((e) => {
console.log(e);
});
},
searchId(id) {
ServiceCenterService.get(id)
.then((response) => {
this.currentServiceCenter = response.data;
})
.catch((e) => {
console.log(e);
});
this.refreshList();
// if(typeof id ==='string'){
// }
// else if(typeof id==='number'){
// CompanyService.get(id).then(response =>{
// this.currentCompany=response.data
// })
// .catch(e=>{
// console.log(e);
// });
// }
},
},
mounted() {
this.retrieveServiceCenters();
this.retrieveUsers();
},
computed:{
usersFilteredListByName(){
if(this.searchValue.trim().length >0){
return this.serviceCenters.filter((servicecenter) =>servicecenter.name.toLowerCase().includes(this.searchValue.trim()))
}
// if(this.searchValue.length>0 && isNaN(parseInt(this.searchValue))){
// return this.companies.filter((company) =>company.id.includes(this.searchValue))
// }
return this.serviceCenters
},
usersFilteredListById(){
if(this.searchValue.trim().length >0){
return this.serviceCenters.filter((servicecenter) =>servicecenter.id.toString().includes(this.searchValue))
}
return this.serviceCenters
},
}
};
</script>
You can ignore the rest of the functions and focus on the retrieve() and retrieveAllUsersByServiceCenterId(). Any help would be highly appreciated.
I use two functions to load some components for my page and save it in two fields representatives and allUsers. Then I try to use additional func subtractSets() to a little bit modify loaded data. My problem is that fields this.representatives and this.allUsers in this adittional function are still not initialized. What I can do to get loaded data in subtractSets()?
setup() {
const representatives = ref([])
const allUsers = ref([])
data() {
return {
representatives,
users
}
}
},
methods(): {
loadUsersRepresentatives() {
axios.get('getRepresentatives').then(res => {
this.representatives = res.data
}).catch(() => {
...
})
});
},
loadAllUsers() {
axios.get('/getAllUsers').then(res => {
this.allUsers = res.data
}).catch(() => {
...
})
});
},
subtractSets(obj1, obj2) {
...
},
showRepresentativesDialog(facultyID) {
this.loadUsersRepresentatives(facultyID)
this.loadAllUsers()
this.subtractSets(this.representatives, this.allUsers)
this.representativesDialog = true
}
},
ยดยดยด
You are missing async / await
methods(): {
async loadUsersRepresentatives() {
return axios.get('getRepresentatives').then(res => {
this.representatives = res.data
}).catch(() => {
...
})
});
},
async loadAllUsers() {
return axios.get('/getAllUsers').then(res => {
this.allUsers = res.data
}).catch(() => {
...
})
});
},
subtractSets(obj1, obj2) {
...
},
async showRepresentativesDialog(facultyID) {
await this.loadUsersRepresentatives(facultyID)
await this.loadAllUsers()
this.subtractSets(this.representatives, this.allUsers)
this.representativesDialog = true
}
},
An error keeps bothering me on my app says
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I do declare a useEffect in my Context so that I can have a realtime data storing and getting for my app.
Here is my code in Context;
const FetchProvider = ({ children }) => {
const [userList, setUserList] = useState([]);
const [teamList, setTeamList] = useState([]);
const authContext = useContext(AuthContext);
const authAxios = axios.create({
baseURL: process.env.REACT_APP_API_URL,
});
useEffect(() => {
let isCancelled = false;
if (
authContext.isAuthenticated &&
authContext.authState.userInfo !== null
) {
const getUsers = async () => {
try {
const users = await authAxios.get('/admin/get-all-users');
if (!isCancelled) {
setUserList(users.data);
}
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
const getTeams = async () => {
try {
const teams = await authAxios.get('/get-all-teams');
if (!isCancelled) {
setTeamList(teams.data);
}
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
getUsers();
getTeams();
}
return () => {
isCancelled = true;
};
}, [authAxios, authContext]);
return (
<Provider
value={{
authAxios,
userList,
setUserList,
teamList,
setTeamList,
}}
>
{children}
</Provider>
);
};
And I get this error in my Login.jsx and in my even though I don't declare useEffect in submitting and declaring it in .
Here is my code;
const submitCredentials = async (credentials, resetForm) => {
try {
setLoginLoading(true);
const { data } = await publicFetch.post('signin', credentials);
authContext.setAuthState(data);
setSignupSuccess(data.message);
setSignupError('');
setOpen(true);
setTimeout(() => {
setLoginLoading(false);
setredirectOnlogin(true);
resetForm(true);
}, 400);
} catch (error) {
setLoginLoading(false);
const { data } = error.response;
setSignupError(data.message);
setSignupSuccess('');
setOpen(true);
}
return setLoginLoading(false);
};
And I have tried many ways the internet has offered to fix this up but unfortunately it does not fix my problem.
I do have useEffect in my UsersTable.jsx and TeamTables.jsx.
Here is my code in UsersTable.jsx;
useEffect(() => {
let isCancelled = false;
const getUsers = async () => {
try {
const users = await fetchContext.authAxios.get('/admin/get-all-users');
setIsLoaded(true);
if (isLoaded === true) {
if (!isCancelled) {
fetchContext.setUserList(users.data);
}
}
return () => {
isCancelled = true;
};
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
getUsers();
return () => {
isCancelled = true;
};
}, [fetchContext, isLoaded]);
Here is my useEffect code in my TeamTable.jsx;
useEffect(() => {
let isCancelled = false;
const getTeams = async () => {
try {
const teams = await fetchContext.authAxios.get('get-all-teams');
setIsLoaded(true);
if (isLoaded === true) {
if (!isCancelled) {
fetchContext.setTeamList(teams.data);
}
}
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
getTeams();
return () => {
isCancelled = true;
};
}, [fetchContext, isLoaded]);
The isLoaded is used as an AJAX
Well, you can use the React recommended way to fix this issue. All you need to do is wrap your api call within makeCancellable method and cancel them when your component is unmounting.
Ref: https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
To do that create
const makeCancelable = (promise) => {
let isCancelled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => isCancelled ? reject({isCanceled: true}) : resolve(val),
error => isCancelled ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
isCancelled = true;
},
};
};
create a variable for the request outside your useEffect
let fetchTeamsRequest = null;
and updated your useEffect function like below.
useEffect(() => {
const getTeams = async () => {
if (fetchTeamsRequest) {
try {
await fetchTeamsRequest.promise;
return;
} catch (error) {
return;
}
}
fetchTeamsRequest = makeCancellable(fetchContext.authAxios.get('get-all-teams'));
try {
const teams = await fetchTeamsRequest.promise;
fetchTeamsRequest = null;
setIsLoaded(true);
if (isLoaded === true) {
if (!fetchTeamsRequest.isCancelled) {
fetchContext.setTeamList(teams.data);
}
}
} catch (error) {
if (!fetchTeamsRequest.isCancelled) {
fetchTeamsRequest = null;
console.log(error);
}
}
};
getTeams();
return () => {
if (fetchTeamsRequest) {
fetchTeamsRequest.cancel();
}
};
}, [fetchContext, isLoaded]);
In my store.js, I have a user_data state, its initial method is fetch_user_data:
export default new Vuex.Store({
state: {
user_data: util.fetch_user_data('username')
...
}
in the util.js:
util.fetch_user_data = function(username){
Lml_http('get', Api_urls.productconfig_common.user_data(username), null, response => {
return response.data // there get the data, in the debugger shows it.
}, error => {
})
}
But when I use the state's user_data, it is undefined.
EDIT-1
I want in the store.js to use the fetch_util's method for fetching data, and commit to the state.
EDIT-2
my lml_http code are bellow:
var lml_http = function (method, url, params, success_cb, error_cb, multipart_formdata=undefined) {
var format_to_form_data = function(data){
let formData = new FormData()
for (let item in data) {
formData.append(item, data[item])
}
return formData
}
var lowercase_method = method.toLowerCase()
var formated_params = params
var header_config = null
if (multipart_formdata) {
header_config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
formated_params = format_to_form_data(formated_params)
}
if(lowercase_method === "get") {
formated_params = {params: formated_params}
if (!header_config) {
Axios.get(url, formated_params).then(response => {
success_cb(response)
return
}).catch(response => {
error_cb(response)
return
})
} else {
Axios.get(url, format_to_form_data(formated_params), header_config).then(response => {
success_cb(response)
return
}).catch(response => {
error_cb(response)
return
})
}
return
}
else {
if(!header_config) {
Axios[method](url, formated_params).then(response => {
success_cb(response)
}).catch(response => {
error_cb(response)
})
return
}else {
Axios[method](url, formated_params, header_config).then(response => {
success_cb(response)
}).catch( response => {
error_cb(response)
})
return
}
}
}
Just create an action to fetch the user data as follows:
export default new Vuex.Store({
state: {
user_data: null
},
mutations: {
setUserData(state, data) {
state.user_data = data;
}
},
actions: {
fetchUserData({ commit }, username) {
Lml_http(
"get",
Api_urls.productconfig_common.user_data(username),
null,
response => {
commit("setUserData", response.data);
},
error => {}
);
}
}
});
Then dispatch this action in the created() hook of theroot vue instance
//main.js
new Vue({
el: "#app",
store,
render: h => h(App),
created() {
this.$store.dispatch("fetchUserData", "username");
}
});
`
Edit:
Axios returns a Promise by default. So in lml_http return Axios calls as below:
var lml_http = function(
method,
url,
params,
success_cb,
error_cb,
multipart_formdata = undefined
) {
var format_to_form_data = function(data) {
let formData = new FormData();
for (let item in data) {
formData.append(item, data[item]);
}
return formData;
};
var lowercase_method = method.toLowerCase();
var formated_params = params;
var header_config = null;
if (multipart_formdata) {
header_config = {
headers: {
"Content-Type": "multipart/form-data"
}
};
formated_params = format_to_form_data(formated_params);
}
if (lowercase_method === "get") {
formated_params = { params: formated_params };
if (!header_config) {
return Axios.get(url, formated_params)
.then(response => {
success_cb(response);
})
.catch(response => {
error_cb(response);
});
} else {
return Axios.get(url, format_to_form_data(formated_params), header_config)
.then(response => {
success_cb(response);
})
.catch(response => {
error_cb(response);
});
}
} else {
if (!header_config) {
return Axios[method](url, formated_params)
.then(response => {
success_cb(response);
})
.catch(response => {
error_cb(response);
});
} else {
return Axios[method](url, formated_params, header_config)
.then(response => {
success_cb(response);
})
.catch(response => {
error_cb(response);
});
}
}
};
Also add a return statement in utils.js
util.fetch_user_data = function(username){
return Lml_http('get', Api_urls.productconfig_common.user_data(username), null, response => {
return response.data // there get the data, in the debugger shows it.
}, error => {
})
}
Now in your action fetchUserData
fetchUserData({ commit }) {
util.fetch_user_data('username').then((
commit("setUserData", response.data);
});
}
In main.js dispatch the action without any payload
//main.js
new Vue({
el: "#app",
store,
render: h => h(App),
created() {
this.$store.dispatch("fetchUserData", "username");
}
});
Because the fetch api is async, it will return a promise, not the response data. I think you may try something like:
const store = new Vuex.Store({
state: {
user_data: null,
...
};
util.fetch_user_data('username')
.then(user => store.user_data = user);
export default store;
I am trying to do a simple to do app that does a request to a pokemon api and send me the result.
like this:
<template>
<div id="app">
<div>{{pokemon.forms}}</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "app",
data() {
return {
pokemon: {}
};
},
methods: {
requestPokemon() {
return new Promise((resolve, reject) => {
axios.get("http://pokeapi.co/api/v2/pokemon/2").then(value => {
resolve(value);
})
.catch(error => {
reject(error);
})
});
}
},
beforeMount() {
this.requestPokemon().then(value => {
pokemon = value;
}).error(error => {
console.log(error);
})
}
};
</script>
<style>
</style>
the issue is that i get the following error:
Error in beforeMount hook: "TypeError: this.requestPokemon(...).then(...).error is not a function"
any help with this??
thanks
Replace error with catch, as the others said:
export default {
name: "app",
data() {
return {
pokemon: {}
};
},
methods: {
requestPokemon() {
return new Promise((resolve, reject) => {
axios.get("http://pokeapi.co/api/v2/pokemon/2").then(value => {
resolve(value);
})
.catch(error => {
reject(error);
})
});
}
},
beforeMount() {
this.requestPokemon().then(value => {
pokemon = value;
}).catch(error => {
console.log(error);
})
}
};
Your beforeMount is not correct.
As oMiKeY said in his answer, you need to change .error to .catch
You also need to bind the response to this.pokemon (and not just pokemon)
Then, as requestPokemon function returns a Promise, you need to find the data in it :
beforeMount() {
this.requestPokemon().then(value => {
this.pokemon = value.data
}).catch(error => {
console.log(error);
})
}