skip to Main Content

i have a code like this:

Task {
    async let configsReq = self.interactor.getConfigs()
    async let optionsReq = self.interactor.getOptions()
    async let updateStateReq = self.interactor.getAppUpdateState()
    async let contactsReq = self.interactor.getContactOptions()

    var config: Config?
    var options: AvailableOptions?
    var updateState: UpdateType?
    var contacts: ContactOptions?

    do {
        config = try await configsReq
    } catch {
        config = nil
    }

    do {
        options = try await optionsReq
    } catch {
        options = nil
    }

    do {
        updateState = try await updateStateReq
    } catch {
        updateState = nil
    }

    do {
        contacts = try await contactsReq
    } catch {
        contacts = nil
    }

    self.goToNextPage()
}

in this case it does not matter for me that the requests get correct response or throws error. i don’t want to block user to get correct response.
And also I want to make sure that all my requests are answered (correct or error response) to take the user to the next page

how can i write these codes cleaner and better with new swift concurrency?
i tried like this (but i could not get match error to each related request):

Task {
    async let configs = self.interactor.getConfigs()
    async let options = self.interactor.getOptions()
    async let updateState = self.interactor.getAppUpdateState()
    async let contacts = self.interactor.getContactOptions()

    do {
        let array = try await [configs, options, updateState, contacts]
    } catch {
        print(error)
    }
}

2

Answers


  1. I would make a little helper that helps wrap the error into a Result:

    extension Result {
        init(asyncCatching block: () async throws -> Success) async where Failure == Error {
            do {
                self = .success(try await block())
            } catch {
                self = .failure(error)
            }
        }
    }
    

    In case of errors, you even get the Error object for each getXXX method, rather than just a nil. Of course, if you really just want a nil, you can write a helper that returns optionals instead.

    // this is essentially like refactoring out the repeated parts of your first code
    func asyncCatchWithNil<Result>(function: () async throws -> Result) async -> Result? {
        do {
            return try await function()
        } catch {
            return nil
        }
    }
    

    Then you could do:

    Task {
        async let configs = Result(asyncCatching: self.interactor.getConfigs)
        async let options = Result(asyncCatching: self.interactor.getOptions)
        async let updateState = Result(asyncCatching: self.interactor.getAppUpdateState)
        async let contacts = Result(asyncCatching: self.interactor.getContactOptions)
        /* or
        async let configs = asyncCatchWithNil(function: self.interactor.getConfigs)
        async let options = asyncCatchWithNil(function: self.interactor.getOptions)
        async let updateState = asyncCatchWithNil(function: self.interactor.getAppUpdateState)
        async let contacts = asyncCatchWithNil(function: self.interactor.getContactOptions)
        */
    
        let (configsResult, optionsResult, updateStateResult, contactsResult) 
            = await (configs, options, updateState, contacts)
    
        // you can inspect each result here if you'd like
        
        self.goToNextPage()
    }
    

    The idea here is that you get a type that can contain both the response and error at the point of async let, rather than catching the error later.

    Login or Signup to reply.
  2. If I understand the question correctly, you want to:

    • “match error to each related request”, but that
    • you want to proceed regardless of success or failure, as it “does not matter for me that the requests get correct response or throws error”.

    If that is the pattern you are looking for, I might suggest using Task result:

    async let configsReq  = Task { try await interactor.getConfigs() }
    async let optionsReq  = Task { try await interactor.getOptions() }
    async let stateReq    = Task { try await interactor.getAppUpdateState() }
    async let contactsReq = Task { try await interactor.getContactOptions() }
    
    let config   = await configsReq.result
    let options  = await optionsReq.result
    let state    = await stateReq.result
    let contacts = await contactsReq.result
    
    goToNextPage(config: config, options: options, state: state, contacts: contacts)
    

    Or, more concisely:

    async let configs  = Task { try await interactor.getConfigs() }
    async let options  = Task { try await interactor.getOptions() }
    async let state    = Task { try await interactor.getAppUpdateState() }
    async let contacts = Task { try await interactor.getContactOptions() }
    
    await goToNextPage(config: configs.result, options: options.result, state: state.result, contacts: contacts.result)
    

    Where goToNextPage might be defined as:

    func goToNextPage(
        config:   Result<Config, Error>,
        options:  Result<AvailableOptions, Error>,
        state:    Result<UpdateType, Error>,
        contacts: Result<ContactOptions, Error>
    ) { … }
    

    That way, goToNextPage can look at the .success or .failure for each, to retrieve either the value or error associated with each of the four requests.

    Needless to say, you also could have four properties for these four requests, and then goToNextPage could refer to those, rather than taking them as parameters to the method. It’s functionally the same thing, but you have to decide either local vars that are passed to the next method or update properties that are accessed by the next method.


    You asked:

    … if we don’t want to use Result anymore, how can do that?

    Yes, we do not use Result very much, anymore, as that was historically a pattern for returning either value or error in traditional asynchronous patterns, and nowadays we try a series of tasks, catch thrown errors, but generally early exit once one of them fails.

    But if you really want to capture the success and failure for each of the four concurrent requests, then Result encapsulates that quite well.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search