10

I have a series of nested completion blocks in the code provided below. This is because I need to make separate network requests in the background to abstract data to be used in the next method, which provides another completion block, and so on. Is there any way around this? Any tip is much appreciated!

func fetchNearbyUsers(forCurrentUser user: User, completionHandler: usersCompletionHandler?) {

    self.fetchAllUsers(completionHandler: { (users: [User]) in

        ChatProvider.sharedInstance.fetchAllChatrooms(completionHandler: { (chatrooms: [Chatroom]) in

            self.validateNewUsers(currentUser: user, users: users, chatrooms: chatrooms, completionHandler: { (validUsers: [User]) in

                guard validUsers.isEmpty == false else {
                    completionHandler?([])
                    return
                }
                completionHandler?(validUsers)
            })
        })
    })
} 
  • 1
    it doesn't change the logic of what you're doing - but it can be made more readable by just making a function call to the next step from the completion handler, and having the code seperately – Russell Jun 01 '17 at 07:48
  • 1
    You can also check PromiseKit – Shabir jan Jun 01 '17 at 07:48
  • 2
    See this answer https://stackoverflow.com/a/44092277/1825618 – Bilal Jun 01 '17 at 07:53
  • Thank you all for the help and the resource! –  Jun 01 '17 at 07:57
  • You can also use DispatchQueues to run your network requests sequentially. However, I would also recommend using PromiseKit, once you get the hang of it, it's much more powerful and convenient than any built in way to handle async requests. – Dávid Pásztor Jun 01 '17 at 08:43

2 Answers2

7

One option here would be to use higher order factory functions (i.e., functions that return other functions) to break the blocks out into their own functions...

func fetchNearbyUsers(forCurrentUser user: User, completionHandler: @escaping usersCompletionHandler = { _ in }) {      
    self.fetchAllUsers(completionHandler: self.allUsersFromChatrooms(user: user, completionHandler: completionHandler))
}

func allUsersFromChatrooms(user: User, completionHandler: @escaping usersCompletionHandler) -> ([User]) -> Void {
    return { users in
        ChatProvider.sharedInstance.fetchAllChatrooms(completionHandler: self.validatedUsersInChatrooms(user: user, users: users, completionHandler: completionHandler))
    }
}

func validatedUsersInChatrooms(user: User, users: [User], completionHandler: @escaping usersCompletionHandler) -> ([Chatroom]) -> Void {
    return { chatrooms in
        self.validateNewUsers(currentUser: user, users: users, chatrooms: chatrooms, completionHandler: completionHandler)
    }
}

In the code above validatedUsersInChatrooms will return a function that accepts an array of Chatrooms and calls the provided completion handler with the validated users. The function allUsersFromChatrooms returns a function that accepts an array of users then fetches the chatrooms and calls the provided completion handler with an array of validated users from the chatrooms.

Also note that I changed your fetchNearbyUsers function to accept a completion block and default to a block that does nothing instead of using an optional block. It feels much cleaner to me.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
0

Why not call a function and use some bools to make sure both are complete. Here's some pseudocode

var completeA = false
var completeB = false

func doStuff {

    asyncStuffA(stuff {

        asyncStuffB(stuff {

            }, completion: {

                completeB = true
                completionHandler()

        })

        }, completion: {

            completeA = true
            completionHandler()

    })
}


func completionHandler() {
    if completeA && completeB {
        // Both are complete
        completeA = false
        completeB = false
    }
}
Anters Bear
  • 1,816
  • 1
  • 15
  • 41