I have created a small react App that presents documents from a SharePoint site.
To make the App work correctly I am having to add setTimeouts, but I know there must be a better way using callbacks or promises or something >.<
My knowledge is lacking, so could somebody please point me in the right direction?
// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
// Updates dropdown select
this.setState({ selectedOption: e.target.value, isLoading: true });
// Checks security permissions - MAKES A GET REQUEST
setTimeout(() => {
this.getSecurityGroupUsers();
}, 1000);
// Ghecks if user can access docs
setTimeout(() => {
this.checkForDocAccess();
}, 2000);
// Gets documents - MAKES A GET REQUEST
setTimeout(() => {
this.getDocuments();
}, 4000);
// Delete Mark as Reviewed property THEN displays docs
setTimeout(() => {
this.hideMarkAsReviewed();
}, 8000);
}
One of the functions:
// Grabs the documents
public getDocuments() {
axios
.get("https://bpk.sharepoint.com/_api/search/query?querytext='owstaxIdDocumentx0020Owner:" + this.state.selectedOption + "'&trimduplicates=true&rowsperpage=100&rowlimit=1000&selectproperties='LastReviewDateOWSDATE,ScheduledReviewDateOWSDATE,Path'",
{ params:{},
headers: { 'accept': 'application/json;odata=verbose' }
})
.then(response =>
response.data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results.map(document => ({
Type: this.checkFile(document),
Name: document.Cells.results[6].Value.split("/").pop(),
'Scheduled Review Date': document.Cells.results[8].Value.slice(0,-11),
Path: document.Cells.results[6].Value.replace('https://bpkintelogydev.sharepoint.com', ''),
Site: document.Cells.results[6].Value.split('/').slice(4).slice(0,1),
'Last Review Date': document.Cells.results[7].Value.slice(0,-11),
View: <a href="#" onClick={()=>window.open(document.Cells.results[6].Value + '?web=1&action=edit')}>View</a>,
'Mark as Reviewed': <a href='#'>Mark</a>
}))
)
.then(documents => {
this.setState({documents, isLoading: true});
})
.catch(error => {
//console.log(error);
});
}
Yes, callbacks or promises are what you do here. Promises would be the more modern way (not least so you can use async/await with them). setState provides a callback for when the state is set if you want that (it's an optional second argument to setState). Then your various functions (getSecurityGroupUsers, etc.) would return promises that they fulfill or reject.
In your getDocuments, for instance, you want to:
return the result of calling then, and
Don't have an error handler, leave that to handleDropdownChange
...and similar in the others. For any that don't already have a promise to chain on (getDocuments has the one from axios) because they use a callback-style API you can create a promise and fulfill/reject it yourself (see this question's answers for details there).
Doing that, handleDropdownChange might look something like:
// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
// Updates dropdown select
this.setState({ selectedOption: e.target.value, isLoading: true });
this.getSecurityGroupUsers()
.then(() => this.checkForDocAccess())
.then(() => this.getDocuments())
.then(() => this.hideMarkAsReviewed())
.catch(error => {
// ...handle/report error...
});
}
or if you didn't want to start those until the state had been changed initially:
// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
// Updates dropdown select
this.setState(
{ selectedOption: e.target.value, isLoading: true },
() => {
this.getSecurityGroupUsers()
.then(() => this.checkForDocAccess())
.then(() => this.getDocuments())
.then(() => this.hideMarkAsReviewed())
.catch(error => {
// ...handle/report error...
});
}
);
}
In this particular case, async/await doesn't help much, but perhaps some. I assume handleDropdownChange shouldn't be async since it's an event handler and so nothing would handle the promise it would return, so:
// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
// Updates dropdown select
this.setState({ selectedOption: e.target.value, isLoading: true });
(async () => {
try {
await this.getSecurityGroupUsers();
await this.checkForDocAccess();
await this.getDocuments();
await this.hideMarkAsReviewed();
} catch (error) {
// ...handle/report error...
}
})();
}
Notice how the entire body of the async function is in a try/catch, because nothing hooks its promise, so it's important we handle errors directly. (Ensure that "handle/report error" doesn't throw an error. :-) )
You can use Async await for waiting promise to resolve
public handleDropdownChange = async e => {
this.setState({ selectedOption: e.target.value, isLoading: true });
await this.getSecurityGroupUsers();
await this.checkForDocAccess();
await this.getDocuments();
await this.hideMarkAsReviewed();
}
You need a function call back!
function functionOne(x) { return x; };
function functionTwo(var1) {
// some code
}
functionTwo(functionOne);
Use async await and a simple sleep function to wait between them if it is required
const sleep = time => new Promise(res => {
setTimeout(res, time);
});
async handleDropdownChange(e) {
// Updates dropdown select
this.setState({ selectedOption: e.target.value, isLoading: true });
// Checks security permissions - MAKES A GET REQUEST
await sleep(1000);
await this.getSecurityGroupUsers();
await sleep(1000);
// Ghecks if user can access docs
await this.checkForDocAccess();
await sleep(1000);
// Gets documents - MAKES A GET REQUEST
await this.getDocuments();
await sleep(1000);
// Delete Mark as Reviewed property THEN displays docs
await this.hideMarkAsReviewed();
}
// But you methods should return a promise like this
getDocuments = () => {
return fetch('some url').then(res => res.json());
}
// Or something like
getSecurityGroupUsers = async () => {
const data = await axios('some url');
return data.message;
}
Related
This question already has answers here:
await setTimeout is not synchronously waiting
(2 answers)
Closed last month.
I have a async fetch function that waits 2 seconds and returns a object:
async function fetch() {
var object;
await setTimeout(() => { object = { name: 'User', data: 'API Data' } }, 2000);
return object;
}
I want to display the object when the initialization is completely done (after 2 seconds)
fetch().then((val) => {
console.log("DONE!");
console.log(val.name);
}).catch((err) => {
console.log("ERROR!");
console.log(err);
});
The code prints both DONE and ERROR Cannot read properties of undefined (reading 'name')
I have tried with Promise, no luck
let promise = new Promise((resolve, reject) => {
let request = fetch();
if (request !== undefined)
resolve(request);
else
reject(request);
}).then((val) => {
console.log(val);
});
How can I properly check that fetch() has returned a value before printing without changing the inside of the function. I can delete the async and await in it but I am unable to edit it (I.E. adding a Promise inside)
Based on requirement
I can delete the async and await in it (fetch function) but I am unable to edit it (I.E. adding a Promise inside)
The only way I see is to override window.setTimeout function to make it to return a promise. That way you will be able to await it and there will be no need to modify your fetch function.
const oldTimeout = window.setTimeout;
window.setTimeout = (fn, ms) => {
return new Promise((resolve, reject) => {
oldTimeout(() => {
fn();
resolve();
}, ms);
});
};
async function fetch() {
var object;
await setTimeout(() => {
object = { name: "User", data: "API Data" };
}, 2000);
return object;
}
fetch()
.then((val) => {
console.log("DONE!");
console.log(val.name);
})
.catch((err) => {
console.log("ERROR!");
console.log(err);
});
NOTE: For anyone without this requirement - please, use other answers to this question or check await setTimeout is not synchronously waiting for additional details/explanations. This kind of overridings are very confusing due to everyone expect common and well-known functions to behavior in a way described in the docs.
You cannot await the setTimeout function, this is because your function returns undefined. You have used the promise in the wrong way. Below code will fix your issue.
function fetch() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name: "User", data: "API Data" });
}, 2000);
});
}
fetch()
.then((val) => {
console.log("DONE!");
console.log(val.name);
})
.catch((err) => {
console.log("ERROR!");
console.log(err);
});
And remember that there is no need to change the setTimeout function.
The problem is that setTimeout does not actually return a promise, which means you cannot use await with setTimeout, that's why the var object; is returned instantly as undefined.
To solve this issue, you simply need to wrap setTimeout around a promise.
Like so:
function setTImeoutAwait(time) {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
}
You can then use it like this:
async function fetch() {
var object;
await setTImeoutAwait(1000).then(() => {
object = { name: "test" };
});
return object;
}
I'm trying to add an async/await in my code to have the app wait for the execution of a function to invoke an other one, but I can't seem to be able to figure out where my code is wrong.
I have an API call in a redux action, this is the action
export const editSecondaryCategory = (text) => {
return (dispatch) => {
let obj = {
text
};
axios
.put(
`${ROOT_URL}/...`,
obj
)
.then(() => {
dispatch({ type: EDIT_NOTE, payload: [...annotations] });
dispatch(loadAllAnnotations(cid, uuid));
})
.catch((error) =>
dispatch(
notifSend({
message: error.message,
kind: "danger",
dismissAfter: 2000,
})
)
);
};
};
I want, in my component, to wait after this action is completed to call an other function to update the state. Ideally, it should be something like this (I guess?):
async updateCategory() {
// do the thing I have to wait for
const propsUpdate = await this.props.editSecondaryCategory(text);
// if you've done the thing then call the function to update the state
if (updatedCat) {
this.updateStateArray();
}
}
And I'd call this.updateCategory() inside my component after the user is done editing the information.
Clearly, this code does not work. I know it's wrong, I just don't know why. Basically I have no clue what to write inside updateCategory() to make this work.
Please help lol
You need to rewrite editSecondaryCategory function to make it async.
export async function editSecondaryCategory(text){
return (dispatch) => {
let obj = {
text
};
axios
.put(
`${ROOT_URL}/...`,
obj
)
.then(() => {
dispatch({ type: EDIT_NOTE, payload: [...annotations] });
dispatch(loadAllAnnotations(cid, uuid));
})
.catch((error) =>
dispatch(
notifSend({
message: error.message,
kind: "danger",
dismissAfter: 2000,
})
)
);
};
};
Currently, your function is not an async function do the above changes and check.
I have this problem where in, user must be redirected to respective dashboards on successful log-in. Say user has accounts in two profiles "p1" and "p2" . After Sign-In success, I am making fetch API to see if user has entries the corresponding profiles.
1)Say if a user has entries in p1, I need to redirect him to "p1" dashboard ;
2) if entries in p2, then redirect him to "p2" dashboard.
3)If no entries are present neither in p1 or p2, then redirect to configuration page where he can make some entries.
4) If in both profile, then user will be asked to select a respective dashboard
Now In my case, the code that I have written is not working as expected. Even though I have no accounts in "p2" but have accounts in "p1" its taking me to configuration page. Can someone help what is wrong in this code?
Note that fetch call just works fine! It gives me array of items. If no items present it returns an empty array
// On login
handleLogin = async () => {
// after successful signin
await Promise.all([
this.setDashboard("p1"),
this.setDashboard("p2"),
]);
this.navigateToDashboards();
};
setDashboard = async (profile) => {
let response, value;
let type = profile;
if (type === "p1") {
response = await this.fetchUserAccounts(type);
value = !!response && response.length ? true : false;
this.setState({ isP1: value });
} else {
response = await this.fetchUserAccounts(type);
value = !!response && response.length ? true : false;
this.setState({ isP2: value });
}
};
fetchUserAccounts = async (type) => {
try {
let response = await fetch({
url: "/fetch/entries",
method: "POST",
body: {
profile: type,
}
);
return response.json();
} catch (error) {
console.log(error);
}
};
navigateToDashboards = () => {
let { isP1, isP2 } = this.state;
if (isP1 && isP2) {
// CODE FOR PROMPTING WHICH DASHBOARD TO GO
} else if (!isP1 && !isP2) {
this.props.history.push("/configpage");
} else if (isP1) {
this.props.history.push("/p1dashboard");
} else if (isP2) {
this.props.history.push("/p2dashboard");
}
};
There are some issues with the code you wrote above and they probably go deeper than we can see in the snippet – why having the logic for the dashboard type on the client-side instead of having it sent from the server?
There is also cloudType that is not specified in the setDashboard method.
I do not know what you are trying to achieve so I'm guessing – you should probably use fetchUserAccounts inside componentDidMount and save the response in the state.
I've came up with something like this with one TODO: in the code:
class X extends React.Component {
constructor(props) {
super(props);
this.state = {
p1: null,
p2: null,
};
}
async componentDidMount() {
const [p1, p2] = await Promise.all([
this.fetchUserAccounts('p1'),
this.fetchUserAccounts('p1'),
]);
// NOTE: setState has a second parameter, which is a callback – this way you make sure that the state has been updated – read more here:
// https://reactjs.org/docs/react-component.html#setstate
this.setState(s => ({ ...s, p1, p2 }), this.navigateToDashboards);
}
fetchUserAccounts = async (type) => {
try {
let response = await fetch({
url: "/fetch/entries",
method: "POST",
body: {
profile: type,
},
});
return response.json();
} catch (error) {
console.log(error);
}
};
navigateToDashboards = () => {
const { history } = this.props;
const { p1, p2 } = this.state;
// TODO: Implement your logic how you decide
if (isP1 && isP2) {
// CODE FOR PROMPTING WHICH DASHBOARD TO GO
return;
}
if (isP1) return history.push('/p1dashboard');
if (isP2) return history.push('/p2dashboard');
return history.push('/configpage');
}
}
If this does not work at least have a look at the code structure. There are places where you do not require the else statement. Use the function version of the setState. Make use of object/array desctrucuring. You should also probably read about what is false and true in the JS world based on the !!response && response.length ? true : false; lines – or maybe there is something I'm missing out here.
The problem is you're using async / await wrong.
await works on promises- you give it a promise, and it waits for it to resolve / reject. If you do not give it a promise- its like passing it an immediately resolved promise.
Your setDashboard function does not reutrn a promise, so what happens is that this code:
await Promise.all([
this.setDashboard("p1"),
this.setDashboard("p2"),
]);
actually resolves immediately, but the setDashboard functions are not executed up until this.navigateToDashboards();. why? because they are defined as async, which puts them in event loop. Then comes in the await, but it immediately resolves as the functions inside does not return a promise (well, it does, because it is async, but its like writing return promise.resolve() as the first line- so the await does nothing only placing the function in event loop)- and the code continues to execute in synchronous fashion until it reaches the end of the function (e.g- executing this.navigateToDashboards();, and only then goes to event loop and execute the setDashboards functions.
On the same subject, response = await this.fetchUserAccounts(type); line also has redundant await, because fetchUserAccounts() does not return a promise.
To correctly use async / await- make sure your async function return a promise, for example:
setDashboard = async (profile) => {
return new Promise( resolve, reject => {
let response, value;
let type = profile;
if (type === "p1") {
response = await this.fetchUserAccounts(type);
value = !!response && response.length ? true : false;
this.setState({ isP1: value });
resolve();
} else {
response = await this.fetchUserAccounts(cloudType);
value = !!response && response.length ? true : false;
this.setState({ isP2: value });
resolve();
}
})
};
as for fetchUserAccount:
fetchUserAccounts = async (type) => {
return new Promise( resolve, reject => {
try {
let response = await fetch({
url: "/fetch/entries",
method: "POST",
body: {
profile: type,
}
);
resolve(response.json());
} catch (error) {
console.log(error);
reject(error);
}
}
};
Notice that you will now have to handle promise rejections using .catch, otherwise you'll get unhandled promise rejection error.
I made my componentWillMount() async. Now I can using await with the setState.
Here is the sample code:
componentWillMount = async() => {
const { fetchRooms } = this.props
await this.setState({ })
fetchRooms()
}
So question here is this.setState returns promise because I can use await with it?
Edit
When I put await then it runs in a sequence 1, 2, 3 And when I remove await then it runs 1, 3, 2??
componentWillMount = async() => {
const { fetchRooms } = this.props
console.log(1)
await this.setState({ } => {
console.log(2)
})
console.log(3)
fetchRooms()
}
You can promisify this.setState so that you can use the React API as a promise. This is how I got it to work:
class LyricsGrid extends Component {
setAsyncState = (newState) =>
new Promise((resolve) => this.setState(newState, resolve));
Later, I call this.setAsyncState using the standard Promise API:
this.setAsyncState({ lyricsCorpus, matrix, count })
.then(foo1)
.then(foo2)
.catch(err => console.error(err))
setState is usually not used with promises because there's rarely such need. If the method that is called after state update (fetchRooms) relies on updated state (roomId), it could access it in another way, e.g. as a parameter.
setState uses callbacks and doesn't return a promise. Since this is rarely needed, creating a promise that is not used would result in overhead.
In order to return a promise, setState can be promisified, as suggested in this answer.
Posted code works with await because it's a hack. await ... is syntactic sugar for Promise.resolve(...).then(...). await produces one-tick delay that allows to evaluate next line after state update was completed, this allows to evaluate the code in intended order. This is same as:
this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, () => {
console.log(2)
})
setTimeout(() => {
console.log(3)
});
There's no guarantee that the order will stay same under different conditions. Also, first setState callback isn't a proper place to check whether a state was updated, this is what second callback is for.
setState does not return a promise.
setState has a callback.
this.setState({
...this.state,
key: value,
}, () => {
//finished
});
It does not return a promise.
You can slap the await keyword in front of any expression. It has no effect if that expression doesn't evaluate to a promise.
setState accepts a callback.
Don't think setState is returning a Promise but you can always do this
await new Promise( ( resolve ) =>
this.setState( {
data:null,
}, resolve )
)
or you can make some utility function like this
const setStateAsync = ( obj, state ) => {
return new Promise( ( resolve ) =>
obj.setState( state , resolve )
)
}
and use it inside a React.Component like this:
await setStateAsync(this,{some:'any'})
You can simple customize a Promise for setState
componentWillMount = async () => {
console.log(1);
await this.setRooms();
console.log(3);
};
setRooms = () => {
const { fetchRooms } = this.props;
return fetchRooms().then(({ room }) => {
this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, _ =>
console.log(2)
);
});
};
Or
setRooms = async () => {
const { fetchRooms } = this.props;
const { room } = await fetchRooms();
return new Promise(resolve => {
this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, _ =>
resolve()
);
});
};
Hope this help =D
Simpler way to answer this question is we can use promisify function present in pre-installed util library of node.js and then use it with the await keyword.
import {promisify} from 'util';
updateState = promisify(this.setState);
await this.updateState({ image: data });
I have a problem with rerender after getting result from web3 call - execution of smart contract. Code below:
this.setState({ loading: true });
await contractInstance.methods
.myMethod(params)
.send({ from: myAccount, gas: 10000000 })
.then(async function(receipt) {
let txHash = receipt.transactionHash;
...
// await saveToDb(thHash, ...)
this.setState({ dateToDisplay: myVar.publishDate, loading: false });
..
and the render is as below:
render() {
if (!this.state.loading) {
return (
...
{this.state.dateToDisplay}
I have other methods where this pattern works, but here I could not make it work. I tried to make setState async and await it, like:
setStateAsync(state) {
return new Promise(resolve => {
this.setState(state, resolve);
});
}
But does not help either.
Any ideas?
Why do you combine await and promises?
The point of await is to stop the execution at that point and wait for the promise to resolve. The const result = await promise; is a replacement for promise.then(result => ...).
You could do this:
const receipt = await contractInstance.methods
.myMethod(params)
.send({ from: myAccount, gas: 10000000 });
let txHash = receipt.transactionHash;
...
// await saveToDb(thHash, ...)
this.setState({ dateToDisplay: myVar.publishDate, loading: false });
In my opinion, this makes the code less complex and easier to follow what's going on and make sense of it.
You need to change Async function to arrow function or bind the function so that this will be available inside that function
await contractInstance.methods
.myMethod(params)
.send({ from: myAccount, gas: 10000000 })
.then(async receipt => {
let txHash = receipt.transactionHash;
...
// await saveToDb(thHash, ...)
this.setState({ dateToDisplay: myVar.publishDate, loading: false });
Or bind it
await contractInstance.methods
.myMethod(params)
.send({ from: myAccount, gas: 10000000 })
.then(async function(receipt) {
let txHash = receipt.transactionHash;
...
// await saveToDb(thHash, ...)
this.setState({ dateToDisplay: myVar.publishDate, loading: false });
}.bind(this))