Override Mongoose save method to retry on `duplicate key error` - javascript

My Mongoose schema uses a custom _id value and the code I inherited does something like this
const sampleSchema = new mongoose.Schema({
_id: String,
key: String,
});
sampleSchema.statics.generateId = async function() {
let id;
do {
id = randomStringGenerator.generate({length: 8, charset: 'hex', capitalization: 'uppercase'});
} while (await this.exists({_id: id}));
return id;
};
let SampleModel = mongoose.model('Sample', sampleSchema);
A simple usage looks like this:
let mySample = new SampleModel({_id: await SampleModel.generateId(), key: 'a' });
await mySample.save();
There are at least three problems with this:
Every save will require at least two trips to the database, one to test for a unique id and one to save the document.
For this to work, it is necessary to manually call generateId() before each save. An ideal solution would handle that for me, like Mongoose does with ids of type ObjectId.
Most significantly, there is a potential race condition that will result in duplicate key error. Consider two clients running this code. Both coincidentally generate the same id at the same time, both look in the database and find the id absent, both try to write the record to the database. The second will fail.
An ideal solution would, on save, generate an id, save it to the database and on duplicate key error, generate a new id and retry. Do this in a loop until the document is stored successfully.
The trouble is, I don't know how to get Mongoose to let me do this.
Here's what I tried: Based on this SO Question, I found a rather old sample (using a very old mongoose version) of overriding the save function to accomplish something similar and based this attempt off it.
// First, change generateId() to force a collision
let ids = ['a', 'a', 'a', 'b'];
let index = 0;
let generateId = function() {
return ids[index++];
};
// Configure middleware to generate the id before a save
sampleSchema.pre('validate', function(next) {
if (this.isNew)
this._id = generateId();
next();
});
// Now override the save function
SampleModel.prototype.save_original = SampleModel.prototype.save;
SampleModel.prototype.save = function(options, callback) {
let self = this;
let retryOnDuplicate = function(err, savedDoc) {
if (err) {
if (err.code === 11000 && err.name === 'MongoError') {
self.save(options, retryOnDuplicate);
return;
}
}
if (callback) {
callback(err, savedDoc);
}
};
return self.save_original(options, retryOnDuplicate);
}
This gets me close but I'm leaking a promise and I'm not sure where.
let sampleA = new SampleModel({key: 'a'});
let sampleADoc = await sampleA.save();
console.log('sampleADoc', sampleADoc); // prints undefined, but should print the document
let sampleB = new SampleModel({key: 'b'});
let sampleBDoc = await sampleB.save();
console.log('sampleBDoc', sampleBDoc); // prints undefined, but should print the document
let all = await SampleModel.find();
console.log('all', all); // prints `[]`, but should be an array of two documents
Output
sampleADoc undefined
sampleBDoc undefined
all []
The documents eventually get written to the database, but not before the console.log calls are made.
Where am I leaking a promise? Is there an easier way to do this that addresses the three problems I outlined?
Edit 1:
Mongoose version: 5.11.15

I fixed the problem by changing the save override. The full solution looks like this:
const sampleSchema = new mongoose.Schema({
_id: String,
color: String,
});
let generateId = function() {
return randomStringGenerator.generate({length: 8, charset: 'hex', capitalization: 'uppercase'});
};
sampleSchema.pre('validate', function() {
if (this.isNew)
this._id = generateId();
});
let SampleModel = mongoose.model('Sample', sampleSchema);
SampleModel.prototype.save_original = SampleModel.prototype.save;
SampleModel.prototype.save = function(options, callback) {
let self = this;
let isDupKeyError = (error, field) => {
// Determine whether the error is a duplicate key error on the given field
return error?.code === 11000 && error?.name === 'MongoError' && error?.keyValue[field];
}
let saveWithRetries = (options, callback) => {
// save() returns undefined if used with callback or a Promise otherwise.
// https://mongoosejs.com/docs/api/document.html#document_Document-save
let promise = self.save_original(options, callback);
if (promise) {
return promise.catch((error) => {
if (isDupKeyError(error, '_id')) {
return saveWithRetries(options, callback);
}
throw error;
});
}
};
let retryCallback;
if (callback) {
retryCallback = (error, saved, rows) => {
if (isDupKeyError(error, '_id')) {
saveWithRetries(options, retryCallback);
} else {
callback(error, saved, rows);
}
}
}
return saveWithRetries(options, retryCallback);
}
This will generate an _id repeatedly until a successful save is called and addresses the three problems outlined in the original question:
The minimum trips to the database has been reduced from two to one. Of course, if there are collisions, more trips will occur but that's the exceptional case.
This implementation takes care of generating the id itself with no manual step to take before saving. This reduces complexity and removes the required knowledge of prerequisites for saving that are present in the original method.
The race condition has been addressed. It won't matter if two clients attempt to use the same key. One will succeed and the other will generate a new key and save again.
To improve this:
There ought to be a maximum number of save attempts for a single document followed by failure. In this case, you've perhaps used up all the available keys in whatever domain you're using.
The unique field may not be named _id or you might have multiple fields that require a unique generated value. The embedded helper function isDupKeyError() could be updated to look for multiple keys. Then on error you could add logic to regenerate just the failed key.

Related

Uncaught DOMException: Failed to execute 'put' on 'IDBObjectStore': Evaluating the object store's key path did not yield a value at request.onsuccess

I'm trying to Store some application data using indexedDB
Here is my code
function _getLocalApplicationCache(_, payload) {
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.shimIndexedDB;
if (!indexedDB) {
if (__DEV__) {
console.error("IndexedDB could not found in this browser.");
}
}
const request = indexedDB.open("ApplicationCache", 1);
request.onerror = event => {
if (__DEV__) {
console.error("An error occurred with IndexedDB.");
console.error(event);
}
return;
};
request.onupgradeneeded = function () {
const db = request.result;
const store = db.createObjectStore("swimlane", {keyPath: "id", autoIncrement: true});
store.createIndex("keyData", ["name"], {unique: false});
};
request.onsuccess = () => {
// creating the transition
const db = request.result;
const transition = db.transaction("swimlane", "readwrite");
// Reference to our object store that holds the swimlane data;
const store = transition.objectStore("swimlane");
const swimlaneData = store.index("keyData");
payload = JSON.parse(JSON.stringify(payload));
store.put(payload);
const Query = swimlaneData.getAll(["keyData"]);
Query.onsuccess = () => {
if (__DEV__) {
console.log("Application Cache is loaded", Query.result);
}
};
transition.oncomplete = () => {
db.close();
};
};
}
If I do use different version then 1 here --> indexedDB.open("ApplicationCache", 1);
I'm getting a error like they keyPath is already exist. And other than than for version 1 I'm getting this error.
Can someone please help me where i'm doing wrong.
Review the introductory materials on using indexedDB.
If you did something like connect and create a database without a schema, or created an object store without an explicit key path, and then you stored some objects, and then you edited the upgradeneeded callback to specify the keypath, and then never triggered the upgradeneeded callback to run because you continue to use current version number instead of a newer version number, it would be one possible explanation for this error.
The upgradeneeded callback needs to have logic that checks for whether the object stores and indices already exist, and only create them if they do not exist. If the store does not exist, create it and its indices. If the store exists and the indices do not, add indices to the store. If the store exists and the indices exist, do nothing.
You need to trigger the upgradeneeded callback to run after changing your database schema by connecting with a higher version number. If you do not connect with a higher version number, the callback never runs, so you will end up connecting to the older version where your schema changes have not taken place.

Firestore listen only to root component [duplicate]

Is it possible to count how many items a collection has using the new Firebase database, Cloud Firestore?
If so, how do I do that?
2023 Update
Firestore now supports aggregation queries.
Node SDK
const collectionRef = db.collection('cities');
const snapshot = await collectionRef.count().get();
console.log(snapshot.data().count);
Web v9 SDK
const coll = collection(db, "cities");
const snapshot = await getCountFromServer(coll);
console.log('count: ', snapshot.data().count);
Notable Limitation - You cannot currently use count() queries with real-time listeners and offline queries. (See below for alternatives)
Pricing - Pricing depends on the number of matched index entries rather than the number of documents. One index entry contains multiple documents making this cheaper than counting documents individually.
Old Answer
As with many questions, the answer is - It depends.
You should be very careful when handling large amounts of data on the front end. On top of making your front end feel sluggish, Firestore also charges you $0.60 per million reads you make.
Small collection (less than 100 documents)
Use with care - Frontend user experience may take a hit
Handling this on the front end should be fine as long as you are not doing too much logic with this returned array.
db.collection('...').get().then(snap => {
size = snap.size // will return the collection size
});
Medium collection (100 to 1000 documents)
Use with care - Firestore read invocations may cost a lot
Handling this on the front end is not feasible as it has too much potential to slow down the users system. We should handle this logic server side and only return the size.
The drawback to this method is you are still invoking Firestore reads (equal to the size of your collection), which in the long run may end up costing you more than expected.
Cloud Function:
db.collection('...').get().then(snap => {
res.status(200).send({length: snap.size});
});
Front End:
yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
size = snap.length // will return the collection size
})
Large collection (1000+ documents)
Most scalable solution
FieldValue.increment()
As of April 2019 Firestore now allows incrementing counters, completely atomically, and without reading the data prior. This ensures we have correct counter values even when updating from multiple sources simultaneously (previously solved using transactions), while also reducing the number of database reads we perform.
By listening to any document deletes or creates we can add to or remove from a count field that is sitting in the database.
See the firestore docs - Distributed Counters
Or have a look at Data Aggregation by Jeff Delaney. His guides are truly fantastic for anyone using AngularFire but his lessons should carry over to other frameworks as well.
Cloud Function:
export const documentWriteListener = functions.firestore
.document('collection/{documentUid}')
.onWrite((change, context) => {
if (!change.before.exists) {
// New document Created : add one to count
db.doc(docRef).update({ numberOfDocs: FieldValue.increment(1) });
} else if (change.before.exists && change.after.exists) {
// Updating existing document : Do nothing
} else if (!change.after.exists) {
// Deleting document : subtract one from count
db.doc(docRef).update({ numberOfDocs: FieldValue.increment(-1) });
}
return;
});
Now on the frontend you can just query this numberOfDocs field to get the size of the collection.
Simplest way to do so is to read the size of a "querySnapshot".
db.collection("cities").get().then(function(querySnapshot) {
console.log(querySnapshot.size);
});
You can also read the length of the docs array inside "querySnapshot".
querySnapshot.docs.length;
Or if a "querySnapshot" is empty by reading the empty value, which will return a boolean value.
querySnapshot.empty;
As far as I know there is no build-in solution for this and it is only possible in the node sdk right now.
If you have a
db.collection('someCollection')
you can use
.select([fields])
to define which field you want to select. If you do an empty select() you will just get an array of document references.
example:
db.collection('someCollection').select().get().then(
(snapshot) => console.log(snapshot.docs.length)
);
This solution is only a optimization for the worst case of downloading all documents and does not scale on large collections!
Also have a look at this:
How to get a count of number of documents in a collection with Cloud Firestore
Aggregate count query just landed as a preview in Firestore.
Announced at the 2022 Firebase Summit: https://firebase.blog/posts/2022/10/whats-new-at-Firebase-Sumit-2022
Excerpt:
[Developer Preview] Count() function: With the new count function in
Firstore [sic], you can now get the count of the matching documents when you
run a query or read from a collection, without loading the actual
documents, which saves you a lot of time.
Code sample they showed at the summit:
During the Q&A, someone asked about pricing for aggregated queries, and the answer the Firebase team provided was that it'll cost 1 / 1000th of the price of a read (rounded up to the nearest read, see comments below for more details), but will count all records that are part of the aggregate.
Be careful counting number of documents for large collections. It is a little bit complex with firestore database if you want to have a precalculated counter for every collection.
Code like this doesn't work in this case:
export const customerCounterListener =
functions.firestore.document('customers/{customerId}')
.onWrite((change, context) => {
// on create
if (!change.before.exists && change.after.exists) {
return firestore
.collection('metadatas')
.doc('customers')
.get()
.then(docSnap =>
docSnap.ref.set({
count: docSnap.data().count + 1
}))
// on delete
} else if (change.before.exists && !change.after.exists) {
return firestore
.collection('metadatas')
.doc('customers')
.get()
.then(docSnap =>
docSnap.ref.set({
count: docSnap.data().count - 1
}))
}
return null;
});
The reason is because every cloud firestore trigger has to be idempotent, as firestore documentation say: https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees
Solution
So, in order to prevent multiple executions of your code, you need to manage with events and transactions. This is my particular way to handle large collection counters:
const executeOnce = (change, context, task) => {
const eventRef = firestore.collection('events').doc(context.eventId);
return firestore.runTransaction(t =>
t
.get(eventRef)
.then(docSnap => (docSnap.exists ? null : task(t)))
.then(() => t.set(eventRef, { processed: true }))
);
};
const documentCounter = collectionName => (change, context) =>
executeOnce(change, context, t => {
// on create
if (!change.before.exists && change.after.exists) {
return t
.get(firestore.collection('metadatas')
.doc(collectionName))
.then(docSnap =>
t.set(docSnap.ref, {
count: ((docSnap.data() && docSnap.data().count) || 0) + 1
}));
// on delete
} else if (change.before.exists && !change.after.exists) {
return t
.get(firestore.collection('metadatas')
.doc(collectionName))
.then(docSnap =>
t.set(docSnap.ref, {
count: docSnap.data().count - 1
}));
}
return null;
});
Use cases here:
/**
* Count documents in articles collection.
*/
exports.articlesCounter = functions.firestore
.document('articles/{id}')
.onWrite(documentCounter('articles'));
/**
* Count documents in customers collection.
*/
exports.customersCounter = functions.firestore
.document('customers/{id}')
.onWrite(documentCounter('customers'));
As you can see, the key to prevent multiple execution is the property called eventId in the context object. If the function has been handled many times for the same event, the event id will be the same in all cases. Unfortunately, you must have "events" collection in your database.
In 2020 this is still not available in the Firebase SDK however it is available in Firebase Extensions (Beta) however it's pretty complex to setup and use...
A reasonable approach
Helpers... (create/delete seems redundant but is cheaper than onUpdate)
export const onCreateCounter = () => async (
change,
context
) => {
const collectionPath = change.ref.parent.path;
const statsDoc = db.doc("counters/" + collectionPath);
const countDoc = {};
countDoc["count"] = admin.firestore.FieldValue.increment(1);
await statsDoc.set(countDoc, { merge: true });
};
export const onDeleteCounter = () => async (
change,
context
) => {
const collectionPath = change.ref.parent.path;
const statsDoc = db.doc("counters/" + collectionPath);
const countDoc = {};
countDoc["count"] = admin.firestore.FieldValue.increment(-1);
await statsDoc.set(countDoc, { merge: true });
};
export interface CounterPath {
watch: string;
name: string;
}
Exported Firestore hooks
export const Counters: CounterPath[] = [
{
name: "count_buildings",
watch: "buildings/{id2}"
},
{
name: "count_buildings_subcollections",
watch: "buildings/{id2}/{id3}/{id4}"
}
];
Counters.forEach(item => {
exports[item.name + '_create'] = functions.firestore
.document(item.watch)
.onCreate(onCreateCounter());
exports[item.name + '_delete'] = functions.firestore
.document(item.watch)
.onDelete(onDeleteCounter());
});
In action
The building root collection and all sub collections will be tracked.
Here under the /counters/ root path
Now collection counts will update automatically and eventually! If you need a count, just use the collection path and prefix it with counters.
const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
.doc('counters/' + collectionPath)
.get()
.then(snap => snap.get('count'));
Limitations
As this approach uses a single database and document, it is limited to the Firestore constraint of 1 Update per Second for each counter. It will be eventually consistent, but in cases where large amounts of documents are added/removed the counter will lag behind the actual collection count.
I agree with #Matthew, it will cost a lot if you perform such query.
[ADVICE FOR DEVELOPERS BEFORE STARTING THEIR PROJECTS]
Since we have foreseen this situation at the beginning, we can actually make a collection namely counters with a document to store all the counters in a field with type number.
For example:
For each CRUD operation on the collection, update the counter document:
When you create a new collection/subcollection: (+1 in the counter) [1 write operation]
When you delete a collection/subcollection: (-1 in the counter) [1 write operation]
When you update an existing collection/subcollection, do nothing on the counter document: (0)
When you read an existing collection/subcollection, do nothing on the counter document: (0)
Next time, when you want to get the number of collection, you just need to query/point to the document field. [1 read operation]
In addition, you can store the collections name in an array, but this will be tricky, the condition of array in firebase is shown as below:
// we send this
['a', 'b', 'c', 'd', 'e']
// Firebase stores this
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
// since the keys are numeric and sequential,
// if we query the data, we get this
['a', 'b', 'c', 'd', 'e']
// however, if we then delete a, b, and d,
// they are no longer mostly sequential, so
// we do not get back an array
{2: 'c', 4: 'e'}
So, if you are not going to delete the collection , you can actually use array to store list of collections name instead of querying all the collection every time.
Hope it helps!
As of October 2022, Firestore has introduced a count() method on the client SDKs. Now you can count for a query without downloads.
For 1000 documents, it will charge you for 1 document read.
Web (v9)
Introduced in Firebase 9.11.0:
const collectionRef = collection(db, "cities");
const snapshot = await getCountFromServer(collectionRef);
console.log('count: ', snapshot.data().count);
Web V8
Not Available.
Node (Admin)
const collectionRef = db.collection('cities');
const snapshot = await collectionRef.count().get();
console.log(snapshot.data().count);
Android (Kotlin)
Introduced in firestore v24.4.0 (BoM 31.0.0):
val query = db.collection("cities")
val countQuery = query.count()
countQuery.get(AggregateSource.SERVER).addOnCompleteListener { task ->
if (task.isSuccessful) {
val snapshot = task.result
Log.d(TAG, "Count: ${snapshot.count}")
} else {
Log.d(TAG, "Count failed: ", task.getException())
}
}
Apple Platforms (Swift)
Introduced in Firestore v10.0.0:
do {
let query = db.collection("cities")
let countQuery = query.countAggregateQuery
let snapshot = try await countQuery.aggregation(source: AggregateSource.server)
print(snapshot.count)
} catch {
print(error)
}
Increment a counter using admin.firestore.FieldValue.increment:
exports.onInstanceCreate = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
.onCreate((snap, context) =>
db.collection('projects').doc(context.params.projectId).update({
instanceCount: admin.firestore.FieldValue.increment(1),
})
);
exports.onInstanceDelete = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
.onDelete((snap, context) =>
db.collection('projects').doc(context.params.projectId).update({
instanceCount: admin.firestore.FieldValue.increment(-1),
})
);
In this example we increment an instanceCount field in the project each time a document is added to the instances sub collection. If the field doesn't exist yet it will be created and incremented to 1.
The incrementation is transactional internally but you should use a distributed counter if you need to increment more frequently than every 1 second.
It's often preferable to implement onCreate and onDelete rather than onWrite as you will call onWrite for updates which means you are spending more money on unnecessary function invocations (if you update the docs in your collection).
No, there is no built-in support for aggregation queries right now. However there are a few things you could do.
The first is documented here. You can use transactions or cloud functions to maintain aggregate information:
This example shows how to use a function to keep track of the number of ratings in a subcollection, as well as the average rating.
exports.aggregateRatings = firestore
.document('restaurants/{restId}/ratings/{ratingId}')
.onWrite(event => {
// Get value of the newly added rating
var ratingVal = event.data.get('rating');
// Get a reference to the restaurant
var restRef = db.collection('restaurants').document(event.params.restId);
// Update aggregations in a transaction
return db.transaction(transaction => {
return transaction.get(restRef).then(restDoc => {
// Compute new number of ratings
var newNumRatings = restDoc.data('numRatings') + 1;
// Compute new average rating
var oldRatingTotal = restDoc.data('avgRating') * restDoc.data('numRatings');
var newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;
// Update restaurant info
return transaction.update(restRef, {
avgRating: newAvgRating,
numRatings: newNumRatings
});
});
});
});
The solution that jbb mentioned is also useful if you only want to count documents infrequently. Make sure to use the select() statement to avoid downloading all of each document (that's a lot of bandwidth when you only need a count). select() is only available in the server SDKs for now so that solution won't work in a mobile app.
UPDATE 11/20
I created an npm package for easy access to a counter function: https://code.build/p/9DicAmrnRoK4uk62Hw1bEV/firestore-counters
I created a universal function using all these ideas to handle all counter situations (except queries).
The only exception would be when doing so many writes a second, it
slows you down. An example would be likes on a trending post. It is
overkill on a blog post, for example, and will cost you more. I
suggest creating a separate function in that case using shards:
https://firebase.google.com/docs/firestore/solutions/counters
// trigger collections
exports.myFunction = functions.firestore
.document('{colId}/{docId}')
.onWrite(async (change: any, context: any) => {
return runCounter(change, context);
});
// trigger sub-collections
exports.mySubFunction = functions.firestore
.document('{colId}/{docId}/{subColId}/{subDocId}')
.onWrite(async (change: any, context: any) => {
return runCounter(change, context);
});
// add change the count
const runCounter = async function (change: any, context: any) {
const col = context.params.colId;
const eventsDoc = '_events';
const countersDoc = '_counters';
// ignore helper collections
if (col.startsWith('_')) {
return null;
}
// simplify event types
const createDoc = change.after.exists && !change.before.exists;
const updateDoc = change.before.exists && change.after.exists;
if (updateDoc) {
return null;
}
// check for sub collection
const isSubCol = context.params.subDocId;
const parentDoc = `${countersDoc}/${context.params.colId}`;
const countDoc = isSubCol
? `${parentDoc}/${context.params.docId}/${context.params.subColId}`
: `${parentDoc}`;
// collection references
const countRef = db.doc(countDoc);
const countSnap = await countRef.get();
// increment size if doc exists
if (countSnap.exists) {
// createDoc or deleteDoc
const n = createDoc ? 1 : -1;
const i = admin.firestore.FieldValue.increment(n);
// create event for accurate increment
const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);
return db.runTransaction(async (t: any): Promise<any> => {
const eventSnap = await t.get(eventRef);
// do nothing if event exists
if (eventSnap.exists) {
return null;
}
// add event and update size
await t.update(countRef, { count: i });
return t.set(eventRef, {
completed: admin.firestore.FieldValue.serverTimestamp()
});
}).catch((e: any) => {
console.log(e);
});
// otherwise count all docs in the collection and add size
} else {
const colRef = db.collection(change.after.ref.parent.path);
return db.runTransaction(async (t: any): Promise<any> => {
// update size
const colSnap = await t.get(colRef);
return t.set(countRef, { count: colSnap.size });
}).catch((e: any) => {
console.log(e);
});;
}
}
This handles events, increments, and transactions. The beauty in this, is that if you are not sure about the accuracy of a document (probably while still in beta), you can delete the counter to have it automatically add them up on the next trigger. Yes, this costs, so don't delete it otherwise.
Same kind of thing to get the count:
const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const colSnap = await db.doc('_counters/' + collectionPath).get();
const count = colSnap.get('count');
Also, you may want to create a cron job (scheduled function) to remove old events to save money on database storage. You need at least a blaze plan, and there may be some more configuration. You could run it every sunday at 11pm, for example.
https://firebase.google.com/docs/functions/schedule-functions
This is untested, but should work with a few tweaks:
exports.scheduledFunctionCrontab = functions.pubsub.schedule('5 11 * * *')
.timeZone('America/New_York')
.onRun(async (context) => {
// get yesterday
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
const eventFilterSnap = await eventFilter.get();
eventFilterSnap.forEach(async (doc: any) => {
await doc.ref.delete();
});
return null;
});
And last, don't forget to protect the collections in firestore.rules:
match /_counters/{document} {
allow read;
allow write: if false;
}
match /_events/{document} {
allow read, write: if false;
}
Update: Queries
Adding to my other answer if you want to automate query counts as well, you can use this modified code in your cloud function:
if (col === 'posts') {
// counter reference - user doc ref
const userRef = after ? after.userDoc : before.userDoc;
// query reference
const postsQuery = db.collection('posts').where('userDoc', "==", userRef);
// add the count - postsCount on userDoc
await addCount(change, context, postsQuery, userRef, 'postsCount');
}
return delEvents();
Which will automatically update the postsCount in the userDocument. You could easily add other one to many counts this way. This just gives you ideas of how you can automate things. I also gave you another way to delete the events. You have to read each date to delete it, so it won't really save you to delete them later, just makes the function slower.
/**
* Adds a counter to a doc
* #param change - change ref
* #param context - context ref
* #param queryRef - the query ref to count
* #param countRef - the counter document ref
* #param countName - the name of the counter on the counter document
*/
const addCount = async function (change: any, context: any,
queryRef: any, countRef: any, countName: string) {
// events collection
const eventsDoc = '_events';
// simplify event type
const createDoc = change.after.exists && !change.before.exists;
// doc references
const countSnap = await countRef.get();
// increment size if field exists
if (countSnap.get(countName)) {
// createDoc or deleteDoc
const n = createDoc ? 1 : -1;
const i = admin.firestore.FieldValue.increment(n);
// create event for accurate increment
const eventRef = db.doc(`${eventsDoc}/${context.eventId}`);
return db.runTransaction(async (t: any): Promise<any> => {
const eventSnap = await t.get(eventRef);
// do nothing if event exists
if (eventSnap.exists) {
return null;
}
// add event and update size
await t.set(countRef, { [countName]: i }, { merge: true });
return t.set(eventRef, {
completed: admin.firestore.FieldValue.serverTimestamp()
});
}).catch((e: any) => {
console.log(e);
});
// otherwise count all docs in the collection and add size
} else {
return db.runTransaction(async (t: any): Promise<any> => {
// update size
const colSnap = await t.get(queryRef);
return t.set(countRef, { [countName]: colSnap.size }, { merge: true });
}).catch((e: any) => {
console.log(e);
});;
}
}
/**
* Deletes events over a day old
*/
const delEvents = async function () {
// get yesterday
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const eventFilter = db.collection('_events').where('completed', '<=', yesterday);
const eventFilterSnap = await eventFilter.get();
eventFilterSnap.forEach(async (doc: any) => {
await doc.ref.delete();
});
return null;
}
I should also warn you that universal functions will run on every
onWrite call period. It may be cheaper to only run the function on
onCreate and on onDelete instances of your specific collections. Like
the noSQL database we are using, repeated code and data can save you
money.
There is no direct option available. You cant't do db.collection("CollectionName").count().
Below are the two ways by which you can find the count of number of documents within a collection.
1 :- Get all the documents in the collection and then get it's size.(Not the best Solution)
db.collection("CollectionName").get().subscribe(doc=>{
console.log(doc.size)
})
By using above code your document reads will be equal to the size of documents within a collection and that is the reason why one must avoid using above solution.
2:- Create a separate document with in your collection which will store the count of number of documents in the collection.(Best Solution)
db.collection("CollectionName").doc("counts")get().subscribe(doc=>{
console.log(doc.count)
})
Above we created a document with name counts to store all the count information.You can update the count document in the following way:-
Create a firestore triggers on the document counts
Increment the count property of counts document when a new document is created.
Decrement the count property of counts document when a document is deleted.
w.r.t price (Document Read = 1) and fast data retrieval the above solution is good.
A workaround is to:
write a counter in a firebase doc, which you increment within a transaction everytime you create a new entry
You store the count in a field of your new entry (i.e: position: 4).
Then you create an index on that field (position DESC).
You can do a skip+limit with a query.Where("position", "<" x).OrderBy("position", DESC)
Hope this helps!
I have try a lot with different approaches.
And finally, I improve one of the methods.
First you need to create a separate collection and save there all events.
Second you need to create a new lambda to be triggered by time. This lambda will Count events in event collection and clear event documents.
Code details in article.
https://medium.com/#ihor.malaniuk/how-to-count-documents-in-google-cloud-firestore-b0e65863aeca
one of the fast + money saver trick is that:-
make a doc and store a 'count' variable in firestore, when user add new doc in the collection, increase that variable, and when user delete a doc, decrease variable. e.g.
updateDoc(doc(db, "Count_collection", "Count_Doc"), {count: increment(1)});
note: use (-1) for decreasing, (1) for increasing count
How it save money and time:-
you(firebase) don't need to loop through the collection, nor browser needs to load whole collection to count number of docs.
all the counts are save in a doc of only one variable named "count" or whatever, so less than 1kb data is used, and it use only 1 reads in firebase firestore.
Solution using pagination with offset & limit:
public int collectionCount(String collection) {
Integer page = 0;
List<QueryDocumentSnapshot> snaps = new ArrayList<>();
findDocsByPage(collection, page, snaps);
return snaps.size();
}
public void findDocsByPage(String collection, Integer page,
List<QueryDocumentSnapshot> snaps) {
try {
Integer limit = 26000;
FieldPath[] selectedFields = new FieldPath[] { FieldPath.of("id") };
List<QueryDocumentSnapshot> snapshotPage;
snapshotPage = fireStore()
.collection(collection)
.select(selectedFields)
.offset(page * limit)
.limit(limit)
.get().get().getDocuments();
if (snapshotPage.size() > 0) {
snaps.addAll(snapshotPage);
page++;
findDocsByPage(collection, page, snaps);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
findDocsPage it's a recursive method to find all pages of collection
selectedFields for otimize query and get only id field instead full body of document
limit max size of each query page
page define inicial page for pagination
From the tests I did it worked well for collections with up to approximately 120k records!
Firestore is introducing a new Query.count() that fetches the count of a query without fetching the docs.
This would allow to simply query all collection items and get the count of that query.
Ref:
Firebase 10 iOS SDK
[JS SDK PR] (https://github.com/firebase/firebase-js-sdk/pull/6608)
There's a new build in function since version 9.11.0 called getCountFromServer(), which fetches the number of documents in the result set without actually downloading the documents.
https://firebase.google.com/docs/reference/js/firestore_#getcountfromserver
Took me a while to get this working based on some of the answers above, so I thought I'd share it for others to use. I hope it's useful.
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.countDocumentsChange = functions.firestore.document('library/{categoryId}/documents/{documentId}').onWrite((change, context) => {
const categoryId = context.params.categoryId;
const categoryRef = db.collection('library').doc(categoryId)
let FieldValue = require('firebase-admin').firestore.FieldValue;
if (!change.before.exists) {
// new document created : add one to count
categoryRef.update({numberOfDocs: FieldValue.increment(1)});
console.log("%s numberOfDocs incremented by 1", categoryId);
} else if (change.before.exists && change.after.exists) {
// updating existing document : Do nothing
} else if (!change.after.exists) {
// deleting document : subtract one from count
categoryRef.update({numberOfDocs: FieldValue.increment(-1)});
console.log("%s numberOfDocs decremented by 1", categoryId);
}
return 0;
});
This uses counting to create numeric unique ID. In my use, I will not be decrementing ever, even when the document that the ID is needed for is deleted.
Upon a collection creation that needs unique numeric value
Designate a collection appData with one document, set with .doc id only
Set uniqueNumericIDAmount to 0 in the firebase firestore console
Use doc.data().uniqueNumericIDAmount + 1 as the unique numeric id
Update appData collection uniqueNumericIDAmount with firebase.firestore.FieldValue.increment(1)
firebase
.firestore()
.collection("appData")
.doc("only")
.get()
.then(doc => {
var foo = doc.data();
foo.id = doc.id;
// your collection that needs a unique ID
firebase
.firestore()
.collection("uniqueNumericIDs")
.doc(user.uid)// user id in my case
.set({// I use this in login, so this document doesn't
// exist yet, otherwise use update instead of set
phone: this.state.phone,// whatever else you need
uniqueNumericID: foo.uniqueNumericIDAmount + 1
})
.then(() => {
// upon success of new ID, increment uniqueNumericIDAmount
firebase
.firestore()
.collection("appData")
.doc("only")
.update({
uniqueNumericIDAmount: firebase.firestore.FieldValue.increment(
1
)
})
.catch(err => {
console.log(err);
});
})
.catch(err => {
console.log(err);
});
});
var variable=0
variable=variable+querySnapshot.count
then if you are to use it on a String variable then
let stringVariable= String(variable)
Along with my npm package adv-firestore-functions above, you can also just use firestore rules to force a good counter:
Firestore Rules
function counter() {
let docPath = /databases/$(database)/documents/_counters/$(request.path[3]);
let afterCount = getAfter(docPath).data.count;
let beforeCount = get(docPath).data.count;
let addCount = afterCount == beforeCount + 1;
let subCount = afterCount == beforeCount - 1;
let newId = getAfter(docPath).data.docId == request.path[4];
let deleteDoc = request.method == 'delete';
let createDoc = request.method == 'create';
return (newId && subCount && deleteDoc) || (newId && addCount && createDoc);
}
function counterDoc() {
let doc = request.path[4];
let docId = request.resource.data.docId;
let afterCount = request.resource.data.count;
let beforeCount = resource.data.count;
let docPath = /databases/$(database)/documents/$(doc)/$(docId);
let createIdDoc = existsAfter(docPath) && !exists(docPath);
let deleteIdDoc = !existsAfter(docPath) && exists(docPath);
let addCount = afterCount == beforeCount + 1;
let subCount = afterCount == beforeCount - 1;
return (createIdDoc && addCount) || (deleteIdDoc && subCount);
}
and use them like so:
match /posts/{document} {
allow read;
allow update;
allow create: if counter();
allow delete: if counter();
}
match /_counters/{document} {
allow read;
allow write: if counterDoc();
}
Frontend
Replace your set and delete functions with these:
set
async setDocWithCounter(
ref: DocumentReference<DocumentData>,
data: {
[x: string]: any;
},
options: SetOptions): Promise<void> {
// counter collection
const counterCol = '_counters';
const col = ref.path.split('/').slice(0, -1).join('/');
const countRef = doc(this.afs, counterCol, col);
const countSnap = await getDoc(countRef);
const refSnap = await getDoc(ref);
// don't increase count if edit
if (refSnap.exists()) {
await setDoc(ref, data, options);
// increase count
} else {
const batch = writeBatch(this.afs);
batch.set(ref, data, options);
// if count exists
if (countSnap.exists()) {
batch.update(countRef, {
count: increment(1),
docId: ref.id
});
// create count
} else {
// will only run once, should not use
// for mature apps
const colRef = collection(this.afs, col);
const colSnap = await getDocs(colRef);
batch.set(countRef, {
count: colSnap.size + 1,
docId: ref.id
});
}
batch.commit();
}
}
delete
async delWithCounter(
ref: DocumentReference<DocumentData>
): Promise<void> {
// counter collection
const counterCol = '_counters';
const col = ref.path.split('/').slice(0, -1).join('/');
const countRef = doc(this.afs, counterCol, col);
const countSnap = await getDoc(countRef);
const batch = writeBatch(this.afs);
// if count exists
batch.delete(ref);
if (countSnap.exists()) {
batch.update(countRef, {
count: increment(-1),
docId: ref.id
});
}
/*
if ((countSnap.data() as any).count == 1) {
batch.delete(countRef);
}*/
batch.commit();
}
see here for more info...
J
This feature is now supported in FireStore, albeit in Beta.
Here are the official Firebase docs
With the new version of Firebase, you can now run aggregated queries!
Simply write
.count().get();
after your query.
As it stands, firebase only allows server-side count, like this
const collectionRef = db.collection('cities');
const snapshot = await collectionRef.count().get();
console.log(snapshot.data().count);
Please not this is for nodeJS
New feature available in Firebase/Firestore provides a count of documents in a collection:
See this thread to see how to achieve it, with an example.
How To Count Number of Documents in a Collection in Firebase Firestore With a WHERE query in react.js
According to this documentation Cloud Firestore supports the count() aggregation query and is available in preview.
The Flutter/Dart code was missing (at the time of writing this) so I played around with it and the following function seems to work:
Future<int> getCount(String path) async {
var collection = _fireStore.collection(path);
var countQuery = collection.count();
var snapShot = await countQuery.get(source: AggregateSource.server);
return snapShot.count;
}
firebaseFirestore.collection("...").addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
int Counter = documentSnapshots.size();
}
});
So my solution for this problem is a bit non-technical, not super precise, but good enough for me.
Those are my documents. As I have a lot of them (100k+) there are 'laws of big numbers' happening. I can assume that there is less-or-more equal number of items having id starting with 0, 1, 2, etc.
So what I do is I scroll my list till I get into id's starting with 1, or with 01, depending on how long you have to scroll
👆 here we are.
Now, having scrolled so far, I open the inspector and see how much did I scroll and divide it by height of single element
Had to scroll 82000px to get items with id starting with 1. Height of single element is 32px.
It means I have 2500 with id starting with 0, so now I multiply it by number of possible 'starting char'. In firebase it can be A-Z, a-z, 0-9 which means it's 24 + 24 + 10 = 58.
It means I have ~~2500*58 so it gives roughly 145000 items in my collection.
Summarizing: What is wrong with you firebase?

How to create and return an array of objects using protractor page object model design and promises

I'm creating a page object model and one of the properties is all the users from a table. The table has a few columns so I'd like to parse that table and create a user object for each row, then return that set to then be used in tests. So far, this is what that property of my page object looks like:
users: {
get: function() {
let userRecords = [];
var users = element.all(by.repeater('user in users')).each(function(tr, index) {
let user = {
name: tr.element(by.css('td:nth-child(2)')).getText().then(text => {return text;}),
role: tr.element(by.css('td:nth-child(3)')).getText().then(text => {expect(text).toBeTruthy();}),
status: tr.element(by.css('td:nth-child(4)')).getText().then(text => {expect(text).toBeTruthy();}),
//actionsButton: tr.element(by.css('btn btn-default'))
};
userRecords += user;
}).then(userRecords => {return userRecords});
return userRecords;
}
},
Through trial and error I encounter one of two outcomes when just trying to print to screen each element of userRecords:
each element prints as undefined or
userRecords is not defined.
Just to reiterate, I'm simply trying to build an array that holds each user as an object to then be able to iterate / filter on that set in my tests.
Given the approach I'm taking, what's the ideal way to construct this user array and resolve the promises?
Edit: it's worth noting that if I do a console.log() within each of the getText().then() statements, it does print the correct data from the table. So, I do know that it's reading the table correctly.
I'd go with a method that returns json, and would make it async
users: async function() {
let userRecords = [];
var users = element.all(by.repeater('user in users'));
for (let i = 0; i < await users.count(); i++) {
let tr = users.get(i);
let user = {
name: await tr.element(by.css('td:nth-child(2)')).getText(),
role: await tr.element(by.css('td:nth-child(3)')).getText(),
status: await tr.element(by.css('td:nth-child(4)')).getText()
};
userRecords.push()
}
return userRecords;
},
and then use:
console.log(
JSON.stringify(
await constructorName.users()
)
)
should be as simple as that. Note, I didn't test the code, but I did use the approach in my experience. So it may require some minor modifications
In general, try to avoid .then - async/await is easier to use, .each - go with for loop instead. Also userRecords += user; doesn't do what you think it does (though I may be wrong here)

MongoDB - Mongoose - TypeError: save is not a function

I am attempting to perform an update to a MongoDB document (using mongoose) by first using .findById to get the document, then updating the fields in that document with new values. I am still a bit new to this so I used a tutorial to figure out how to get it working, then I have been updating my code for my needs. Here is the tutorial: MEAN App Tutorial with Angular 4. The original code had a schema defined, but my requirement is for a generic MongoDB interface that will simply take whatever payload is sent to it and send it along to MongoDB. The original tutorial had something like this:
exports.updateTodo = async function(todo){
var id = todo.id
try{
//Find the old Todo Object by the Id
var oldTodo = await ToDo.findById(id);
}catch(e){
throw Error("Error occured while Finding the Todo")
}
// If no old Todo Object exists return false
if(!oldTodo){
return false;
}
console.log(oldTodo)
//Edit the Todo Object
oldTodo.title = todo.title
oldTodo.description = todo.description
oldTodo.status = todo.status
console.log(oldTodo)
try{
var savedTodo = await oldTodo.save()
return savedTodo;
}catch(e){
throw Error("And Error occured while updating the Todo");
}
}
However, since I don't want a schema and want to allow anything through, I don't want to assign static values to specific field names like, title, description, status, etc. So, I came up with this:
exports.updateData = async function(update){
var id = update.id
// Check the existence of the query parameters, If they don't exist then assign a default value
var dbName = update.dbName ? update.dbName : 'test'
var collection = update.collection ? update.collection : 'testing';
const Test = mongoose.model(dbName, TestSchema, collection);
try{
//Find the existing Test object by the Id
var existingData = await Test.findById(id);
}catch(e){
throw Error("Error occurred while finding the Test document - " + e)
}
// If no existing Test object exists return false
if(!existingData){
return false;
}
console.log("Existing document is " + existingData)
//Edit the Test object
existingData = JSON.parse(JSON.stringify(update))
//This was another way to overwrite existing field values, but
//performs a "shallow copy" so it's not desireable
//existingData = Object.assign({}, existingData, update)
//existingData.title = update.title
//existingData.description = update.description
//existingData.status = update.status
console.log("New data is " + existingData)
try{
var savedOutput = await existingData.save()
return savedOutput;
}catch(e){
throw Error("An error occurred while updating the Test document - " + e);
}
}
My original problem with this was that I had a lot of issues getting the new values to overwrite the old ones. Now that that's been solved, I am getting the error of "TypeError: existingData.save is not a function". I am thinking the data type changed or something, and now it is not being accepted. When I uncomment the static values that were in the old tutorial code, it works. This is further supported by my console logging before and after I join the objects, because the first one prints the actual data and the second one prints [object Object]. However, I can't seem to figure out what it's expecting. Any help would be greatly appreciated.
EDIT: I figured it out. Apparently Mongoose has its own data type of "Model" which gets changed if you do anything crazy to the underlying data by using things like JSON.stringify. I used Object.prototype.constructor to figure out the actual object type like so:
console.log("THIS IS BEFORE: " + existingData.constructor);
existingData = JSON.parse(JSON.stringify(update));
console.log("THIS IS AFTER: " + existingData.constructor);
And I got this:
THIS IS BEFORE: function model(doc, fields, skipId) {
model.hooks.execPreSync('createModel', doc);
if (!(this instanceof model)) {
return new model(doc, fields, skipId);
}
Model.call(this, doc, fields, skipId);
}
THIS IS AFTER: function Object() { [native code] }
Which showed me what was actually going on. I added this to fix it:
existingData = new Test(JSON.parse(JSON.stringify(update)));
On a related note, I should probably just use the native MongoDB driver at this point, but it's working, so I'll just put it on my to do list for now.
You've now found a solution but I would suggest using the MongoDB driver which would make your code look something along the lines of this and would make the origional issue disappear:
// MongoDB Settings
const MongoClient = require(`mongodb`).MongoClient;
const mongodb_uri = `mongodb+srv://${REPLACE_mongodb_username}:${REPLACE_mongodb_password}#url-here.gcp.mongodb.net/test`;
const db_name = `test`;
let db; // allows us to reuse the database connection once it is opened
// Open MongoDB Connection
const open_database_connection = async () => {
try {
client = await MongoClient.connect(mongodb_uri);
} catch (err) { throw new Error(err); }
db = client.db(db_name);
};
exports.updateData = async update => {
// open database connection if it isn't already open
try {
if (!db) await open_database_connection();
} catch (err) { throw new Error(err); }
// update document
let savedOutput;
try {
savedOutput = await db.collection(`testing`).updateOne( // .save() is being depreciated
{ // filter
_id: update.id // the '_id' might need to be 'id' depending on how you have set your collection up, usually it is '_id'
},
$set: { // I've assumed that you are overwriting the fields you are updating hence the '$set' operator
update // update here - this is assuming that the update object only contains fields that should be updated
}
// If you want to add a new document if the id isn't found add the below line
// ,{ upsert: true }
);
} catch (err) { throw new Error(`An error occurred while updating the Test document - ${err}`); }
if (savedOutput.matchedCount !== 1) return false; // if you add in '{ upsert: true }' above, then remove this line as it will create a new document
return savedOutput;
}
The collection testing would need to be created before this code but this is only a one-time thing and is very easy - if you are using MongoDB Atlas then you can use MongoDB Compass / go in your online admin to create the collection without a single line of code...
As far as I can see you should need to duplicate the update object. The above reduces the database calls from 2 to one and allows you to reuse the database connection, potentially anywhere else in the application which would help to speed things up. Also don't store your MongoDB credentials directly in the code.

NodeJS and Mongoose Callback inside a For Loop

So here's the problem. I have a REST API that handles a booking creation, however, before saving the booking inside mongo it validates if there is a clash with another booking.
exports.create = function(req, res) {
var new_type = new Model(req.body);
var newBooking = new_type._doc;
//check if the new booking clashes with existing bookings
validateBooking.bookingClash(newBooking, function(clash){
if(clash == null) // no clashes, therefore save new booking
{
new_type.save(function(err, type) {
if (err)
{
res.send(err); //error saving
}
else{
res.json(type); //return saved new booking
}
});
}
else //clash with booking
{
//respond with "clashDate"
}
});
};
Here you have the validation function to check if there is a clash with bookings on the same day:
exports.bookingClash = function (booking, clash) {
//find the bookings for the same court on the same day
var courtId = (booking.courtId).toString();
Model.find({courtId: courtId, date: booking.date}, function(err, bookings) {
if(err == null && bookings == null)
{
//no bookings found so no clashes
clash(null);
}
else //bookings found
{
//for each booking found, check if the booking start hour falls between other booking hours
for(var i = 0; i<bookings.length ; i++)
{
//here is where I check if the new booking clashes with bookings that are already in the DB
{
//the new booking clashes
//return booking date of the clash
clash(clashDate); //return the clashDate in order to tell the front-end
return;
}
}
//if no clashes with bookings, return null
clash(null);
}
});
};
So, ALL of this works with one single new booking. However, now I want to be able to handle a recursive booking (booking that is made weekly). I have recreated the "create" function and call the validateBooking.bookingClash function inside a for loop.
Unfortunately, when I run this, it calls the bookingClash function perfectly, but when it reaches the line making the search in the database:
Model.find({courtId: courtId, date: booking.date}, function(err, bookings)
It does not wait for the callback and before handling the response "clash", makes i++ and continues.
How can I make it work and wait for the callback?
var array = req.body;
var clashes = [];
for(var i = 0; i<array.length;i++)
{
validateBooking.bookingClash(array[i], function(clash)
{
if(clash)
{
clashes.push(clash);
}
else{
console.log("no clash");
}
}
}
Seems like a basic async call problem, for loops do not wait for callbacks to be called.
You could use async 'series' function for exmaple instead of the for loop. This way each find will get called after the previous one.
Mongoose also has a promise based syntax which can help you : http://mongoosejs.com/docs/promises.html
You Can use async eachSeries
async.eachSeries(users, function iterator(user, callback) {
if(something) {
//thing you want to do
callback();
} else {
callback();
}
}
Since you are using callback functions there are two ways you could try to solve this:
1) use some external library that allows you to perform an asynchronous map operation and run all the checks for each clash. Once they are done check the combined results for a clash and proceed accordingly
I would suggest using the async library
your code would look something like:
async.map(array,(entry,callback) => validateBooking.bookingClash(entry,callback),(error,mappingResults)=>{...})
2) you could try to change this function to a recursive one
`function recursiveValidation(arrayToCheck,mainCallback){
if(arrayToCheck.length === 0) {
return cb(null} // end of array without errors
}
validateBooking.bookingClash(_.head(arrayToCheck), function(clash)
{
if(clash)
{
return mainCallback(clash);
}
return recursiveValidation(_.tail(arrayToCheck),mainCallback);
}
}`
The above code is just a mockup but it should show the point.
The _ is lodash
No need to changing anything in your code except the declaration use let instead of var and your loop should work.
var array = req.body;
var clashes = [];
`
for(**let** i = 0; i<array.length;i++)
{
validateBooking.bookingClash(array[i], function(clash)
{
if(clash)
{
clashes.push(clash);
}
else{
console.log("no clash");
}
}
}`
You have to understand the difference between let and var. Also why var cannot be used for running async code inside a loop.
Learn about let: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
I found the way to get this done after trying all of your answers.
What I had to do was this:
validateBooking.singleBooking(new_type._doc, newBookingClubId, function (clash) {
if (clash == null) // no clash
{
validatorArray.push(0);
if(validatorArray.length == array.length) //has received everything from mongo
{
console.log("Clashes: " + clashes.toString());
if(validatorArray.indexOf(1) > -1) //contains a clash
{
var error = {
code: 409,
message: "409 Conflict",
clashes: clashes
};
errorsHandler.handleError(error, res);
}
This way, I created an array called "validatorArray" that was called every time I received something back from Mongo.
This way I could easily compare the length of the array of bookings and the validatorArray length. When they were equal, it meant that it had received everything back from mongo and could send back the response.
Thanks for the help!

Categories

Resources