Resolve freezing in react while making calls - javascript

I am trying to set a spinner while my react app makes a few calls. I am writing a Dapp with web3 and solidity. While making calls to my smart contract I try to set state to show a spinner, but the whole app freezes while the calls to the contract are being made, after calls are made and values returned it sets the spinner and unsets the spinner real fast (you can see the value change console logging the boolean). The app unfreezes and proceeds. How can I get the spinner to set and the app not to freeze while the calls are made:
onSubmit(e) {
e.preventDefault();
// inProcessOfCalling sh
this.setState({ inProcessOfCalling: true });
...
const contractData = callContract(parseTestJson.steps, web3);
Promise.all(contractData).then((values) => {
...
// turn spinner off
this.setState({ inProcessOfCalling: false });
});
}
function callContract(callInfo, web3) {
const dataArr = [];
for (let i = 0; i < callInfo.length; i += 1) {
// ...
const getData = thisContract[callInfo[i].func].getData(
...callInfo[i].args,
{ from: callInfo[i].from },
);
// ...
const trans = web3.eth.sendTransaction({
to: callInfo[i].contract,
from: callInfo[i].from,
data: getData,
});
let receipt = web3.eth.getTransactionReceipt(trans);
returnObj = {
receipt,
testName: callInfo[i].name,
expectPassed: callInfo[i].expects.success === transactionPassed,
expectMessage: callInfo[i].expects.message,
};
dataArr.push({ error: false, returnObj });
}
return dataArr;
}
I dont think this is a web3 issue. I would assume it would freeze the app with making any multiple fetch calls to APIs.

You are already calling the callContract function which is heavy and the request here <- this is the one causing the hang
const contractData = callContract(parseTestJson.steps, web3);
it is already called before you put it into a Promise.all
and contractData is not a Promise at all, what you can do is return a Promise object inside that function like this
function callContract(callInfo, web3) {
return new Promise((resolve, reject) => {
const dataArr = [];
for (let i = 0; i < callInfo.length; i += 1) {
// ...
const getData = thisContract[callInfo[i].func].getData(
...callInfo[i].args, {
from: callInfo[i].from
},
);
// ...
const trans = web3.eth.sendTransaction({
to: callInfo[i].contract,
from: callInfo[i].from,
data: getData,
});
let receipt = web3.eth.getTransactionReceipt(trans);
returnObj = {
receipt,
testName: callInfo[i].name,
expectPassed: callInfo[i].expects.success === transactionPassed,
expectMessage: callInfo[i].expects.message,
};
dataArr.push({
error: false,
returnObj
});
}
resolve(dataArr);
}
}
Learn more about promise here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Related

Javascript reduce in other function

How can I get the data I parse from my JSON file to run through the reduce function to eliminate duplicates and then beeing available by calling the getFiilteredData() function?
async function getFilteredData() {
return new Promise((resolve) => {
oWebViewInterface.on("loadData", function (data) {
var schwellWerte = data.monitor;
var monitorData = data.data.reduce((arr, d) => {
if (arr.find((i) => i.zeitstempel === d.zeitstempel)) {
return arr;
} else {
return [...arr, d];
}
}, []);
resolve(monitorData); // resolve the promise with the data
//can I do: resolve(monitorData, schwellWerte) to resolve both?
});
});
}
Doing it like this, results in "Uncaught TypeError: Cannot read property '0' of undefined" for the two last console.log() but the first works fine and logs the expected value.
The easiest way is to use a Promise and async/await. Wrap your asynchronous call in a Promise and await it at the client:
async function getFilteredData() {
return new Promise( resolve => {
oWebViewInterface.on("loadData", function (data) {
var monitorData = JSON.parse(data).reduce((arr, d) => {
if (arr.find((i) => i.zeitstempel === d.zeitstempel)) {
return arr;
} else {
return [...arr, d];
}
}, []);
resolve(monitorData); // resolve the promise with the data
});
});
}
and then when you call it just await the call
var filteredData = await getFilteredData();
console.log(filteredData[0].id);
Edit: I notice from your comments that in your code you're calling getFilteredData twice - this seems like a bad idea. Call it once. If you put the configuration of your chart into its own async method this gets easier
async function configChart(){
var data = await getFilteredData();
var werteArr = [];
var zsArr = [];
for (i = 0; i < data.length; i++) {
werteArr.push(data[i].wert);
zsArr.push(data[i].zeitstempel);
}
//defining config for chart.js
var config = {
type: "line",
data: {
labels: zsArr ,
datasets: {
data: werteArr,
// backgroundcolor: rgba(182,192,15,1),
},
},
// -- snip rest of config -- //
}
var ctx = document.getElementById("canvas").getContext("2d");
window.line_chart = new window.Chart(ctx, config);
}
window.onload = function () {
configChart(); // no need to await this. It'll happen asynchronously
};

RXJS Stream - How to wait for my STREAM to end and return DATA and then do my OPERATIONS

I have a Websocket Endpoint I am subscribing. I want to get that Data, and then operate on them.
CODE:
// Simple HTTP POST Request. Works Perfectly. I am Logged In to the API
const authenticationRequest = () => axios.post(authenticationUrl, {
user: username, password
})
.then((response) => response.data)
.catch((error) => console.error(console.error('Error Response', error)));
// WS Request. I need to wait for this to return my Data and then operate on them
const wsRequest = async () => {
// Getting the Auth Token. Working Perfectly.
const reqToken = await authenticationRequest();
// Hitting the ws Endplint. Working Perfectly.
const webSocketRequest = new WebSocket(topicDataUrl);
// Setting the Data for the First Message. Works Perfectly.
const firstMessage = {
token: reqToken,
stats: 2,
sql: "SELECT * FROM cc_payments LIMIT 100",
live: false
};
// Initialising an Empty Array. Works.
let websocketData = [];
// Opening the Endpoint
webSocketRequest.onopen = () => {
// Sending the first Message
webSocketRequest.send(JSON.stringify(firstMessage));
// On Each Message
webSocketRequest.onmessage = (streamEvent) => {
of(streamEvent).pipe(
map(event => JSON.parse(event.data)), // Parse the Data
filter(message => message.type === 'RECORD') // Filter the Data
).subscribe(
message => websocketData.push(message.data.value)// Adding each Value from each message to the Array.
);
};
};
console.log(JSON.stringify(websocketData), 'Websocket DATA'); // Empty Array
return websocketData;
};
Here I am calling it a few lines down, but still with no results. I get an empty Array.
(async function () {
const data = await wsRequest();
console.log(JSON.stringify(data), 'Data'); // Still Empty
}());
So, what am I doing wrong? Can someone, explain to me the problem? I mean I get the asynchronisity of things, but I am awaiting. I even tried setting a timeout but didn't work.
Is my stream correct? Maybe there is a problem there??
So, the RXJS Actions are asynchronous. So, I would need 2 Things.
- Close the Stream when Operationg Completed. (Tried takeUntil, takeWhile, but obviously was doing something wrong)
- Wait in order to return the Actual Data(WebsocketData).
UPDATE:
async function authenticationRequest() {
const AuthenticateWith = await axios.post(authenticationUrl, {
user: username,
password
})
.then(response => response.data)
.catch((error) => console.error('Error:', error));
return AuthenticateWith;
}
const webSocketRequest = new WebSocket(topicDataUrl);
const websocketData = new Array;
const subject = new Subject();
async function requestToWSEndpoint() {
const reqToken = await authenticationRequest();
const firstMessage = {
token: reqToken,
stats: 2,
sql: "SELECT * FROM cc_payments LIMIT 100",
live: false
};
webSocketRequest.onopen = () => {
webSocketRequest.send(JSON.stringify(firstMessage));
webSocketRequest.onmessage = (streamEvent) => {
JSON.parse(streamEvent.data).type === 'RECORD' && websocketData.push(JSON.parse(streamEvent.data).data.value);
subject.next(websocketData);
JSON.parse(streamEvent.data).type === 'END' && subject.complete();
};
};
};
(async function () {
requestToWSEndpoint();
const chartData = subject.subscribe((event) => console.log(event, 'Event')); // Event Adds to the Array and at the End I have all my Items(Filtered). It printed 100 Times.
console.log('ARRAY', chartData); // This returns [Subscriber {closed: false, _parentOrParents: null, _subscriptions: Array(1), syncErrorValue: null, syncErrorThrown: false, …}]. This is what I want. The Array.
}());
My suggestion as outlined in my comment:
const subject = new Subject();
...
}
(async function () {
wsRequest();
subject.pipe(finalize(()=> {console.log('ARRAY', websocketData);})).subscribe();
}());
Actually you dont even need a function for what you do in wsRequest, except for const reqToken = await authenticationRequest();. The rest can easily be globally scoped.
You should take a look at the documentation for rxjs operators.

automatically perform action on Promise method resolve

I have a MobX data store, called BaseStore that handles the status of an API request, telling the view to render when request is in progress, succeeded, or failed. My BaseStore is defined to be:
class BaseStore {
/**
* The base store for rendering the status of an API request, as well as any errors that occur in the process
*/
constructor() {
this._requestStatus = RequestStatuses.NOT_STARTED
this._apiError = new ErrorWrapper()
}
// computed values
get requestStatus() {
// if there is error message we have failed request
if (this.apiError.Error) {
return RequestStatuses.FAILED
}
// otherwise, it depends on what _requestStatus is
return this._requestStatus
}
set requestStatus(status) {
this._requestStatus = status
// if the request status is NOT a failed request, error should be blank
if (this._requestStatus !== RequestStatuses.FAILED) {
this._apiError.Error = ''
}
}
get apiError() {
// if the request status is FAILED, return the error
if (this._requestStatus === RequestStatuses.FAILED) {
return this._apiError
}
// otherwise, there is no error
return new ErrorWrapper()
}
set apiError(errorWrapper) {
// if errorWrapper has an actual Error, we have a failed request
if (errorWrapper.Error) {
this._requestStatus = RequestStatuses.FAILED
}
// set the error
this._apiError = errorWrapper
}
// actions
start = () => {
this._requestStatus = RequestStatuses.IN_PROGRESS
}
succeed = () => {
this._requestStatus = RequestStatuses.SUCCEEDED
}
failWithMessage = (error) => {
this.apiError.Error = error
}
failWithErrorWrapper = (errorWrapper) => {
this.apiError = errorWrapper
}
reset = () => {
this.requestStatus = RequestStatuses.NOT_STARTED
}
}
decorate(BaseStore, {
_requestStatus: observable,
requestStatus: computed,
_apiError: observable,
apiError: computed,
})
That store is to be extended by all stores that consume API layer objects in which all methods return promises. It would look something like this:
class AppStore extends BaseStore {
/**
* #param {APIObject} api
**/
constructor(api) {
super()
this.api = api
// setup some observable variables here
this.listOfData = []
this.data = null
// hit some initial methods of that APIObject, including the ones to get lists of data
api.loadInitialData
.then((data) => {
// request succeeded
// set the list of data
this.listOfData = data
}, (error) => {
// error happened
})
// TODO: write autorun/reaction/spy to react to promise.then callbacks being hit
}
save = () => {
// clean up the data right before we save it
this.api.save(this.data)
.then(() => {
// successful request
// change the state of the page, write this.data to this.listOfData somehow
}, (error) => {
// some error happened
})
}
decorate(AppStore, {
listOfData : observable,
})
Right now, as it stands, I'd end up having to this.succeed() manually on every Promise resolve callback, and this.failWithMessage(error.responseText) manually on every Promise reject callback, used in the store. That would quickly become a nightmare, especially for non-trivial use cases, and especially now that we have the request status concerns tightly coupled with the data-fetching itself.
Is there a way to have those actions automatically happen on the resolve/reject callbacks?
Make an abstract method that should be overridden by the subclass, and call that from the parent class. Let the method return a promise, and just hook onto that. Don't start the request in the constructor, that only leads to problems.
class BaseStore {
constructor() {
this.reset()
}
reset() {
this.requestStatus = RequestStatuses.NOT_STARTED
this.apiError = new ErrorWrapper()
}
load() {
this.requestStatus = RequestStatuses.IN_PROGRESS
this._load().then(() => {
this._requestStatus = RequestStatuses.SUCCEEDED
this._apiError.error = ''
}, error => {
this._requestStatus = RequestStatuses.FAILED
this._apiError.error = error
})
}
_load() {
throw new ReferenceError("_load() must be overwritten")
}
}
class AppStore extends BaseStore {
constructor(api) {
super()
this.api = api
this.listOfData = []
}
_load() {
return this.api.loadInitialData().then(data => {
this.listOfData = data
})
}
}
const store = new AppStore(…);
store.load();
MobX can update data that is asynchronously resolved. One of the options is to use runInAction function
example code:
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
// after await, modifying state again, needs an actions:
runInAction(() => {
this.state = "done"
this.githubProjects = filteredProjects
})
} catch (error) {
runInAction(() => {
this.state = "error"
})
}
}
You can read more in official documentation: Writing asynchronous actions

async/await works with real data, but not with local test data

https://snack.expo.io/#haosmark/kw-companion2
in the file api.js, when I set debugMode to false on line 17, the app works without a hiccup. It fetches a list of players, rooms, and matches, however if I set it to debugMode false, the app is showing 0 players online, and duplicates active match on "hosted rooms" and "active games" tabs. How do I correctly simulate network request with fake data? The above is complete code, and the following is the function in question:
export const fetchDataAsync = async () => {
playerList = {};
activeGames = {};
stagingRooms = {};
//console.log('fetching data')
if (debugMode) {
result = await mockDataSmall.cnc3kw;
} else {
const response = await fetch(url);
result = (await response.json()).cnc3kw;
}
//console.log('here0')
Object.keys(result.users).map(parsePlayers);
result.games.playing.map(parseRooms(ACTIVE_MATCH));
result.games.staging.map(parseRooms(STAGING_ROOM));
//console.log('here1')
if (debugMode) {
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1000);
});
}
//console.log('here2')
return { playerList, activeGames, stagingRooms };
};
fakeData is in the same format as the live data, but a smaller sample

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