How to make sure the function is executed in VueJS - javascript

I'm trying to execute 3 functions, and after than console.log the values that they change. I think there should be better approach for this kind of problems, but I'm not sure what it is. What I've done is I went old school, and added loading flag. Basically, loading = 3, when function is loaded, loading--
I'd like to demonstrate my current code (well actually it's not the same, but it will work for demo purposes), so you can get the feeling:
data:() => ({
loading: 3,
first: null,
second: null,
third: null
}),
methods: {
first() {
this.$route.get('/data/for/first').then(response => {
this.first = response.data;
this.loading--;
})
},
second() {
this.$route.get('/data/for/second').then(response => {
this.second = response.data;
this.loading--;
})
},
third() {
this.$route.get('/data/for/third/a').then(responseA => {
let thirdA = responseA.data;
this.$route.get('/data/for/third/b').then(responseB => {
let thirdB = responseB.data;
if (thirdA === thirdB) {
this.third = true;
}
this.loading--;
})
})
},
fireFunctions() {
this.first();
this.second();
this.third();
}
},
watch: {
loading: function() {
if (this.loading === 0) {
console.log(this.first, this.second, this.third)
}
}
}
The output looks like this:
dataForFirst, dataForSecond, dataForThird;
But, if I don't use the watcher, and load this.fireFunctions() in mounted() i get:
dataForFirst, dataForSecond, undefined;
Now, as I understand, this is happening because this.third() needs more time to process the data. As you can see in the code, I added loading flag. So, fire functions will only execute when all of the functions are loaded.
I don't think this is the best approach, so I'd like to hear your opinion on this one.
How would you handle it?

Use Promise.all to wait on all your async functions to return and then run whatever code you need to afterward, example:
methods: {
async all() {
let [first, second, third] = await Promise.all([
this.$route.get('/data/for/first'),
this.$route.get('/data/for/second'),
this.$route.get('/data/for/third')
]);
this.first = first;
this.second = second;
this.third = third;
console.log(first, second, third);
}
}

Related

access global script function vue

How
<script>
const fakeApiRequest = (id) => {
return id;
};
export default {
data() {
return {
ids: [1, 2, 3, 4, 5, 6],
};
},
methods: {
newValues(id) {
this.ids.forEach((el) => el.fakeApiRequest(id));
}
},
};
</script>
How can I access global script function - const fakeApiRequest ? Meanwhile using Window object (alike window.fakeApiRequest ) is useless also.
You can try something like this:
<script>
export default {
data() {
return {
ids: [1, 2, 3, 4, 5, 6],
};
},
methods: {
fakeApiRequest(id){
return id
},
newValues(id) {
this.ids.forEach((element) => {
console.log(this.fakeApiRequest(element))
});
}
},
};
</script>
<template>
<div>
{{newValues(5)}}
</div>
</template>
Welcome to stackoverflow,
the first thing that came to my mind was if it is really needed to do that many api requests. Just a side note: wouldn’t it be better to receive the data for multiple ids at the same time with only one API call?
I think in your example, that you posted into the codesandbox the problem is, that you don’t set the new array. The call to [].forEach is not setting anything. As you call an api, you need to make sure, all requests are resolved.
As some of the requests might be rejected, you could await Promise.allSettled and filter the results by the status:
const fakeApiRequest = async (id) => {
// await some api call...
return id;
};
export default {
methods: {
async newSuccessIds() {
const results = await Promise.allSettled(
// the fakeApiRequest can be called directly
// as it is part of the closure context.
this.ids.map(fakeApiRequest)
);
return results
.filter(({ status }) => status === 'fulfilled')
.map(({ value }) => value)
}
},
};

React useEffect does not work as expected

useEffect(() => {
const method = methodsToRun[0];
let results = [];
if (method) {
let paramsTypes = method[1].map(param => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes); //this is emscripten stuff
let params = method[1].map(param => document.getElementById(param[0][0]).value);
let result = runAlgo(...params); //this runs the emscripten stuff
results.push(...(JSON.parse(result)));
setGlobalState('dataOutput', results);
}
let newMethodsToRun = methodsToRun.slice(1);
if (methodsToRun.length>0) {
setGlobalState('methodsToRun', newMethodsToRun);
}
} , [dataOutput, methodsToRun]);
Hello, I am working on a ReactJS app that uses Webassembly with Emscripten.
I need to run a series of algorithms from Emscripten in my Js and I need to show the results as they come, not altogether at the end. Something like this:
Run first algo
Show results on screen form first algo
Run second algo AFTER the results from the first algo are rendedred on the screen (so that user can keep checking them)
Show results on screen form second algo.....
and so on
I already tried a loop that updates a global state on each iteration with timeouts etc, but no luck, so I tried this approach using useEffect, I tried various things but the results keep coming altogether and not one by one. Any clue?
I tried to mock your example HERE:
import React, { useState, useEffect } from 'react';
import './style.css';
export default function App() {
const [methodsToRun, setMethodsToRun] = useState([method1, method2]);
const [globalState, setGlobalState] = useState([]);
useEffect(() => {
if (methodsToRun.length <= 0) return;
const method = methodsToRun[0];
let results = [];
if (method) {
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes); //this is emscripten stuff
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = runAlgo(...params); //this runs the emscripten stuff
results.push(...JSON.parse(result));
setGlobalState((global) => [...global, results]);
}
setMethodsToRun((methods) => methods.slice(1));
}, [methodsToRun]);
console.log('METHODS TO RUN', methodsToRun);
console.log('GLOBAL RESULT', globalState);
return (
<div>
<div> Global state 1: {globalState[0] && globalState[0][0]}</div>
<div> Global state 2: {globalState[1] && globalState[1][0]}</div>
<div id="1">DIV 1 </div>
<div id="4">DIV 2</div>
<div id="7">DIV 3</div>
</div>
);
}
const method1 = [
'1param0',
[
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
],
];
const method2 = [
'2param0',
[
['11', '223', '3'],
['44', '55', '66'],
['77', '88', '99'],
],
];
//mock wasm
window.wasm = {
cwrap:
(...args) =>
() =>
JSON.stringify(args),
};
This example assumes your wasm methods are synchronous, your DOM will update and rerender with a new globalState after each method is executed, the cycle is ensured by the fact you are setting a new methods array sliced by one in the state, that will trigger a rerender and a new execution of the useEffect since it has the methodsToRun array in the deps array. Infinite render loop is prevented by the check if (methodsToRun.length <= 0) return; .
The drawback of this approach though is that if your wasms methods are synchronous and heavy, your UI will be frozen until it returns, and that's a terrible user experience for the end user. I'm not sure what kind of task you are trying to perform there, if it can be made async and awaited ( the logic would be the same ) or if you have to move that on a service worker.
So from what you've explained here, I think the issue can be resolved in 2 ways:
1 is with synchronous requests/process.
Assuming you have 2 functions (AlgoX and AlgoY) you want to run one after the other, you can return the expected response. e.g.
return AlgoX();
return AlgoY();
The return keyword will block the running process from continuing until you have result.
Another alternative I will suggest will be to use Self-Invoking-Expression:
useEffect(() => {
(
async function AlgoHandler(){
const method = methodsToRun[0];
let results = [];
if (method) {
let paramsTypes = method[1].map(param => param[0][2]);
let runAlgo = await window.wasm.cwrap(method[0], 'string', paramsTypes); //this is emscripten stuff
let params = method[1].map(param => document.getElementById(param[0][0]).value);
let result = await runAlgo(...params); //this runs the emscripten stuff
results.push(...(JSON.parse(result)));
setGlobalState('dataOutput', results);
}
let newMethodsToRun = methodsToRun.slice(1);
if (methodsToRun.length>0) {
setGlobalState('methodsToRun', newMethodsToRun);
}
}
)();
} , [dataOutput, methodsToRun]);
Notice where I used async and await in the second solution. Let me know if its helpful.
I'm not sure if this answers your question, but I wrote some example code that may help you fulfill your needs. It will need to be tweaked to fit your use case though, as this should be considered pseudo code:
const initialState = {
count: 0,
results: [],
};
function reducer(state, action) {
switch (action.type) {
case 'addResult':
return {
...state,
count: state.count + 1,
results: [...state.results, action.result],
};
default:
return state;
}
}
function Example({ methodsToRun }) {
const [{ count, results }, dispatch] = useReducer(reducer, initialState);
const next = useCallback(async() => {
if(methodsToRun.length > count) {
const result = await methodsToRun[count]();
dispatch({ type: 'addResult', result });
}
}, [count, methodsToRun]);
useEffect(() => {
next();
}, [next]);
return (
<div>
<p>Results:</p>
<ol>
{results.map(result => (
<li key={result/* choose a unique key from result here, do not use index */}>{result}</li>
))}
</ol>
</div>
);
}
So basically, my idea is to create a callback function, which at first run will be the method at index 0 in your array of methods. When the result arrives, it will update state with the result, as well as update the counter.
The updated count will cause useCallback to change to the next method in the array.
When the callback changes, it will trigger the useEffect, and actually call the function. And so on, until there are no more methods to run.
I have to admit I haven't tried to run this code, as I lack the context. But I think it should work.
At the end of the day, this solution looks like is working for me:
useEffect(() => {
(
async function AlgoHandler(){
if (methodsToRun.length <= 0) {
setGlobalState('loadingResults', false);
return;
}
const method = methodsToRun[0];
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes);
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = await runAlgo(...params);
setGlobalState('dataOutput', (prev) => [...prev, ...JSON.parse(result)]);
await sleep(100);
setGlobalState('methodsToRun', (prev) => prev.filter(m => m != method));
})();}, [methodsToRun]);

Creating new array vs modifing the same array in react

Following is the piece of code which is working fine, but I have one doubt regarding - const _detail = detail; code inside a map method. Here you can see that I am iterating over an array and modifying the object and then setting it to setState().
Code Block -
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
const { invoiceData } = this.state;
invoiceData.map(invoiceItem => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(detail => {
const _detail = detail;
if (_detail.tagNumber === data.tagNumber) {
_detail.id = data.id;
}
return _detail;
});
}
return invoiceItem;
});
state.invoiceData = invoiceData;
}
this.setState(state);
};
Is this approach ok in React world or I should do something like -
const modifiedInvoiceData = invoiceData.map(invoiceItem => {
......
code
......
})
this.setState({invoiceData: modifiedInvoiceData});
What is the pros and cons of each and which scenario do I need to keep in mind while taking either of one approach ?
You cannot mutate state, instead you can do something like this:
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
this.setState({
invoiceData: this.state.invoiceData.map(
(invoiceItem) => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(
(detail) =>
detail.tagNumber === data.tagNumber
? { ...detail, id: data.id } //copy detail and set id on copy
: detail //no change, return detail
);
}
return invoiceItem;
}
),
});
}
};
Perhaps try something like this:
checkInvoiceData = (isUploaded, data) => {
// Return early
if (!isUploaded) return
const { invoiceData } = this.state;
const updatedInvoices = invoiceData.map(invoiceItem => {
if (invoiceItem.number !== data.savedNumber) return invoiceItem
const details = invoiceItem.details.map(detail => {
if (detail.tagNumber !== data.tagNumber) return detail
return { ...detail, id: data.id };
});
return { ...invoiceItem, details };
});
this.setState({ invoiceData: updatedInvoices });
};
First, I would suggest returning early rather than nesting conditionals.
Second, make sure you're not mutating state directly (eg no this.state = state).
Third, pass the part of state you want to mutate, not the whole state object, to setState.
Fourth, return a new instance of the object so the object reference updates so React can detect the change of values.
I'm not saying this is the best way to do what you want, but it should point you in a better direction.

Vue Chart.js - Can't use the return data for chart data set

I have no idea why I can't use my return data like this.point0, this.point1 in the async mounted() section, all the value remain 0 when I call them. immediate:true is not work, compute dataset first is not work.
I'm trying to do responsive time series that change the value when I press the different button, everything works fine, the value is updated smoothly, except that I can use these value in the chart.
You can try out my code here: https://jsfiddle.net/1eahf26q/
An interesting comment from #Saksham asked "Why do you want to initialize the line chart in mounted(). It can be done whenever you are ready with the data" I'm not quite sure what he means. Sorry for this kind of question, my first time into Vue and javascript.
extends: Line,
props: ["height","breedKey", "time"],
computed: {
topic() {
return this.breedKey;
}
},
data() {
return {
point0: [],
point1: [],
point2: [],
};
},
watch: {
async breedKey (newVal, oldVal) {
try {
this.promise = axios.get(api_url);
const res = await this.promise;
this.point0 = res.data.data[0].Freq;
this.point1 = res.data.data[1].Freq;
this.point2 = res.data.data[2].Freq;
}
}
},
async mounted() {
await this.promise;
const datapresent = [];
let p0 = this.point0;
let p1 = this.point1;
let p2 = this.point2;
datapresent.push(p0, p1, p2);
this.renderChart(
{
labels: ['First','Second', "Third"],
datasets: [
{
label: "28 FEB",
data: datapresent
},
]
}
I don't really understand the patterns in your code, like where this.promise is initialised.
Why don't you try something that's a bit easier to understand.
methods: {
async getApiChartData() {
let responseData = (await axios.get(api_url)).data.data;
return [responseData[0], responseData[1], responseData[2]];
}
}
Then in mounted
async mounted() {
let datapresent = await this.getApiChartData();
//rest of your code
}

How can I stop multiple calls to a function inside an interval?

How could I ensure that one one call of a function in an interval can run at once?
My code looks like this:
var promisePutTestQuestion;
onEnter: ['$interval', 'questionService',
($interval, qus: IQuestionService) => {
promisePutTestQuestion = $interval(() => {
qus.putTestQuestionResponses();
}, 5 * 1000);
}],
onExit: ['$interval',
($interval) => {
$interval.cancel(promisePutTestQuestion);
}],
The problem I am having is if the putTestQuestionResponse (which returns a promise) is slow then I have multiple putTestQuestionResponse function calls one after the other.
Would appreciate if anyone has any ideas on this. Thanks
In javascript it's safe to use state variable for this purpose. Whenever qus.putTestQuestionResponses operation takes a lot of time next call will be skipped. So just maintain right state of processing variable, and if it's true quit the interval callback without running task again.
var promisePutTestQuestion;
let processing = false;
onEnter: ['$interval', 'questionService',
($interval, qus: IQuestionService) => {
promisePutTestQuestion = $interval(() => {
if (processing)
return;
processing = true;
qus.putTestQuestionResponses()
.then(() => processing = false)
}, 5 * 1000);
}],
onExit: ['$interval', ($interval) => {
$interval.cancel(promisePutTestQuestion);
}]
Not sure what promise framework you're using, but () => processing = false should be either in finally() for $q I guess, and in the then that should be after catch to ensure it is executed in any case.
So one of these:
qus.putTestQuestionResponses().finally(() => processing = false)
or
qus.putTestQuestionResponses().catch(...).then(() => processing = false)`
or even
qus.putTestQuestionResponses().then(() => processing = false).catch(() => processing = false)

Categories

Resources