Typescript (remove the types if you use Javascript) version for getting random documents using random integers (and wrap-around). This answer uses Cloud function for testing but essentially you just need to copy the getDocuments
function.
export const getRandomDoc = functions.https.onRequest(async (req, res): Promise<any> => {
try {
const randomDocs = await getDocuments(2)
return res.send(randomDocs)
} catch (err) {
return res.send(err)
}
});
const getDocuments = async (count: number): Promise<Array<FirebaseFirestore.DocumentData>> => {
const randomNum = Math.floor(Math.random() * 10000)
const snapshot = await admin.firestore().collection("col").where("rInt", ">=", randomNum).limit(count).get()
if (snapshot.empty) {
return getDocuments(count)
}
return snapshot.docs.map(d => d.data())
}
Whenever you add a new document to that collection, add the rInt
field along with it which is an integer between 0 to 10000 (both inclusive). You pass the number of documents you need in the getDoucments
function. It will fetch N consecutive matched docs though as this uses limit()
method.
In the query we look for documents where rInt
is greater than or equal to that random number generated using Math.random()
and limit the results to count
parameter passed in the function. If the snapshot is empty we retry the function. (It'll be worth to add a logic which makes this function repeat only N number of time else recursion will take it's time).
Using .limit()
as in the function above will end up returning N documents in a row. By default, docs will be ordered by their document ID unless you specify any particular field using orderBy
method. Instead making a separate request for each doc will increase the randomicity.
/**
* @param {number} count - number of documents to retrieve
* @param {number} loopNum - number of times the loop is being repeated. Defaults to 0
* @param {Array<any>} curDocs - array of documents matched so far. Defaults to an empty array
* @returns
*/
const getDocuments = async (count: number, loopNum: number, curDocs: Array<any>): Promise<Array<FirebaseFirestore.DocumentData>> => {
// Creating an array of requests to get documents
const requests = []
for (let i = 0; i < count; i++) {
// New random number for each request
const randomNum = Math.floor(Math.random() * 10000)
console.log(`Random Num: ${randomNum}`);
requests.push(admin.firestore().collection("col").where("rInt", ">=", randomNum).limit(1).get())
// limit is set to 1 so each request will return 1 document only
}
// Using Promise.all() to run all the promises
const snapshots = await Promise.all(requests)
// Removing empty snapshots
const filteredSnapshots = snapshots.filter(d => !d.empty)
// Creating an array of doc data
const matchedDocs = filteredSnapshots.map(doc => doc.docs[0].data())
// If documents received are less than requested,
// repeat the function
if (matchedDocs.length !== count) {
// If the function is repeated 5 times,
// return with whatever has matched so far
if (loopNum + 1 === 5) {
console.log("Returning CurDocs")
return curDocs
}
return getDocuments(count, loopNum + 1, [...curDocs, ...matchedDocs])
}
// Return if you get requested num of docs in first go
return snapshots.map(d => d.docs[0].data())
}
// Calling the function
getDocuments(5, 0, [])
Do note that you can use in
operator if you are requesting less than or equal to 10 documents. The in operator combines up to 10 equality (==) clauses on the same field with a logical OR.
For original version (swift) by @Dan McGrath, check this answer.