vincentf
vincentf

Reputation: 1499

How do I handle async functions in a loop?

I'm looping through a table's rows, for each of them I'm doing a couple of async calls like fetching data from API, copying files, running shell script... How do I wait for the result until going to the next one.

Also I'm new to Swift, not sure if this is the best way to handle a group of async tasks. Should I use concurrency in this case ?

tableView.selectedRowIndexes.forEach { row in
     myData.fetch(url: urlList[row]) { res in
        self.anotherAsyncCall(res) { data in
            //continue to deal with next row now
        }
    }
}

Upvotes: 1

Views: 153

Answers (2)

Rob
Rob

Reputation: 438437

If you really want to do this sequentially, the easiest way is to perform your tasks recursively, actually invoking the next task in the completion handler of the prior one:

processNext(in: tableView.selectedRowIndexes) {
    // do something when they're all done
}

Where:

func processNext(in rows: [Int], completion: @escaping () -> Void) {
    guard let row = rows.first else {
        completion()
        return
    }

    myData.fetch(url: urlList[row]) { res in
        self.anotherAsyncCall(res) { data in
            //continue to deal with next row now

            self.processNext(in: Array(rows.dropFirst()), completion: completion)
        }
    }
}

But I agree with GoodSp33d that the other approach is to wrap this asynchronous process in a custom, asynchronous, Operation subclass.


But this begs the question why you want to do these sequentially. You will pay a significant performance penalty because of the inherent network latency for each request. So the alternative is to let them run concurrently, and use dispatch group to know when they're done:

let group = DispatchGroup()

tableView.selectedRowIndexes.forEach { row in
    group.enter()
    myData.fetch(url: urlList[row]) { res in
        self.anotherAsyncCall(res) { data in
            //continue to deal with next row now
            group.leave()
        }
    }
}

group.notify(queue: .main) {
    // do something when they're all done
}

Whether you can run these concurrently (or to what degree) is a function of what you're doing inside various asynchronous methods. But I would suggest you think hard about making this work concurrently, as the performance is likely to be much better.

Upvotes: 1

luochen1990
luochen1990

Reputation: 3867

If you are using some promise library, just use the all function.

Here is some Document about promise.all()

And PromiseKit use when instead, you can read about the faq and the tutorial about when for more information.

If you want to do that without any promise library, here is the pseudocode:

var results = []
rows.forEach {row in
    fetch(row) {res in
        results.push(res)
        if(results.length == rows.length) {
            // do something using the results here
        }
    }
}

Upvotes: 0

Related Questions