PromiseKit - Pass previous result to next promise in chain - ios

Im trying to follow the Documentation to pass the result of a promise to the next promise
https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#saving-previous-results
I keep getting
Contextual closure type '(_) throws -> _' expects 1 argument, but 2 were used in closure body
Here my Code
class func loginInOrSignup() -> Promise<PFUser> {
let lm = LoginManager()
lm.logOut()
return logInInBackground().then{user in
if user.isNew{
return getUserInfo().map{ ($0, user) }
}
else{
user.fetchIfNeededInBackground()
return Promise.value(user)
}
}.then{(userInfo, user) -> Promise<PFUser> in
let name = userInfo["name"] as! String
if let email = userInfo["email"] as? String {
user.email = email
}
let username = G8FacebookLogin.generateSuggestedUsername(name: name)
user.username = username
return Promise.value(user)
}
}
private class func logInInBackground() -> Promise<PFUser>{
return Promise {seal in
PFFacebookUtils.logInInBackground(withReadPermissions: ["public_profile", "email"]){user, error in
guard let user = user else {
seal.reject(error ?? AuthError.msg(reason: "Some FB Error"))
return
}
seal.fulfill(user)
}
}
}
private class func getUserInfo() -> Promise<Dictionary<String,Any>> {
return Promise {seal in
G8FacebookLogin.getUserInfo { (userDict, error) in
guard let userInfo = userDict else{
PFUser.logOut()
seal.reject(AuthError.loginFailed(reason: "no user Info retrieved"))
return
}
seal.fulfill(userInfo)
}
}
}
It's pretty much the same as the Snippet code in the documentation. I don't understand what is the right way to do it.

Related

Cannot convert return expression of type 'EventLoopFuture<String>' to return type 'String'

I have this error in my Vapor project:
Cannot convert return expression of type 'EventLoopFuture<String> ' to return type 'String'
app.get("user", ":uuid") { req throws -> EventLoopFuture<String> in
let uuid = req.parameters.get("uuid") ?? ""
return User.query(on: req.db).filter(\.$uuid == uuid).first().flatMapThrowing { user in
if let user = user {
// return a string value
return try Response(data: ["user": user.makeDictionary()])
}else {
// not found, return all users
// problem is: Cannot convert return expression of type 'EventLoopFuture<String>' to return type 'String'
return User.query(on: req.db).all().map { $0.map { $0.makeDictionary() } }.flatMapThrowing { dicArray in
return try Response(data: ["users": dicArray])
}
}
}
}
How can I resolve it?
You just declared that you will return String in the future, while you're trying to return Response. And also you have to replace flatMapThrowing with tryFlatMap.
Fixed code
app.get("user", ":uuid") { req throws -> EventLoopFuture<Response> in
let uuid = req.parameters.get("uuid") ?? ""
return User
.query(on: req.db)
.filter(\.$uuid == uuid)
.first()
// here you can't use flatMapThrowing
// cause it doesn't support EventLoopFuture result
.tryFlatMap { user -> EventLoopFuture<Response> in
if let user = user {
// here you wrap simple return with future
// cause it is needed for tryFlatMap
let response = try Response(data: ["user": user.makeDictionary()])
return req.eventLoop.makeSucceededFuture(response)
} else {
// here you return future with User
return User.query(on: req.db).all()
// then you transform it to future with dictionary
.map { $0.map { $0.makeDictionary() } }
// then you transform it to future with Response
.flatMapThrowing { dicArray in
return try Response(data: ["users": dicArray])
}
}
}
}

Code after guard is called later than expected

Hi I am new in iOS development and I am having hard time to understand the following issue. Basically I am trying to get user's name by passing current user's id to Cloud Firestore. However I am having hard time to understand a bug in the code. I can successfully pass the name of user to name variable, while the function returns default value of name which is "" (empty string). It seems that the block of code inside
if let data = snapshot?.data() {
guard let userName = data["name"] as? String else { return }
name = userName
print("after guard") // this line
}
happens later than
print("name") // this line
return name
Full code:
private func returnCurrentUserName() -> String {
// User is signed in.
var name = ""
if let user = Auth.auth().currentUser {
let db = Firestore.firestore()
db.collection("users").document(user.uid).getDocument { (snapshot, error) in
if error == nil {
if let data = snapshot?.data() {
guard let userName = data["name"] as? String else { return }
name = userName
print("after guard") // this line
}
}
}
print("name") // this line
return name
}else {
return ""
}
}
(Note: the query from Cloud Firestore is successful and I can get users name on the console but "name" is printed after "after guard".)
In addition to the other answer:
If you would like to execute code after your operation is done, you could use a completion block (that's just a closure which gets called upon completion):
private func returnCurrentUserName(completion: #escaping () -> ()) -> String {
// User is signed in.
var name = ""
if let user = Auth.auth().currentUser {
let db = Firestore.firestore()
db.collection("users").document(user.uid).getDocument { (snapshot, error) in
if error == nil {
if let data = snapshot?.data() {
guard let userName = data["name"] as? String else { return }
name = userName
completion()//Here you call the closure
print("after guard") // this line
}
}
}
print("name") // this line
return name
}else {
return ""
}
}
How you would call returnCurrentUserName:
returnCurrentUserName {
print("runs after the operation is done")
}
Simplified example:
func returnCurrentUserName(completion: #escaping () -> ()) -> String {
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
completion() //runs after 4 seconds
}
return "xyz"
}
let test = returnCurrentUserName {
print("runs after the operation is done")
}
print(test)
The reason is your getDocument is an asynchronous operation. It takes a callback, and that callback will be invoked when the operation is done. Because of the asynchronous operation, the program will continue process the next line without waiting for the async operation to be completed. That's why you see your print("name") getting executed before the print("after guard")

Reassigning a value in an if statement in Swift [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
the following is printing "no user with username" but is printing retVal as "false" ( I changed function to a string just for troubleshooting, ideally this should be bool ) I am new to swift and this is absolutely driving me crazy. it is making it to the chunk of code where retVal would get reassigned, but it isn't reassigning it
static func isUserNameUnique(_ username : String) -> String {
var retVal = "false"
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments { (document, error) in
if document!.isEmpty {
retVal = "true"
print("No user with username")
}
}
print("\(retVal)")
return retVal
}
func validateFields() -> String? {
//Check that all fields are filled in
if premierCodeTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" || userNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
return "Please fill in all fields."
}
//Check unique username
let cleanedUserName = userNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
Utilities.isUserNameUnique(cleanedUserName) { res in
if !res {
// return "please choose a unique username"
}
}
return nil
}
You are trying to return a value synchronously while using and asynchronous method.
When you do newQuery.getDocuments execution continues without waiting for completion handler to be called. So after that line is executed, the return is executed, and THEN the completion handler gets called.
If you want to get a value from an asynchronous method, you need to create a method that takes a completion handler like the answer Khan gave you.
static func isUserNameUnique(_ username: String, completionHandler: #escaping (Bool) -> Void) {
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments { (document, error) in
completionHandler(document!.isEmpty)
}
}
You need a completion as the request is asynchnous , plus use Bool instead of a String
static func isUserNameUnique(_ username : String,completion:#escaping((Bool) ->())) {
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments { (document, error) in
completion(document!.isEmpty)
}
}
Call
Utilities.isUserNameUnique { res in
if !res {
// name exists
}
}
It's impossible to achieve what you want since newQuery.getDocuments isn't returning value instantly. It will answer you at some point by calling function that you passed to it.
Your code can be described as
func foo() -> String {
// set retVal to "false"
var retVal = "false"
// create query
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
// ask query to evaluate
newQuery.getDocuments { (document, error) in
// at some point probably after foo ends
if document!.isEmpty {
// if document is not empty set retVal to "true" (at this point there is no-one that could look at value of retVal)
retVal = "true"
print("No user with username")
}
}
// while query is evaluating in background
// print retVal (probably still "false")
print("\(retVal)")
// return retVal (probably still "false")
return retVal
}
Now let's fix your problem.
Solution could be:
class X {
private var document: <insert correct type here>? {
didSet {
// do what you want with document
}
}
func foo() {
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments {
[weak self] (document, error) in // [weak self] is important!
// I have no idea on which thread firebase runs it's callback
// It's important that only one thread could modify self.document
// otherwise you will have race condition and a lot of strange behaviours
DispatchQueue.main.async {
self?.document = document;
}
}
}
}
If you really need to create func foo() -> String and you don't care that your thread will have to wait (UI will not respond, you will have 0 fps etc) you can do it using NSLock (I won't post code since it's really bad idea in most of the cases).

Proper way to throw error from my closure?

I'm actually trying to some code logic with swift for training purpose at the moment, I was wondering what is the proper way to throw my error from my init ?
So the flow is Controller ask for account creation when initializing Model is asking my sql manager to create the account and this method return the result from a closure.
But something feels wrong, should I just use a return from the sql manager who contained both my Int? and Error? ?
init(_ username: String, _ password: String) throws {
self.id = 0
self.username = username
self.password = password
var toThrow: Error? = nil
// Register in database
userManager.create(self) { (id: Int?, err: Error?) in
Thread.sleep(forTimeInterval: 10)
if let error = err {
// Register in database goes wrong
debugPrint("Handle error from user creation...")
toThrow = error
} else {
// There is no id and no error ?
guard let _ = id else { return }
self.id = id!
}
}
if let error = toThrow {
throw error
}
}
If you are on Swift 5 you could look into using Result and define your closure like
(id: Int) -> Result<Int, Error>
and change your code to
userManager.create(self) { (id: Int?) -> Result<Int, Error> in
Thread.sleep(forTimeInterval: 10)
if let error = err {
// Register in database goes wrong
debugPrint("Handle error from user creation...")
return .failure(error)
} else {
// There is no id and no error ?
guard let _ = id else { return }
return .success(id)
}
}
If you have your own error enum for the Db class like
enum DbError {
case create
case update
//...
}
Then you can use that type in the closure declaration
(id: Int?) -> Result<Int, DbError>
and return a specific error for this action
return .failure(.create)
Note that I haven't compiled this so consider it an example
Here is the solution if people want to see:
#IBAction func didPressRegister() {
guard let username = usernameField.text else { return }
guard let password = passwordField.text else { return }
let user = UserModel(username, password)
userManager.create(user) { result in
switch(result) {
case .failure(let error):
// TODO: UIAlert
debugPrint(error)
case .success(let int):
// TODO: Generate user token and redirect main
debugPrint(int)
}
}
}
// TODO
public func create(_ user: UserModel, _ complete: #escaping (Result<Int, Error>) -> ()) {
debugPrint("Requested to create the user... \(user)")
complete(.failure(toThrow.ACCOUNT_ERROR))
}

Unexpected non-void return value in void function Swift3

I have a function that returns either a class object or nil. The function's purpose is to check if a Chat exists. The chat ID's are stored in MySQL. If the ID exists, I perform a Firebase reference to get a snapshot and then get the object. If the ID does not exist, I return nil:
func findChat(string: String) -> Chat? {
var returnValue: (Chat?)
let url = getChatsURL
let Parameters = [ "title" : string ]
Alamofire.request("\(url)", method: .post, parameters: Parameters).validate().responseString { response in
if let anyResponse = response.result.value {
self.responseFromServer = anyResponse
}
if self.responseFromServer == "" {
returnValue = nil
} else {
let ref = DatabaseReference.chats.reference()
let query = ref.queryOrdered(byChild: "uid").queryEqual(toValue: (self.responseFromServer))
query.observe(.childAdded, with: { snapshot in
returnValue = Chat(dictionary: snapshot.value as! [String : Any])
})
}
return returnValue
}
}
However, at return returnValue I am getting
Unexpected non-void return value in void function.
Any thoughts of what I could be missing?
The problem is that you are trying to return a non-void value from inside a closure, which only returns from the closure, but since that closure expects a void return value, you receive the error.
You cannot return from an asynchronous function using the standard return ... syntax, you have to declare your function to accept a completion handler and return the value from the async network call inside the completion handler.
func findChat(string: String, completion: #escaping (Chat?)->()) {
var returnValue: (Chat?)
let url = getChatsURL
let Parameters = [ "title" : string ]
Alamofire.request("\(url)", method: .post, parameters: Parameters).validate().responseString { response in
if let anyResponse = response.result.value {
self.responseFromServer = anyResponse
}
if self.responseFromServer == "" {
completion(nil)
} else {
let ref = DatabaseReference.chats.reference()
let query = ref.queryOrdered(byChild: "uid").queryEqual(toValue: (self.responseFromServer))
query.observe(.childAdded, with: { snapshot in
completion(Chat(dictionary: snapshot.value as! [String : Any]))
})
}
}
}
Then you can call this function and use the return value like this:
findChat(string: "inputString", completion: { chat in
if let chat = chat {
//use the return value
} else {
//handle nil response
}
})
Your block is executed asynchronously, but you're trying to return a value from the enclosing function. It doesn't work that way. Your findChat function needs to take a completion block itself instead of returning a value, and then you can call that completion block from the point where you're trying to say return returnValue.

Resources