How can I prevent nested completion blocks in Swift 3?
Asked Answered
G

2

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)
            })
        })
    })
} 
Glacis answered 1/6, 2017 at 7:42 Comment(5)
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 seperatelySecularity
You can also check PromiseKitHysterotomy
See this answer https://mcmap.net/q/1167077/-alamofire-nested-requestsBoo
Thank you all for the help and the resource!Glacis
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.Latex
M
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.

Mud answered 3/8, 2017 at 16:13 Comment(0)
C
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
    }
}
Chasitychasm answered 1/6, 2017 at 15:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.