So I'm trying to make sure a set of async tasks get executed in a specific order when a user is being deleted.
So what I want to happen is :
- Check if user has added guests with their purchase
- if user has no guests or any purchases at all, return from the function and continue with deletion process (bullet point 6)
- if user has guests for any of their purchases, delete every single guest
- once all the guests are deleted, go ahead and delete every purchase they made
- once all purchases made are deleted, go ahead and delete the actual user itself out of Firestore
- 2 seconds after that, I delete the user out of firebase auth just to make sure there are no crashes trying to delete documents with an empty user
- then I simply segue to the main menu
So I'm trying to accomplish this with this block of code in my function:
let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { (deletion) in
let semaphore = DispatchSemaphore(value: 0)
self.deleteButton.isHidden = true
self.loadingToDelete.alpha = 1
self.loadingToDelete.startAnimating()
DispatchQueue.global(qos: .background).async {
self.db.collection("student_users/\(user.uid)/events_bought").getDocuments { (querySnapshot, error) in
guard error == nil else {
print("The docs couldn't be retrieved for deletion.")
return
}
guard querySnapshot?.isEmpty == false else {
print("The user being deleted has no events purchased.")
return
}
for document in querySnapshot!.documents {
let docID = document.documentID
self.db.collection("student_users/\(user.uid)/events_bought/\(docID)/guests").getDocuments { (querySnap, error) in
guard querySnap?.isEmpty == false else {
print("The user being deleted has no guests with his purchases.")
return
}
for doc in querySnap!.documents {
let guest = doc.documentID
self.db.document("student_users/\(user.uid)/events_bought/\(docID)/guests/\(guest)").delete { (error) in
guard error == nil else {
print("Error deleting guests while deleting user.")
return
}
print("Guests deleted while deleting user!")
semaphore.signal()
}
semaphore.wait()
}
}
}
}
self.db.collection("student_users/\(user.uid)/events_bought").getDocuments { (querySnapshot, error) in
guard error == nil else {
print("There was an error retrieving docs for user deletion.")
return
}
guard querySnapshot?.isEmpty == false else {
return
}
for document in querySnapshot!.documents {
let docID = document.documentID
self.db.document("student_users/\(user.uid)/events_bought/\(docID)").delete { (err) in
guard err == nil else {
print("There was an error deleting the the purchased events for the user being deleted.")
return
}
print("Purchases have been deleted for deleted user!")
semaphore.signal()
}
semaphore.wait()
}
}
self.db.document("student_users/\(user.uid)").delete(completion: { (error) in
guard error == nil else {
print("There was an error deleting the user document.")
return
}
print("User doc deleted!")
semaphore.signal()
})
semaphore.wait()
}
DispatchQueue.main.asyncAfter(deadline: .now()+1.5) {
user.delete(completion: { (error) in
guard error == nil else {
print("There was an error deleting user from the system.")
return
}
print("User Deleted.")
})
}
self.loadingToDelete.stopAnimating()
self.performSegue(withIdentifier: Constants.Segues.studentUserDeletedAccount, sender: self)
}
I've been trying to play around with DispatchSemaphore()
for the last couple of hours and implement it into my code, but it just doesn't do what I expect it to do. I would read up on articles and examples of DispatchSemaphore()
online but the scenarios aren't exactly the same as mine in regards to what I specifically want to do.
When this alert action gets triggered on tap, nothing prints, and it just ends up spinning forever, the user isn't deleted out of Firebase Auth and there is still leftover data in the Firestore database like so:
I just basically want to figure out the best way to order these async tasks in the ordered list above and have a clean user deletion with no leftover in the database. Thanks in advance.
You should be handling this with a Firebase Cloud Function which has multiple ways of reacting to client requests and database changes. This does require billing and migrating your code to javascript with node v10 which is fairly straightforward due to the consistent methods of firebase across most languages.
Firebase Function
Two popular methods are simply importing the firebase cloud functions module into your app or calling the request over https, each has its own entry points with pros and cons which are worth reading into.
From there, you would delete the core files that would impact the user immediately, then updating the client on its result before proceeding with your clean-up of residual files.
Firestore Trigger
An alternative that is just as sound and more manageable from potential abuse is invoking a trigger based on a document deletion, you can use this to then trigger other documents to proceed to be removed and cleaned up
You can read about them below, and it can contain fundamentally the same logic in both situations but this option doesn't require the bulky firebase functions module.
https://firebase.google.com/docs/functions/firestore-events#function_triggers
Update: Async
Async methods are simply functions that are flagged as
async
that allow tasks to operate without blocking structure, this allows multiple tasks to be fired without depending on another. However, to pause your code to wait for something to be done, you can append theawait
flagreference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Update: Promises
Promises work the same as async functions and run independently to the parent function and itself can be flagged with
await
if need be.reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise