In a react component I havel the following code:
const handleButton = async () => {
const resp = await updateProject(projectInfo);
setIsProjectModalVisible(false)
};
updateProject() is a POST request to a server and setIsProjectModalVisible is a setState function from the parent component that was passed as a prop.
I was having a strange behavior: with the previous code, the Modal wouldn't be hidden (setIsProjetModalVisible(false) wouldn't run), but if I swapped the two lines:
const handleButton = async () => {
setIsProjectModalVisible(false)
const resp = await updateProject(projectInfo);
};
it would work as expected and the Modal would become invisible.
After a bit of research, I identified the problem: the server wasn't responding to updateProject(). Fixing that, the second line of code would now result in the expected behavior.
Even though the problem is solved, I'd like to understand that behavior: since the server wasn't responding, I might expect an infinite wait in the await line, but while debugging, it would just skip that line and no error was shown. Why is that? Thanks!
When you call updateProject, it returns a Pending Promise. At first a pending promise wouldn't block the next following code setIsProjectModalVisible(false).
But when you declare your function as async, and mark await to your promise you are stating that following code should only execute when your promise resolves. If the promise hangs on pending your code is blocked until the promise resolves.
The promise could also rejects (you get a server timeout error or something else goes wrong), which would throw an error, but your code wouldn't be executed. Your app would crash, unless you had wrapped your promise in a try/catch block (which is recommended), where you would handle the error at the catch block properly.
const handleButton = async () => {
try {
const resp = await updateProject(projectInfo);
// if it rejects following code doesn't execute. jumps to catch block
setIsProjectModalVisible(false)
} catch (error) {
// handle error here
console.log(error)
}
};
Depending on how your updateProject is written, when a POST request inside it fails in some way, it probably does not resolve (i.e. you don't catch the error) and thus your const resp = await updateProject(projectInfo); never actually gets anything back. That, of course, results in this part setIsProjectModalVisible(false) never being reached. Hence your modal does not disappear.
So that should explain the behavior. As for fixing it, well, just catch the error and still resolve the function. So something like this would work:
const updateProject = (project_data) => {
return axios.post('url', project_data)
.catch(e => {
console.log(e);
return "Error occurred";
})
}
This way even when your post request bombs, your await will still get something back and won't stop the code from contunuing to execute.
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'm hoping someone can suggest a better method for what I'm trying to achieve.
While performing a rather long and complicated webflow using puppeteer, occasionally an error will occur that disrupts the actions I'm trying to take on the page. There's nothing I can do about it in these situations other than return a useful error. However it doesn't happen often enough to explicitly wait and try to catch it after each step in my code.
The solution I have is:
async function runCode() {
try {
const page = browser.open(url)
listenForError(page)
await longWebFlow()
} catch (err) {
return err.message
}
}
async function listenForError(page) {
await page.waitForXPath(errorMessageXPath)
throw new Error('Error found!')
}
try {
await runCode()
} catch (err) {
console.log(err.message)
// should print('Error found')
}
Obviously, the unawaited listenForError call won't be caught in the try/catch, but I also cant await the call, or else I'll never get to the main part of the code.
The code works to short-circuit the process and return, since the error occurred, but how can I refactor this to catch the error message?
It seems like you want to wait until either the error is thrown or the longWebFlow() finishes - basically doing both concurrently. That's a perfect use case for promises with Promise.race:
async function runCode() {
const page = browser.open(url)
await Promise.race([
listenForError(page),
longWebFlow(),
]);
}
You could also use Promise.all if you made longWebFlow cancel the listenForError and fulfill the promise.
Either way, since you can properly await the promises now, the error also will be caught in the try/catch, as it should.
If you can't await the async operation then the only other "follow up" is with callbacks like .then() and .catch(). For example, you can catch the error here:
listenForError(page).catch(e => console.log(e));
This won't await the operation, it's just supplying a callback for whenever that operation fails at whatever point in the future.
I'm having trouble understanding how to properly end a firestore trigger. From what I read from this and this, it seems you should only return null to end a function if there's no async code such as to quickly end if a condition isn't met. When I return null in my scenario below, it seems to work fine. Is there a better practice for what I'm doing that I'm missing?
I need to log my own custom error message which why I need the catch. I know I could return Promise.all here instead of null in the try block, but this is just sudo code for my scenario.
export const delacc = functions.auth.user().onDelete(async (user) => {
const userUID = user.uid;
try{
await admin.firestore().collection("users").doc(userUID).delete();
await admin.firestore().collection("spam").doc(userUID).delete();
await admin.firestore().collection("photos").doc(userUID).delete();
return null;
}catch(error){
functions.logger.error(error)
return null
}
});
There's no hard requirement to return null. In fact, async functions always return a promise, no matter what you do inside the function. The promise it returns is based on the completion of any other promises that you await during processing. Even if you explicitly return null, the function is still actually just returning a promise that is immediately fulfilled with the value null. It has no effect on the final outcome, since Cloud Functions onDelete triggers don't use the fulfilled value in any way. The important thing is that the function indeed returns a promise that indicates when all the work is complete (and, as I stated, async functions always do that if you use await correctly inside the function on all async work).
When you're not using async/await, I advise programmers to always return a promise, or null if there is no async work. The null there is an explicit way to tell the reader of your code that you do not intend for Cloud Functions to wait for any async work before fully terminating the function. It helps also helps satisfy eslint or TypeScript warnings, which will suggest to you that you should return something. The value itself isn't really important - it's what you're communicating to others about the termination of your function. Code readability is important if you work with others.
What I'd do is make the delete operations atomic—delete all of the documents or none of them—and return the promise returned by the batch since there isn't any other task performed in this function (which makes me think returning null isn't a necessary abstraction). If the batch throws an error, throw the client a new HTTPS error (along with the batch error) which (a) automatically terminates the cloud function and (b) gives the client the batch error to evaluate, which, depending on the reason, could warrant a retry or an error message to the end user.
export const delacc = functions.auth.user().onDelete(async (user) => {
const userUID = user.uid;
const db = admin.firestore();
const batch = db.batch();
try {
batch.delete(db.collection("users").doc(userUID));
batch.delete(db.collection("spam").doc(userUID));
batch.delete(db.collection("photos").doc(userUID));
const writeResult = await batch.commit();
return writeResult;
} catch(error) {
functions.logger.error(error);
throw new functions.https.HttpsError("unknown", "batch-error", error);
}
});
I am reading MDN on how async code works with javascript. They propose this example:
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
myFetch()
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
Function myFetch() is defined and then it's called in a Promise-way style. How exactly is the function executed? It's an async function, so I assume that it's given to some thread to execute without interfering with the single thread that runs Javascript code?
Also in the docs they said await is stoping the code on that line until completion? So the function can't be executed by the main application thread because then it's not async code anymore?
So those are my issues and I wanna be sure I understand this mechanism before I start writing actual code. Thank you!
using async/await is pretty similar to using promises, async function returns a Promise for you, that promise can have one of three statuses (Pending, Fulfilled, Rejected)
By default, the returned Promise is Fulfilled (Resolved)
similar to:
Promise.resolve().then(_ => { //... })
using await, the Promise will be pending on the code that you are awaiting for
new Promise((resolve, reject) => {
// ... some tasks here
resolve(resolvedData);
})
.then(_ => { //... })
when an error is thrown, automatically or manually, the Promise will be rejected
new Promise((resolve, reject) => {
reject(); // Reject manually
})
.then(_ => { })
.catch(e => { //... })
By default, all JavaScript code is executing within the main thread, which is the only thread that JavaScript has
Look at the following example:
async function c() {
const t = await Array.from(Array(100000000).keys())
}
c().then(t => console.log('I am waiting for the Promise to be fulfilled!'))
console.log('Hey, I don\'t need to wait');
You will get the expected output:
"Hey, I don't need to wait"
"I am waiting for the Promise to be fulfilled!"
but both logs are waiting for filling this 100000000 array, so the main thread now is busy and blocked to complete this task first, JavaScript itself can't help with this case,
Another Example:
async function c() {
const t = await fetch('https://jsonplaceholder.typicode.com/photos')
}
c().then(_ => console.log('I am waiting for the Promise to be fulfilled!'))
console.log('Hey, I don\'t need to wait');
Here you'll get the same result, but Hey, I don't need to wait will be logged immediately, and only I am waiting for the Promise to be fulfilled! will be waiting,
Why? here we need to refer to few important things to read more about (Event Loop, Call Stack, Micro & Macro tasks )
In simple words, the promise behaves the same, it's pending on something, and the first console log in the code is awaiting the promise to be fulfilled,
the difference is that the task that we are waiting for to have a fulfilled promise now is not thread-blocking, it is not controlled by JavaScript itself
the fetching task is managed by an external API that is observed by the Event loop, in this case, the call stack will not be busy, and the main JavaScript thread can continue working, the code that will resolve the promise is added to a queue and will be pushed to the call stack by the Event loop when the fetching task is done by the external API
Function myFetch() is defined and then it's called in a Promise-way style.
It's called like any other function. All async functions wrap the return value in a Promise, thus there are then, catch and finally methods available.
How exactly is the function executed?
Using () like any other function.
It's an async function, so I assume that it's given to some thread to execute without interfering with the single thread that runs Javascript code?
There is no need to have it run on some other thread. As you say, JavaScript execution happens in a single thread.
Also in the docs they said await is stoping the code on that line until completion?
Yes, in that async function.
So the function can't be executed by the main application thread because then it's not async code anymore?
There doesn't have to be any threading involved. It is only the fetch and blob operations that are performed asynchronously. While those operations are being awaited, the event loop and the execution of code outside the async function will continue. If you have code below the call to myFetch then that will be executed while the fetch is being awaited, unless you await the call to myFetch itself.
async and await are syntactic sugar on top of Promises, so effectively your code is doing the following:
fetch('coffee.jpg').then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
response.blob().then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
});
}).catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
I am using Axios in my React-Native app to communicate with a Nodejs backend, and am using react-redux dispatch to call the actions that will utilize Axios. However, no matter what I try I land up getting "Unhandled Promise Rejection" anytime there is an error of any sort. This is frustrating me so much I'm about to give up on Axios and just go back to using fetch. I've googled this problem (which seems to be very common indeed), but have not found any solid solution yet.
Here is an example of how I'm using Axios in my actions to send requests to my Nodejs backend server:
export const getAppointments = (userId) => {
return async (dispatch) => {
const request = axios
.get(`${SERVER_BOOKINGS}/upcoming_appointments/${userId}`)
.then((response) => {
let ourPayload = {};
ourPayload.success = response.data.success;
if (ourPayload.success) {
ourPayload.bookings = response.data.bookings;
dispatch({
type: GET_UPCOMING_APPOINTMENTS,
payload: ourPayload,
});
}
})
.catch((err) => {
console.log("caught an error, HERE:", err);
//throw err
});
};
};
And here is the code I'm using to call this action:
const getAppointments = async () => {
try {
await dispatch(
bookingActions.getAppointments(userObject.userData.userId)
);
} catch (error) {
console.log("we actually caught an axios error client side!");
}
}
If I leave the code exactly as above and I deliberately cause an error response from my Nodejs code , I get a console.log message saying "caught an error, HERE", but get nothing from the catch block where I actually dispatch the action (2nd block of code).
If I uncomment out the throw err line in the first catch block, I still get the console.log, still get nothing from the second catch block.... but now I get an Unhandled Promise Rejection warning.
Right now, this code works very well as long as there isn't an error, and is completely worthless anytime there is an one. In all honestly, I don't know whether the issue is one with axios, react-redux dispatch or just a failure on my part to understand the way Promises are meant to work, but I've wasted untold hours trying to figure out what should be a really simple matter of catching errors... and I'm falling behind schedule on this project because of it. Any advice/help would be greatly appreciated!
I think that the problem is caused since "bookingActions.getAppointments" is not a Promise function so there is no need to "try-catch" it.
try to change it like this:
const getAppointments = () => dispatch(bookingActions.getAppointments(userObject.userData.userId));