I'm having a trouble wrapping my head around this problem: suppose I have a form where I want to handle onSubmit using async callback through event listener and I want to prevent the default behavior. I don't understand why this works:
form.addEventListener('submit', async(event) => {
// do stuff
event.preventDefault();
await asyncFetching(); // first await
// do more stuff
}
And this doesn't:
form.addEventListener('submit', async(event) => {
// do stuff
await asyncFetching(); // first await
event.preventDefault();
// do more stuff
}
I've come to understand from event.preventDefault in async functions that event will have happened by the time await unblocks. But I don't get it. This is what I expect:
Event is triggered once we click the button
Event handler fires up
Event handler finishes execution
Event has happened
What am I missing here?
After some time experimenting and reading up further I think I get it now.
From MDN:
The body of an async function can be thought of as being split by zero or more await expressions. Top-level code, up to and including the first await expression (if there is one), is run synchronously. In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously.
Inside the event handler everything is executed synchronously. Once first await is reached, it is the same as returning a Promise in pending state that has just executed asyncFetching() and has event.preventDefault() inside of its then() block. This return indeed signals that event handler callback has finished its execution, so by the time when asyncFetching() fails or succeeds, event.preventDefault() will still execute, but it won't have any effect because its eventPhase will be 0 meaning Event.NONE ("No event is being processed at this time").
Related
I would like to run this code with babel:
redisClientAsync.delAsync('key');
return await someOtherAsyncFunction();
inside an async function without await the first line. is this OK?
how else can I run something that I don't care?
Can I just fire the non-promisified function del('key',null) without a callback?
Yes, you can do that, and it will run the two asynchronous functions in parallel. You've just created a promise and thrown it away.
However, this means that when the promise is rejected you won't notice. You'll just get an unhandledRejection eventually which will crash your process if not handled.
Is this OK? How can I run something that I don't care?
Probably it's not OK. If you truly wouldn't care, you hadn't run it in the first place. So you should be clear and explicit what you care about (and what not):
do you want to wait? (for side effects)
do you need the result?
do you want to catch exceptions?
If you only want to wait and don't care for the result value, you can easily throw away the result:
void (await someAsyncFunction()); // or omit the void keyword,
// doesn't make a difference in an expression statement
If you don't care about exceptions, you can ignore them using
… someAsyncFunction().catch(function ignore() {}) …
You can throw that away, await it, do anything with it.
If you want the result, you have to await it. If you care about exceptions, but don't really want to wait, you may want to execute it in parallel with the following functions:
var [_, res] = await Promise.all([
someAsyncFunction(), // result is ignored, exceptions aren't
someOtherAsyncFunction()
]);
return res;
inside an async function without await the first line. is this OK?
Yes, there are cases where you'd want to do this which are perfectly reasonable. Especially where you don't care about the result - one example is an analytics tracking operation that should not interfere with business critical code.
how else can I run something that I don't care?
In many ways, however simply calling the promise function works. Your del without a callback would probably work in this case but some functions don't guard against not passing callbacks, so you can pass an empty function instead (.del('key', () => {})).
You do want to however make sure that you know about it failing, even if you don't want to disrupt the operation of code - so please consider adding a process.on("unhandledRejection', event handler to explicitly ignore these particular exceptions or suppress them via:
redisClient.delAsync('key').catch(()=>{});
Or preferably, something like:
redisClient.delAsync('key').catch(logErr);
From all the research I've made so far, I think it's fine to do it, as long as you guarantee that the function you are not awaiting for guarantees a way to handle its own errors in case that happens. For example, a try-catch wrapping the whole function body, like you see in the following snippet for the asyncFunction.
It doesn't matter if the function throws synchronously or asynchronously. It guarantees the your mainFunction will complete no matter what. That's the key point here.
If you don't guarantee that, you have to risks:
If it throws synchronously, your main function will not complete.
If it throws asynchronously, you'll get an unhandled excepction
// THIS IS SOME API CALL YOU DON'T WANT TO WAIT FOR
const mockAPI = () => {
console.log("From mockAPI");
return new Promise((resolve,reject) => {
setTimeout(() => reject("LATE THROW: API ERROR"), 500);
});
};
// THIS IS THE SOME ASYNC FUNCTION YOU CALL BUT NOT AWAIT FOR
const asyncFunction = async (syncThrow) => {
try {
console.log("Async function START");
if (syncThrow) throw new Error("EARLY THROW");
await mockAPI();
console.log("Async function DONE");
}
catch(err) {
console.log("From async function catch");
console.log(err.message || err);
return;
}
};
// THIS IS YOUR MAIN FUNCTION
const mainFunction = async (syncThrow) => {
try {
console.clear();
console.log("Main function START");
asyncFunction(syncThrow);
console.log("Main function DONE <<< THAT'S THE IMPORTANT PART");
}
catch(err) {
console.log("THIS WILL NEVER HAPPEN");
console.log(err);
}
};
<div>
<button onClick="mainFunction(true)">Sync throw</button>
<button onClick="mainFunction(false)">Async throw</button>
</div>
Not in Node.js.
Node does not wait for ever-pending Promises. If other tasks are already completed and there is nothing left in the event loop, the Node process will be terminated even though there exists pending promise.
For the following script, if someOtherAsyncFunction() get resolved in 5 seconds, but redisClientAsync.delAsync('key') takes 10 seconds to execute, the Node process will be terminated after 5 seconds in theory, before the first line is resolved.
async function doSomething() {
redisClientAsync.delAsync('key');
return await someOtherAsyncFunction();
}
await doSomething();
I have a piece of code which i wish to run before the browser refreshes or redirects
window.addEventListener("beforeunload", async (event) => {
await this.save()
});
the method i'm running involves parsing JSON , updating it , and saving it back to the sessionStorage.Is there a way to make sure this.save() completes before unloading? or at least have it run in the background while the page redirects.
I have tried removing the async and await code, but does not work.
EDIT here is the save method:
saveCanvas = async (call_back_) => {
const FlowState = JSON.parse(sessionStorage.getItem('FLOWSTATE'))
...
...
await html2canvas(div).then((screenshot) => {
...
...
sessionStorage.setItem('FLOWSTATE', JSON.stringify(FlowState))
})
}
}
beforeload is the final event before leaving the page(reload leave and reenter for javascript runtime). if you use async functions(include async-await, promise, settimeout) in the event listening functions, it can't be executed anymore.
I think there may be async executions in save function, so it won't work even if you remove async-await keyword.
EDITED
Now, I actually suggest you change the layout.. the logic is to have a while loop awaiting saveCanvas(because while behaves well with await). So when the page is unloading, the last html2canvas(div) would be saved
saveCanvas = async(call_back_) => {
const FlowState = JSON.parse(sessionStorage.getItem('FLOWSTATE'))
await html2canvas(div)
sessionStorage.setItem('FLOWSTATE', JSON.stringify(FlowState))
}
let toRun=1
while(i){await saveCanvas()}
window.addEventListener("beforeunload", (event) => { toRun=0 });
Today I have had this weird problem where in this function actions.order.capture(), it doesn't fire:
actions.order.capture();
this.props.handlePaymentComplete(data.orderID);
But when I use it like this it works:
actions.order.capture().then(() => {this.props.handlePaymentComplete(data.orderID);})
I understand that in the later case it waits for actions.order.capture() to end, and I would have understood if there was any connection between the result of actions.order.capture and the later function, but they are not dependent on each other's value, and when the second function fired before waiting to the first one to end, the first one doesn't fire at all.
What may be the case?
Looks to me it is asynchronous, .then() is called after the Promise resolved.
You should use the code bellow in an async function :
await actions.order.capture();
this.props.handlePaymentComplete(data.orderID);
Make sure it is in an async function. If not, the function will be called but the program won't stop to wait the answer ... So it will execute everything you wrote then close the process and you'll never get an answer. "await" makes the program wait for the answer, but it must be in an async function like this:
async function () {
await .....
}
From what I understand in react versions 16 (current) and under, setState calls are batched IFF they are made in either component lifecycle events or event handlers. Otherwise, in order to batch calls there is an opt in ReactDOM.unstable_batchedUpdates can be used.
If an event handler is an async function though, the browser will fire the event handler but then a promise will be returned, thus the actual event handler Promise callback won't be run until the next microtasks are picked up in the event loop. In other words, the setState updates do not actually occur in the immediate event handler.
Does this mean that we need to opt into ReactDOM.unstable_batchedUpdates if we want setState updates to be batched in event handlers?
After researching, I believe the answer is that the initial portion of the async event handler (that ends up translating to the executor function of the Promise that is returned underneath the hood) will have setState updates batched, but not anything after any await calls.
This is because everything in the async function body before the first await is translated to the executor function, which is executed within the browser event handler for the event, but everything after ends up as a chained Promise callback for the initial executor function, and these chained callbacks are executed on the microtask queue.
This is all because async () => {} is translated to something like return new Promise().then() where each then is a callback created for code after an await statement.
const onClick = async e => {
// 1 and 2 will be batched
setState(1)
setState(2)
await apiCall()
// 3 and 4 will not be batched
setState(3)
setState(4)
}
Below call will be batched by React and will cause single re-render.
const onClick = (e) => {
setHeader('Some Header');
setTitle('Some Tooltip');
};
Without ReactDOM.unstable_batchedUpdates, React would have made 2 sync calls to re-render components. Now it will have single re-render with this API.
const onClick = (e) => {
axios.get('someurl').then(response => {
ReactDOM.unstable_batchedUpdates(() => {
setHeader('Some Header');
setTitle('Some Tooltip');
});
});
};
Additional Notes:
We used it once in our project and it worked seamless. But I personally prefer to make a state as an object and update things at once rather than this approach. But I understand it is not always possible.
Here we can see that it is in Experimental mode, so not sure if you should use it in production.
Update
Based on the OP comment, below is the version for async await in JavaScript.
const onClick = async e => {
setState(1);
setState(2);
await apiCall();
ReactDOM.unstable_batchedUpdates(() => {
setState(3);
setState(4);
});
}
The above code will trigger re-render 2 times. Once for update 1/2 and another for 3/4.
Is the following somehow possible?
async function doesSocketAgree(){
socket.emit('doesOtherSocketAgree', otherSocketId);
await socket.on('responseDoesSocketAgree', (answer)=>{
console.log('answer');
});
}
In socket.io you can use "acknowledgements" callbacks:
async function doesSocketAgree(){
await new Promise(resolve => {
socket.emit('doesOtherSocketAgree', otherSocketId, (answer) => {
resolve(answer);
});
});
}
https://socket.io/docs/#Sending-and-getting-data-acknowledgements
So you can use a single emit() and can trigger a callback.
That has the advantage you dont have to deal with memory leaks from register an event listener every time you call this function.
It is, but not that way. You'll have to wrap things in a promise so that you can "return" from your await once data comes in as part of your "on" handling:
async function doesSocketAgree(){
socket.emit('doesOtherSocketAgree', otherSocketId);
await new Promise(resolve => {
socket.on('responseDoesSocketAgree', answer => {
resolve(answer);
});
});
}
And you probably want to remove that listener before you call resolve() so that it doesn't keep on triggering, because every time you call doesSocketAgree() you'd be adding a new listener to the "on:responseSocketAgree" pile. So that'll end up going wrong pretty quickly without cleanup.
On that note, you probably want to emit your "does it agree?" with a random token that your on handler can verify is the one that's scoped to the current function call, "because async".