Convert promises to async/await with Firebase queries - javascript

I have some firebase queries where I am using their recommended promises but I am running to a problem. I need the code to go through the steps in order but the last snippet of code is running before the prior code finishes. I would like to use async/await but I am not sure exactly how to do it.
here is my current code:
if (result.user.emailVerified) {
let ref = db.collection('users').where('email', '==', result.user.email)
ref.get().then(snapshot => {
if (snapshot.empty) {
let cid = null
let docID = null
let groupID = null
let pendingRef = db.collection('pendingusers')
pendingRef = pendingRef.where('email', '==', result.user.email)
pendingRef.get().then(snapshot => {
if (!snapshot.empty) {
snapshot.forEach(doc => {
cid = doc.data().cid
docID = doc.id
groupID = doc.data().groupID
})
}
}).then(() => {
db.collection('users').add({
url: null,
fullPath: null,
roles: ['member'],
uid: result.user.uid,
email: result.user.email,
emailVerified: result.user.emailVerified,
cid: cid,
active: null,
joined: Date.now()
}).then(() => {
// STEP 1
console.log('1. user has been added')
if (groupID !== null) {
db.collection('groups').doc(groupID).update({
members: firebase.firestore.FieldValue.arrayUnion(result.user.uid)
}).then(() => {
// STEP 2
console.log('2. group has been updated')
})
}
}).then(() => {
if (docID !== null) {
db.collection('pendingusers').doc(docID).delete()
.then(() => {
// STEP 3
console.log('3. removed from pending ', cid)
let churchRef = db.collection('churches')
churchRef = churchRef.where('cid', '==', cid)
churchRef.get().then((querySnapshot) => {
if (!querySnapshot.empty) {
querySnapshot.forEach((doc) => {
// STEP 4
console.log('4. data ', doc.data())
dispatch('AppData/resetNotifications', [], {
root: true
})
dispatch('AppData/resetUnits', [], {
root: true
})
dispatch('AppData/isRunning', false, {
root: true
})
dispatch('AppData/setChurchId', doc.data().cid, {
root: true
})
// set the church
dispatch('AppData/allChurches', [{
churchtitle: doc.data().churchtitle,
cid: doc.data().cid,
}], {
root: true
})
})
}
})
})
}
})
})
} else {
snapshot.forEach(doc => {
commit('updateUser', {
cid: doc.data().cid,
url: doc.data().url,
fullPath: doc.data().fullPath,
user_name: doc.data().user_name
})
dispatch('AppData/getCourse', {
cid: doc.data().cid,
userid: doc.data()
}, {
root: true
})
dispatch('AppData/getActiveCourse', {
cid: doc.data().cid,
user: doc.data()
}, {
root: true
})
})
}
}).then(() => {
// STEP 5
console.log('5. next step')
dispatch('AppData/getSections', {}, {
root: true
})
dispatch('AppData/getUnits', {}, {
root: true
})
dispatch('AppData/getMissions', {}, {
root: true
})
dispatch('AppData/getWeeks', {}, {
root: true
})
// get user's current roles
let ref = db.collection('users').where('email', '==', result.user.email)
ref.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
let docs = change.doc
commit('setRoles', docs.data().roles)
})
})
router.push('home')
})
} else {
// console.log('not verified')
commit('setLoginFeedback', {
code: 'no-verfied email',
message: 'You must first verifiy your email.'
});
}
I have commented the steps to help illustrate the flow. I would like the code to go from step 1,2,3,4,5 but currently it goes 5,1,2,3,4
I know async/await will solve this, I just need some guidance on converting the code from then() to async/await

Related

Sequelize - Foreign keys are always null

Description
I'm starting to learn how to use Sequelize and I've run into an issue with foreign key relationships for one of my tables. I have three tables: Users, Projects, Times. Users and Projects have a Many to One relationship with Times.
Time.belongsTo(User)
Time.belongsTo(Project)
User.hasMany(Time)
Project.hasMany(Time)
After I create an entry in the Times table and then fetch it the resulting record always has Nulls for the two foreign keys.
db.Time.findAll().then(result => {
res.status(200).send(JSON.stringify(result))
})
Model
const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
}
});
const Project = sequelize.define('Project', {
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Time = sequelize.define('Time', {
description: {
type: DataTypes.STRING,
allowNull: false
},
start: {
type: DataTypes.STRING,
allowNull: false
},
stop: {
type: DataTypes.STRING,
allowNull: false
}
});
console.log('Setting up Models')
Time.belongsTo(User)
Time.belongsTo(Project)
User.hasMany(Time)
Project.hasMany(Time)
const db = {
"sequelize": sequelize,
"User": User,
"Project": Project,
"Time": Time
}
exports.User = User;
exports.Project = Project;
exports.Time = Time;
exports.db = db;
Populating with Default Data
const {data} = require('./data')
const {db} = require('../data/models')
const dbHelper = {
connectDB: () => {
return new Promise((resolve, reject) => {
db.sequelize.authenticate().then(() => {
resolve(db)
}).catch((e) => {
reject(e);
})
})
},
hydrateDB: (db) => {
return new Promise((resolve, reject) => {
try {
db.sequelize.sync().then(() => {
hydrateUser(db).then(() => {
hydrateProject(db).then(() => {
hydrateTime(db).then(() => {
resolve()
})
})
})
})
} catch (e) {
reject(e)
}
})
}
}
const hydrateUser = (db) => {
return new Promise(resolve => {
data.user.forEach((datum) => {
db.User.create(datum).then(() => {
resolve()
})
})
})
}
const hydrateProject = (db) => {
return new Promise(resolve => {
data.project.forEach((datum) => {
db.Project.create(datum).then(() => {
resolve()
})
})
})
}
const hydrateTime = (db) => {
return new Promise(resolve => {
data.time.forEach((datum) => {
db.Time.create({
description: datum.description,
start: datum.start,
stop: datum.stop,
userId: 1,
projectId: 1
}).then(() => {
resolve()
})
})
})
}
exports.dbHelper = dbHelper;
You indicated fields in camel case here:
userId: 1,
projectId: 1
And actual fields created for Time are in pascal case as you showed in the screenshot.
So just correct names:
db.Time.create({
description: datum.description,
start: datum.start,
stop: datum.stop,
UserId: 1,
ProjectId: 1
}).then(() => {
resolve()
})

Function in async function chain not being called

I'd like to update the user's data in Firestore whenever they log in and am using the following code to do so. For some reason, the code doesn't work (see comments) and doesn't create a custom User object from firebase.User. Why is this happening/how do I fix this? I'm not getting any errors.
Code that is called to log in
async emailLogIn(email: string, password: string) {
return this.auth.signInWithEmailAndPassword(email, password)
.then( async (credential) => {
this.analytics.logEvent('logged_in', { method: 'email' });
const firebaseUser = credential.user;
if(firebaseUser) {
const user = await this.createUserFromFirebaseUser(firebaseUser);
console.log(user); // This doesn't print anything
this.updateUserData(user);
if(!firebaseUser.emailVerified) {
this.sendEmailVerification();
}
}
});
}
Code that is convert firebase.User to User (doesn't work)
async createUserFromFirebaseUser(firebaseUser: firebase.User): Promise<User> {
console.log('createUserFromFirebaseUser()');
const currentUser = await this.user.toPromise();
console.log(currentUser); // This doesn't print anything
if(currentUser)
return currentUser;
const user: User = {
uid: firebaseUser.uid,
email: firebaseUser.email,
displayName: firebaseUser.displayName,
settings: {
language: 'English',
isPrivate: false,
newFountainNotification: true,
userFountainNotification: true,
feedbackNotification: true,
units: 'Metric'
}
}
return user;
}
Getting user data from Firestore
this.user = this.auth.authState.pipe(
takeUntil(this.destroy),
switchMap( (user) => {
if(user) {
return (this.firestore.collection('users').doc(user.uid).valueChanges() as Observable<User>)
} else {
return of(null);
}
})
);
It seemed to be an issue with using this.user.toPromise()
This is the code that works:
return this.user.pipe(
take(1),
map( (currentUser) => {
if(currentUser)
return currentUser;
const user: User = {
uid: firebaseUser.uid,
email: firebaseUser.email,
displayName: firebaseUser.displayName,
settings: {
language: 'English',
isPrivate: false,
newFountainNotification: true,
userFountainNotification: true,
feedbackNotification: true,
units: 'Metric'
}
}
return user;
})
).toPromise()

Model.findById() is returning undefined

I trying to implement a favorite toggle where it saves the favorites in an array, I create a Schema and a router, the code you can see below the problem is when I try to test it on insomnia I'm getting undefined on my console.log(isFavorite). I don't know what could be wrong.
const userSchema = new Schema({
username: String,
email: String,
password: String,
favorites: [{ type: Schema.Types.ObjectId, ref: "Places" }],
},
{
timestamps: true,
});
// route
router.put("/favorites/:placeId", (req, res) => {
const userId = "5ebd13df31430045957db8c3";
User.findById(userId).then( (user) => {
const isFavorite = user.favorites.find( (favorite) => {
return favorite === req.params.placeId;
});
console.log(isFavorite);
console.log(req.params.placeId);
if (isFavorite) {
User.findOneAndUpdate(
{ _id: userId },
{
$pull: { favorites: req.params.placeId },
},
{
new: true,
})
.then((user) => res.json(user))
.catch((err) => res.status(400).json(err));
} else {
User.findOneAndUpdate(
{ _id: userId },
{
$push: { favorites: req.params.placeId },
},
{
new: true,
})
.then((user) => res.json(user))
.catch((err) => res.status(400).json(err));
}
});
});
this chunk is bad:
User.findById(userId).then((user) => {
const isFavorite = user.favorites.find((favorite) => {
return favorite === req.params.placeId;
});
instead must use populate():
let favorites = await User.findById(userId).populate('favorites');
and then filter favorites by placeId

proper use of onCompleted for GraphQL mutations

I want to run the query first. The query returns an id which is then required for the mutation. Currently, there's an issue with the order of how both things run from the handleSubmit(). If the mutation is successful, the console should print console.log('Checking');but that does not happen. The only output I get on the console is What's the Idand the value is probably something that was stored in one of my previous attempts. If the id was derived from this particular round of query, I would have seen Workingon the log, but that doesn't happen either.
const [loadUsers, { loading, data, error }] = useLazyQuery(LoadUsersQuery, {
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
},
onCompleted: () => getFriendId(),
});
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
},
] = useCreateUserRelationMutation({
variables: {
input: {
relatedUserId: Number(id),
type: RelationType.Friend,
userId: 5,
},
},
onCompleted: () => addFriend(),
});
const getFriendId = () => {
console.log('Working');
if (data) {
console.log(data);
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
}
} else {
if (error) {
setErrorMessage(error.message);
}
}
};
const addFriend = () => {
console.log('Whats the Id', Number(id));
if (addingFriendData) {
console.log('Checking');
console.log(addingFriendData);
}
if (addingFriendError) {
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
};
const handleSubmit = () => {
loadUsers();
createUserRelationMutation();
};
Before this, I was trying this:
const [id, setId] = useState('');
const [friendEmail, setFriendEmail] = useState('');
const [loadUsers, { loading, data, error }] = useLazyQuery(LoadUsersQuery);
const [createUserRelationMutation, { data: addingFriendData, loading: addingFriendLoading, error: addingFriendError }] = useCreateUserRelationMutation();
const getFriendId = () => {
console.log('Email', friendEmail.toLocaleLowerCase());
loadUsers({
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
},
});
if (data) {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
}
addFriend();
};
const addFriend = () => {
console.log('Whats the Id', Number(id));
createUserRelationMutation({
variables: {
input: {relatedUserId: Number(id), type: RelationType.Friend, userId: 7 }
},
});
if (addingFriendData){
console.log('Checking')
console.log(data);
}
if(addingFriendError){
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
}
const handleSubmit = () =>
{getFriendId();};
However, in this case, the values of the id & other states weren't being updated timely. I was running a graphql query inside getFriendId()that returns an id, followed by a mutation (inside addFriend(), which uses the id, along with an input (email) that the user types in. The problem is that on the first attempt, the mutation works fine and with correct values. However, when I change the email address on the input and run the query/mutation again, the values from my previous attempt are being used.
In the second attempt, the mutation was still using the id that we got in the first attempt.
Edit:
onCompleted: (data) => getFriendId(data),
const getFriendId = (data: any) => {
console.log('Working');
if (data) {
console.log(data);
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
}
Updated Code:
const [friendEmail, setFriendEmail] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [loadUsers, { loading, data, error }] = useLazyQuery(LoadUsersQuery);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
},
] = useCreateUserRelationMutation();
const getFriendId = () => {
console.log('Email', friendEmail.toLocaleLowerCase());
loadUsers({
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
},
});
if (data) {
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
addFriend(data.users.nodes[0].id);
}
} else {
console.log('No data');
if (error) {
setErrorMessage(error.message);
}
}
//addFriend();
};
const addFriend = (idd: any) => {
console.log('Whats the Id', Number(idd));
createUserRelationMutation({
variables: {
input: {relatedUserId: Number(idd), type: RelationType.Friend, userId: 9 }
},
});
if (addingFriendData){
console.log('Checking')
console.log(data);
}
if(addingFriendError){
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
}
const handleSubmit = () =>
{
getFriendId();
};
You don’t need state to store ID, instead pass the Id to addFriend method like show below
const [friendEmail, setFriendEmail] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const _onLoadUserError = React.useCallback((error: ApolloError) => {
setErrorMessage(error.message);
}, []);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
called: isMutationCalled
},
] = useCreateUserRelationMutation();
const addFriend = React.useCallback((idd: Number) => {
console.log('Whats the Id', idd);
createUserRelationMutation({
variables: {
input: { relatedUserId: idd, type: RelationType.Friend, userId: 9 }
}
});
}, [createUserRelationMutation]);
const getFriendId = React.useCallback((data: any) => {
console.log('Email', friendEmail.toLocaleLowerCase());
if (data) {
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
addFriend(Number(data.users.nodes[0].id));
}
}
}, [friendEmail, addFriend]);
const [loadUsers] = useLazyQuery(LoadUsersQuery, {
onCompleted: getFriendId,
onError: _onLoadUserError
});
const handleSubmit = React.useCallback(() => {
loadUsers({
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
}
});
}, [loadUsers, friendEmail]);
if (!addingFriendLoading && isMutationCalled) {
if (addingFriendData) {
console.log('Checking')
console.log(data);
}
if (addingFriendError) {
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
}
Update
I have updated the above code, please refer to it. I'm assuming useCreateUserRelationMutation does not accept options as argument, if it accepts option then you could use onCompleted and onError just like loadUsers query.

How to use DataLoader with Mongoose

I'm trying to build the following use case of DataLoader together with Mongoose:
export const PurchaseOrderType = new GraphQLObjectType({
name: "PurchaseOrder",
description: "PurchaseOrder",
interfaces: () => [NodeInterface],
isTypeOf: value => value instanceof PurchaseOrderModel,
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: obj => dbIdToNodeId(obj._id, "PurchaseOrder")
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
customer: {
type: CustomerType,
resolve: (source, args, context) => {
return context.customerLoader.load(source.customer_id);
}
}
})
});
export default () => {
return graphqlHTTP((req, res, graphQLParams) => {
return {
schema: schema,
graphiql: true,
pretty: true,
context: {
customerLoader: customerGetByIdsLoader()
},
formatError: error => ({
message: error.message,
locations: error.locations,
stack: error.stack,
path: error.path
})
};
});
};
export const customerGetByIdsLoader = () =>
new DataLoader(ids => {
return customerGetByIds(ids);
});
export const customerGetByIds = async ids => {
let result = await Customer.find({ _id: { $in: ids }, deletedAt: null }).exec();
let rows = ids.map(id => {
let found = result.find(item => {
return item.id.equals(id);
});
return found ? found : null; << === found always undefined
});
return rows;
};
I'm facing the following problems when loading several PurchaseOrders:
A single customer_id is being called more than once in the ids parameter of the DataLoader. So an example id 5cee853eae92f6021f297f45 is being called on several requests to my loader, in successive calls. That suggests that the cache is not working properly.
My found variable when processing the read result is always being set to false, even comparing the right ids.
You can use findOne
export const customerGetByIds = async ids => {
let result = await Customer.find({ _id: { $in: ids }, deletedAt: null }).exec();
const rows = []
let promiseAll = ids.map(async (id) => {
let found = result.filter(item => item.id.toString() === id.toSring());
if(found) {
rows.push(found[0])
return found[0]
}
return null;
});
await Promise.all(promiseAll);
return rows;
};

Categories