I'm using firebase in my reactjs+typescript project. I want to write a document in firebase and I'm using setDoc to do this job.
import { doc, setDoc } from 'firebase/firestore';
setDoc(doc(database, `path`), objData)
When you check the function return it says Promise<...>. But, how am I supposed to use then() catch() promise handlers if I don't know what the promise returns when it fullfil/reject?
I'm not very familiar with Firebase but after looking around it seems the return is just going to be whether or not the setDoc() was successful. So the .then() would mean yes and the .catch() would mean no. So really all you would want is something like this
import { doc, setDoc } from 'firebase/firestore';
setDoc(doc(database, `path`), objData)
.then(() => {
console.log("Successful")})
.catch((error) => {
console.log(`Unsuccessful returned error ${error}`)});
Again I am not fully sure about the exact values returned as I could not find much but if there is a result value given you would place it in the .then((result) => {console.log(result)}).
The firebase documentation suggest using await, so here you have a complete example of error handling with await and cleanup with finally.
import { doc, setDoc } from 'firebase/firestore';
async function run() {
try {
await setDoc(doc(database, `path`), objData)
} catch (e) {
console.error(e); // handle your error here
} finally {
console.log('Cleanup here'); // cleanup, always executed
}
}
run();
In Typescript, you don't need to specify the promise return type precisely. Here's an example of how to use promise handlers with setDoc:
import { doc, setDoc } from 'firebase/firestore';
setDoc(doc(database, `path`), objData)
.then((data) => {console.log('data', data);})
.catch((err) => {console.log('err', err);})
I found some odd behavior. I'm making a handler function in Angular that calls a Firebase Cloud Function that writes to Firestore. I'm using the emulator.
app.component.ts
wordRequest(word: string, l2Language: L2Language, requestOrigin: string) {
const buildWordObject = httpsCallableFromURL(this.functions, 'http://localhost:5001/my-app/us-central1/buildWordObject');
buildWordObject({
word: word,
l2Language: l2Language,
requestOrigin: requestOrigin
})
.then((result) => {
console.log(result.data);
if (result.data === null) {
console.log("Success! Word object written to database.");
}
})
.catch((error) => {
console.error(error);
});
}
index.js
export const buildWordObject = functions.https.onCall((data, context) => {
...does various stuff here
setDoc(doc(firestore, 'Dictionaries/' + longLanguage + '/Words', word), wordObject)
.then(() => {
console.log("Finished writing word object to Firestore database: " + word);
return 37
})
.catch((error: any) => {
console.error(error);
return error
});
return 38
});
This logs Finished writing word object to Firestore database: luck in the emulator console and logs 38 in the Angular console.
If I comment out return 38 the emulator log is the same but in the Angular console I see:
Success! Word object written to database.
Either way the data is written to the Firestore emulator.
A successful database write returns null to the Angular handler function that called it. I don't see Success! for a few seconds so it's clearly doing something async.
If I comment out return 37 and return error then everything runs without errors. Usually when I write a Firebase Cloud Function without a return statement it throws an error in the log.
What I find odd is that the return to the Angular handler is always null, never 37. The console.log message writes to the emulator log but the return 37 statement never executes. You can't return anything but null back to the calling function.
I tried putting .finally(() => { return 37 }); at the end but I never got 37 back.
Related
I have an async function that posts data to Firebase Firestore. It works, apart from I need it to do some things after the data has been posted, or 'on success'.
In the code below I'm trying to 'setLoading' to false after the data has been posted to Firestore. The data does get posted, but the loading value remains true. Can anyone see what is wrong with my code, please? I want to do some other things apart from change the value of 'loading', but I'm just using this one instance as an example here.
async function uploadData(e) {
e.preventDefault()
try {
setLoading(true)
return await useFireStore
...
}
catch {
setError('Post failed')
}
setLoading(false) // this isn't working
}
Cheers, Matt
If you want something to happen on success, you just place is below your await. If you want it to happen no matter what, you can use the finally block.
try {
setLoading(true)
const result = await useFireStore;
// logic here to run on success
return result;
}
catch {
setError('Post failed')
}
finally{
// logic here to always run
setLoading(false);
}
The issue is that you're using return in your try catch. This is no different from when you're using try/catch and return in a non-async function. (By design.)
Typically you'd use finally for this:
async function uploadData(e) {
e.preventDefault()
try {
setLoading(true)
return await useFireStore/*...*/;
}
catch {
setError('Post failed')
}
finally {
setLoading(false) // this isn't working
}
}
Note that the await in the above is important. There's no need for return await somePromise if it's at the top level of your function code (just use return somePromise), but the await can make a big difference if it's not at the top level, which is the case here. So you do need that await.
Side note: As written, your function fulfills its promise with the result of the useFireStore call if it works, but with undefined if it doesn't work. If useFireStore can return undefined, that means code using your function has no idea whether the operation succeeded or failed. (But if useFireStore doesn't every return undefined on success, then code using your function could tell the difference.)
You used return inside try block so it is returning without setting the loading status to false. Also, you should set the loading status to false on error case as well, so your code should look like:
async function uploadData(e) {
e.preventDefault()
setLoading(true)
try {
await useFireStore
}
catch {
setError('Post failed')
}
finally {
setLoading(false)
}
}
You're returning after the request is created, if you need to use the response, store it in the variable instead of returning, also if you want to make sure any code is executed after the request was completed ( successful or unsuccessful), use the finally block
async function uploadData(e) {
e.preventDefault()
try {
setLoading(true)
const response = await useFireStore(); // don't return here
// use the response var if it request returns any data
} catch {
setError('Post failed')
} finally {
setLoading(false) //move the setLoading here
}
}
I have a problem with a callable firebase cloud function (exports.listAllUsers).
Backend: Nodejs & Firebase-Cloud_functions to use admin.auth().listUsers
Problem: Result (usersList; an array with the uid of the users) is OK in cloud-functions-emulator (log) but NOT in the client (console.log(usersList) in browser is null)
Maybe a problem related with...: bad understanding of promises. A second code example is working with async/await but not working with .then().
The code of the function listAllUsers is basically copy&paste from docs (original code snipet: https://firebase.google.com/docs/auth/admin/manage-users#list_all_users). My code modifications are 5 (comments in the code), to get a list of users uid:
exports.listAllUsers = functions.https.onCall(() => { // -1) callable function
const usersList = ['12323211'] // ----------------------2) first user uid, just a sample
function listAllUsers (nextPageToken) {
// List batch of users, 1000 at a time.
admin.auth().listUsers(1000, nextPageToken)
.then((listUsersResult) => {
listUsersResult.users.forEach((userRecord) => {
usersList.push(userRecord.uid) // --------------3) get users uids
})
if (listUsersResult.pageToken) {
// List next batch of users.
listAllUsers(listUsersResult.pageToken)
}
console.log(usersList) //-------------------------4) list users uid (cloud functions console)
return usersList //-------------------------------5) return to the client the same as showed at the console
})
.catch((error) => {
console.log('Error listing users:', error)
return null
})
}
// Start listing users from the beginning, 1000 at a time.
listAllUsers()
})
The method in the client is...
getUsersList: async function (userType) {
const usersList = await this.$fb.functions().httpsCallable('listAllUsers')()
console.log('usersList: ', usersList)
}
I am using firebase emulators. Cloud functions log is OK, you can see the sample uid and the other uids:
cloud function emulator console output
But I don't get the same in the client:
client console output
I think I am doing something wrong related with promises... because a simplification of the code is working with async/await:
exports.listAllUsers = functions.https.onCall(async () => {
try {
listUsersResult = await admin.auth().listUsers()
return listUsersResult
} catch (error) {
console.log('Error listing users:', error)
return null
}
})
Browser console output (reduced code with async/await)
But the same is not working with then()...
exports.listAllUsers = functions.https.onCall(() => {
admin.auth().listUsers()
.then((listUsersResult) => {
return listUsersResult
})
.catch((error) => {
console.log('Error listing users:', error)
return null
})
})
Browser console output (reduced code with .then())
I can refactor the initial snipet of code with async/await, but I am interested in the solution with the original code (.then() flavor; I always use async/await because I am quite new at js)... Anyone can help me? Thanks!
This is because with the async/await version you correctly return listUsersResult by doing
listUsersResult = await admin.auth().listUsers()
return listUsersResult
but, with the then version, you don't. You should return the entire promises chain, as follows:
exports.listAllUsers = functions.https.onCall(() => {
return admin.auth().listUsers() // !!! Note the return here !!!
.then((listUsersResult) => {
return listUsersResult
})
.catch((error) => {
console.log('Error listing users:', error)
return null
})
})
Finally I decided to code an async/await version for my cloud function. The then version in the first code snipet requires more than just adding the return to the entire promises chain (it initially complains because of the recursivity, maybe, asking me to add asyncto the wrapper function listAllUsers... I'd like the then version to just copy&paste from the firebase docs, but it wanted more).
I'd like to share this homemade (but initially tested) version as a sample with async/await without recursivity to list users with admin.auth().listUsers(maxResults?, pageToken?):
// get list of users
exports.listAllUsers = functions.https.onCall(async () => {
const usersList = []
try {
let listUsersResult
let nextPageToken
do {
if (listUsersResult) {
nextPageToken = listUsersResult.pageToken
}
// eslint-disable-next-line no-await-in-loop
listUsersResult = await admin.auth().listUsers(1000, nextPageToken)
listUsersResult.users.forEach((userRecord) => {
usersList.push(userRecord.uid)
})
} while (listUsersResult.pageToken)
return usersList
} catch (error) {
console.log('Error listing users:', error)
return null
}
})
I have the following code in my own async function that uses another imported function from module which is a custom wrap of axios inside try/catch block:
async function getCharacter (realmSlug, characterName) {
try {
const [{id, name, gender, faction, race, character_class, active_spec, realm, guild, level, last_login_timestamp, average_item_level, equipped_item_level}, {pets, unlocked_battle_pet_slots},{mounts}] = await Promise.all([
getCharacterSummary(realmSlug, characterName), -- custom axios instance
getCharacterPetsCollection(realmSlug, characterName),
getCharacterMountsCollection(realmSlug, characterName)
])
....
return result;
} catch (error) {
console.log(error.code);
if (error.response.status === 404 || error.response.status === 403) {
console.error(`${getCharacter.name},${characterName}#${realmSlug}`);
}
return { name: characterName, realm: realmSlug }
}
}
The problem is that if I use promise.all according to Stackoverflow 1,2 I can not handle errors. So the problem is when I call function to execute, my errors doesn't handle in (catch) block. At all. Even if I don't need print them, anyway I receive messages in console about 404 errors, but console.log(error.code) still gives me nothing. For example:
So is there any way to handle this annoying error messages in console somehow?
For example using .catch somewhere? Or using for await ... of or rxJS instead if it's possible?
Exporting function and using .catch
Even if I export this function getCharacter in another .js file and use the following code:
const getCharacter = require('./getCharacter');
let bulkCharacters = [{realmSlug, characterName},{realmSlug, characterName},... ,n] //array of characters for getCharacter request
const promises = bulkCharacters.map(async ({realmSlug, characterName}) => {
try {
return await getCharacter(realmSlug, characterName);
} catch (e) {
console.log(e)
}
});
let test = await Promise.all(promises)
.catch(function(arrayOfPromises, err) {
// log that I have an error, return the entire array;
console.log('A promise failed to resolve', err);
return arrayOfPromises;
})
.then(function(arrayOfPromises) {
console.log(arrayOfPromises)
})
;
console.log('stop')
I still receive errors in console, without triggering catch block inside getCharacter function or this file in which this function was imported and catch block is outside the function.
I am using Vue.js with Vue-Apollo and initiating a User mutation to sign in a user. I am using the graph.cool service.
I have a request pipeline function setup to catch some errors, like an invalid email.
When the request is made with bad / invalid input, my error catch() fires (as expected) and in the network tab I can see the JSON for the custom errors messages. But how do I access these errors / response from within the catch if an error is triggered from graph.cool?
Example:
signin () {
const email = this.email
const password = this.password
this.$apollo.mutate({
mutation: signinMutation,
variables: {
email,
password
}
})
.then((data) => {
// This never fires on an error, so I can't
// show the user the errors in the network repsonse.
console.log(data)
})
.catch((error) => {
// Error in this part fires in the console
// but I'm unable to show the JSON response
// errors because the 'then()' above doesn't execute.
console.error(error)
})
}
I get the following error for an unrecognised user:
Error: GraphQL error: No user found with that information
at new ApolloError (eval at (app.js:956), :34:28)
at eval (eval at (app.js:1353), :139:33)
at
Any idea how to show the errors in the response from within the catch()?
I can literally see the errors I want to show to the user in the response on the network tab here:
...but I can't figure out how to do it.
Any help much appreciated! Thank you.
So, it looks as though I was handling this the wrong way by barking up the wrong tree.
The key to the answer was examining the error from the .catch() with console.dir(error). This revealed some useful keys...namely:
error.graphQLErrors[0]
So all in all, the corrected code looks like this:
signin () {
const email = this.email
const password = this.password
this.$apollo.mutate({
mutation: signinMutation,
variables: {
email,
password
}
})
.then(data => {
console.log(data)
})
.catch(error => {
console.log(graphQLErrorMessages(error))
})
}
The graphQLErrorMessages() function is a helper I wrote, so that I can reuse this in other .catch() blocks:
function graphQLErrorMessages (errorsFromCatch) {
const errors = errorsFromCatch.graphQLErrors[0]
const messages = []
if (errors.hasOwnProperty('functionError')) {
const customErrors = JSON.parse(errors.functionError)
messages.push(...customErrors.errors)
} else {
messages.push(errors.message)
}
return messages
}
It returns an array of error messages (which is what I needed) but you could format this any way you like.
It might be a little https://graph.cool specific in its logic (I'm not so sure), but I hope this ends up helping someone also stuck in a similar situation!
I may be misunderstanding your question so please comment and correct me if I am but it looks like you may be having trouble with Promises more than with Vue or GraphQL.
Just like in a try...catch statement, once you catch an error, your program will continue to execute unless you re-throw the error. For example:
This Catches
try {
codeThatThrowsAnError();
} catch(e) {
// Do Nothing
}
This re-throws
try {
codeThatThrowsAnError();
} catch(e) {
throw new Error("Err 135: I failed")
}
Similarly, in Promise land, you can either catch the error and move like you have in your example, or you can re-throw. What you may be missing is that anything you return from a catch statement will be used in the next then. For example:
somethingReturningAFailedPromise()
.then(doWork)
.catch((err) => {
return "I'm a New Value"
})
.then(console.log)
//=> "I'm a New Value"
It sounds to me like what you need is a data function that is more resilient to failure like the following:
const getUserProfile = (id) => {
return fetchUserData(id)
.catch((err) => {
logError(err);
return {};
})
}
Currently, I'm using fetch with redux-thunk to read code from an API -
my code reads like this:
export function getUsers() {
return (dispatch) => {
// I have some helper code that automatically resolves the json promise
return fetch(`/users`)
.then((resp, json) => {
if (resp.status === 200) {
dispatch(getUsersSuccess(json));
} else {
dispatch(getUsersFail(json));
}
}).catch((err) => {
// network error
dispatch(getUsersFail(err));
});
};
}
The problem here is the catch method, as it will catch any error thrown in the then block. This commonly means that if some React component's render function fails with a programmer error, that error gets swallowed up back into dispatch(getUsersFail(err)).
Ideally, I'd like to detect if err is a fetch error (and dispatch my own action), otherwise throw. However, fetch throws a generic TypeError. How can I reliably detect that the error caught was one thrown by fetch?
Don't use .catch() but install the error handler directly on the fetch promise, as the second - onreject - argument to .then():
return fetch(`/users`)
.then(([resp, json]) => {
if (resp.status === 200) {
dispatch(getUsersSuccess(json));
} else {
dispatch(getUsersFail(json));
}
}, (err) => {
// network error
dispatch(getUsersFail(err));
});
Check out the difference between .then(…).catch(…) and .then(…, …) for details.
Btw, I'd recommend to write
return fetch(`/users`)
.then(([resp, json]) => resp.status === 200 ? getUsersSuccess(json) : getUsersFail(json)
, getUsersFail)
.then(dispatch);