1

I implemented PromiseKit in Swift to avoid callback hell with completion blocks. I need to know the best way to chain promises together to init custom objects that have other associated objects. For example a Comment object that has a User object attached to it.

First I fetch the comments from the DB, which all have a uid property in the DB structure. I ultimately want to end up with an array of comments, where each one has the correct user attached to it, so I can load both the comment and user data. This all seemed much easier with completion blocks, but I'm a total Promise noob so idk.

Here is the code in the controller that handles fetch

CommentsService.shared.fetchComments(withPostKey: postKey)
            .then { comments -> Promise<[User]> in
                let uids = comments.map({ $0.uid })
                return UserService.shared.fetchUsers(withUids: uids)
        }.done({ users in
            // how to init Comment object with users now?
        })
            .catch { error in
                print("DEBUG: Failed with error \(error)")
        }

Here is comment fetch function:

func fetchComments(withPostKey postKey: String) -> Promise<[Comment]> {
        return Promise { resolver in
            REF_COMMENTS.child(postKey).observeSingleEvent(of: .value) { snapshot in
                guard let dictionary = snapshot.value as? [String: AnyObject] else { return }
                let data = Array(dictionary.values)
                do {
                    let comments = try FirebaseDecoder().decode([Comment].self, from: data)
                    resolver.fulfill(comments)
                } catch let error {
                    resolver.reject(error)
                }
            }
        }
    }

Here is fetch users function

func fetchUsers(withUids uids: [String]) -> Promise<[User]> {
        var users = [User]()

        return Promise { resolver in
            uids.forEach { uid in
                self.fetchUser(withUid: uid).done { user in
                    users.append(user)
                    guard users.count == uids.count else { return }
                    resolver.fulfill(users)
                }.catch { error in
                    resolver.reject(error)
                }
            }
        }
    }

Here is comment object:

struct Comment: Decodable {
    let uid: String
    let commentText: String
    let creationDate: Date
    var user: User?
}

This is how simple it is with completion blocks, starting to think Promises aren't worth it?

func fetchComments(withPostKey postKey: String, completion: @escaping([Comment]) -> Void) {
        var comments = [Comment]()
        REF_COMMENTS.child(postKey).observe(.childAdded) { (snapshot) in

            guard let dictionary = snapshot.value as? [String: AnyObject] else { return }
            guard let uid = dictionary["uid"] as? String else { return }

            UserService.shared.fetchUser(withUid: uid, completion: { (user) in
                let comment = Comment(user: user, dictionary: dictionary)
                comments.append(comment)
                completion(comments)
            })
        }
    }
  • I prefer Rx over promise kit, but in any case conceptually the `then` block is like a `map` in that if you take in a `Promise` but return a `U` then he output is another `Promise`. `then` can also be like flatMap in that if you have a `Promise` but return a `Promise` then the output is a `Promise` and not a `Promise >`. In any case you want a to start with a user so return `Promise` or just a` User` then you want to get the comments so return `Promise` or just `Comments` from the next `then block` – Josh Homann Nov 05 '19 at 20:49
  • I need the `userIds` from the comment structure in order to know which users to fetch, so comments have to be fetched first – Stephan Dowless Nov 05 '19 at 21:17

1 Answers1

0

Ok I think I see what you are trying to do. The issue is that you need to capture the comments along with the users so you can return then together and later combine them. It should look something like this:

CommentsService.shared.fetchComments(withPostKey: postKey)
  .then { comments -> Promise<[Comment], [User]> in
    let uids = comments.map({ $0.uid })
    return UserService.shared.fetchUsers(withUids: uids)
      .then { users in
        return Promise<[Comment], [User]>(comments, users)
      }
    }.done({ combined in
      let (comments, users) = combined
      //Do combiney stuff here
    })
      .catch { error in
        print("DEBUG: Failed with error \(error)")
}

The transforms are [Comment] -> [User] -> ([Comment], [User]) -> [Comments with users attached]

Josh Homann
  • 15,933
  • 3
  • 30
  • 33