Swift Async let with loop - swift

I want to get data in parallel. I found an example to call API in parallel but I want to store async let variables with loop.
Async let example. However, this example doesn't use a loop.
async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])
let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)
I want to do something like the following.
let items = photoNames.map({ photo in
async let item = downloadPhoto(named: photo)
return item
})
let photos = await items
show(photos)

You can use a task group. See Tasks and Task Groups section of the The Swift Programming Language: Concurrency (which would appear to be where you got your example).
One can use withTaskGroup(of:returning:body:) to create a task group to run tasks in parallel, but then collate all the results together at the end.
E.g. here is an example that creates child tasks that return a tuple of “name” and ”image”, and the group returns a combined dictionary of those name strings with their associated image values:
func downloadImages(names: [String]) async -> [String: UIImage] {
await withTaskGroup(
of: (String, UIImage).self,
returning: [String: UIImage].self
) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}
var images: [String: UIImage] = [:]
for await result in group {
images[result.0] = result.1
}
return images
}
}
Or, more concisely:
func downloadImages(names: [String]) async -> [String: UIImage] {
await withTaskGroup(of: (String, UIImage).self) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}
return await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
}
}
They run in parallel:
But you can extract them from the dictionary of results:
let stooges = ["moe", "larry", "curly"]
let images = await downloadImages(names: stooges)
imageView1.image = images["moe"]
imageView2.image = images["larry"]
imageView3.image = images["curly"]
Or if you want an array sorted in the original order, just build an array from the dictionary:
func downloadImages(names: [String]) async -> [UIImage] {
await withTaskGroup(of: (String, UIImage).self) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}
let dictionary = await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
return names.compactMap { dictionary[$0] }
}
}

Rob's answer is good. You can use an Array instead of a Dictionary too, to preserve order.
let photos = await photoNames.map(downloadPhoto)
public extension Sequence where Element: Sendable {
func mapWithTaskGroup<Transformed: Sendable>(
priority: TaskPriority? = nil,
_ transform: #escaping #Sendable (Element) async throws -> Transformed
) async rethrows -> [Transformed] {
try await withThrowingTaskGroup(
of: EnumeratedSequence<[Transformed]>.Element.self
) { group in
for (offset, element) in enumerated() {
group.addTask(priority: priority) {
(offset, try await transform(element))
}
}
return try await group.reduce(
into: map { _ in nil } as [Transformed?]
) {
$0[$1.offset] = $1.element
} as! [Transformed]
}
}
}

If the order of result doesn't matter here, use a TaskGroup instead.

Related

Is there any way to implement a taskGroup to run in parrarel inside another taskGroup?

I am using Firebase to fetch some data from it with a Continuation. Before resuming the continuation I want to run a group of async tasks inside(fetch other data). This would let me achieve maximum concurrency.
I have tried two methods to do this, both does not work.
This is what I have tried first trying to use inside a continuation a taskGroup.(Code Below). The error is in comment.
First Option
try? await withThrowingTaskGroup(of: Void.self) { group in
for referencePosts in referencePostsDict.keys {
group.addTask {
return try! await self.fetchPost(reference: referencePosts)
}
}
func fetchPost(reference: String) async throws -> Void{
var db_ref = Database.database(url:FIREBASEURL.url)
.reference()
.child("posts")
.child(reference)
typealias postContinuation = CheckedContinuation<Void,Error>
return try await withCheckedThrowingContinuation{
(continuation : postContinuation) in
db_ref.observeSingleEvent(of: .value) { data in
if data.exists(){
var three = data.value as! [String : Any]
withThrowingTaskGroup(of: Void.self) { group in //async' call in a function that does not support concurrency
(three["picturePaths"] as! [String : String]).forEach { key, value in
if key != "O"{
group.addTask {
try? await self.fetchPictureDataL(picRef: key)
}
self.threePictures[key] = value
}
}
}
self.threePosts[reference] = three
continuation.resume(returning: ())
}else{
continuation.resume(returning: ())
}
}
}
}
func fetchPictureDataL(picRef : String) async throws ->Void{
var db =Database
.database(url:FIREBASEURL.url)
.reference()
.child("pictures").child(picRef)
typealias postContinuation = CheckedContinuation<Void,Error>
return try await withCheckedThrowingContinuation{
(continuation : postContinuation) in
db.observeSingleEvent(of: .value) { data in
self.threePicturesFetched[picRef] = three as! [String : Any]
continuation.resume(returning: ())
}
}
}
I commented the code where the compiler reports a problem.
Second Option
fetchCheckIn2 method is modified to return a [String]
This is the second method where I have tried to achieve the same result different, to mention the fetchCheckIn2 method is modified to return a [String].
What I want to achieve is that the fetchPictureDataL will run in parallel inside a Task or group, and will not run awaiting each other to finish.
//fetching all references
try! await withThrowingTaskGroup(of: [String].self) { firstGroup in
for referenceCheckIn in reference_checkInInt_dictionary.keys {
firstGroup.addTask {
return try await self.fetchCheckIn2(reference: referenceCheckIn)
}
}
for try await pictureArray in firstGroup {
if pictureArray.count != 0 {
for pic in pictureArray {
try! await self.fetchPictureDataL(picRef: pic)
}
}
}
}
Trying to achieve
I want to achieve parallelism even with fetchPictureDataL method?
If not clear why I am trying to do this, please read the use of case.
Use of this case:
I have social media app with posts. I have a list of paths in order to fetch the post. Each post contains an array of other paths which are pictures.
I want to fetch posts parrarel and also fetch the pictures in parrarel, in a taskGruop, so all this fetch of post+picture is awaited and can be displayed to User.
Thank you

async let does not compile because of the "Reference to captured var 'imageDescriptors' in concurrently-executing code"

I am trying to use withThrowingTaskGroup to get some data from the web. I found that my code runs a bit slow, so I tried to use actors with async/await. I use Descriptors to get the
func getAllStocksList() async throws -> [SingleStockViewModel] {
var stockViewModels = [SingleStockViewModel]()
let urlString = URLBuilder.getAllStocks.makeString()
let (data, response) = try await URLSession.shared.data(from: URL(string: urlString)!)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
throw "Invalid HttpResponseCode"
}
let dataResponse = try JSONDecoder().decode([StockDetails].self, from: data)
let stocksDetailsList = dataResponse[..<25]
let stockSymbolsList = stocksDetailsList.map {
$0.title
}
let dataResponseDict = dataResponse.toDictionary {
$0.title
}
var stockPrices: [String: Double] = [:]
let imageUrlStringsDict = try await imageService.makeStockImageUrlStringsList(for: stockSymbolsList)
let nonEmptyImageUrlStringsList = imageUrlStringsDict.filter {
!$0.value.isEmpty
}
var imageDescriptors = [Descriptor]()
var chartDescriptors = [Descriptor]()
for item in nonEmptyImageUrlStringsList {
imageDescriptors.append(
Descriptor(
stockSymbol: item.key,
stockImageUrlString: nonEmptyImageUrlStringsList[item.key]!,
type: .image)
)
chartDescriptors.append(
Descriptor(stockSymbol: item.key,
stockImageUrlString: "",
type: .marketData)
)
let price = try await fetchStockPrice(for: item.key)
stockPrices[item.key] = price
}
print(chartDescriptors)
async let taskResults = try fetchGroupedStocksInfo(descriptors: imageDescriptors)
async let marketDataTaskResult = try fetchGroupedStocksInfo(descriptors: chartDescriptors)
let (taskResultsDict, marketDataTaskResultDict) = try await (taskResults, marketDataTaskResult)
}
and the stock descriptor is passed to
func fetchGroupedStocksInfo(descriptors: [Descriptor]) async throws -> [String: TaskResult] {
try await withThrowingTaskGroup(of: (String, TaskResult).self, returning: [String: TaskResult].self) { group in
for descriptor in descriptors {
group.addTask { [self] in
switch descriptor.type {
case .image:
let (_, image) = try await self.imageService.makeStockImageTuple(descriptor.stockImageUrlString)
let (symbol, marketResponse) = try await merketInfoSerice.fetchMarketInfo(descriptor.stockSymbol, numberOfDays: 3)
print(image)
return (descriptor.stockSymbol, TaskResult.image(image))
case .marketData:
let (symbol, marketResponse) = try await merketInfoSerice.fetchMarketInfo(descriptor.stockSymbol, numberOfDays: 3)
print(marketResponse.close)
return (symbol, TaskResult.marketData(marketResponse))
}
}
}
return try await group.reduce(into: [:]) {
$0[$1.0] = $1.1
}
}
}
and the errors found in compile time are
(63, 73) Reference to captured var 'imageDescriptors' in concurrently-executing code
(64, 82) Reference to captured var 'chartDescriptors' in concurrently-executing code
How can I make these error disappear, though I sense that my code can cause some problems at runtime.
You received this error:
Reference to captured var 'imageDescriptors' in concurrently-executing code
The issue is that you are supplying a mutable array to async let, an expression that will be await’ed later. As SE-0317 async let bindings says:
The right-hand side of a async let expression can be thought of as an implicit #Sendable closure ... the closure is #Sendable and nonisolated, meaning that it cannot access non-sendable state of the enclosing context.
Bottom line, the problem goes away if you replace it with an immutable array.
You can initialize them to constants directly:
let imageDescriptors = items.map { item in
Descriptor(
stockSymbol: item.key,
stockImageUrlString: nonEmptyImageUrlStringsList[item.key]!,
type: .image
)
}
let chartDescriptors = items.map { item in
Descriptor(
stockSymbol: item.key,
stockImageUrlString: "",
type: .marketData
)
}
You can make your own copy:
let descriptors = imageDescriptors
async let taskResults = try fetchGroupedStocksInfo(descriptors: descriptors)
Instead of async let, you can make your own Task with an explicit capture list:
let subtask1 = Task { [imageDescriptors] in
await self.fetchGroupedStocksInfo(descriptors: imageDescriptors)
}
let subtask2 = Task { [chartDescriptors] in
await try fetchGroupedStocksInfo(descriptors: chartDescriptors)
}
let (taskResultsDict, marketDataTaskResultDict) = try await (subtask1.value, subtask2.value)
NB: This opts out of structured concurrency.

Swift's "async let" error thrown depends on the order the tasks are made

I'm trying to understand async let error handling and it's not making a lot of sense in my head. It seems that if I have two parallel requests, the first one throwing an exception doesn't cancel the other request. In fact it just depends on the order in which they are made.
My testing setup:
struct Person {}
struct Animal {}
enum ApiError: Error { case person, animal }
class Requester {
init() {}
func getPeople(waitingFor waitTime: UInt64, throwError: Bool) async throws -> [Person] {
try await waitFor(waitTime)
if throwError { throw ApiError.person }
return []
}
func getAnimals(waitingFor waitTime: UInt64, throwError: Bool) async throws -> [Animal] {
try await waitFor(waitTime)
if throwError { throw ApiError.animal }
return []
}
func waitFor(_ seconds: UInt64) async throws {
do {
try await Task.sleep(nanoseconds: NSEC_PER_SEC * seconds)
} catch {
print("Error waiting", error)
throw error
}
}
}
The exercise.
class ViewController: UIViewController {
let requester = Requester()
override func viewDidLoad() {
super.viewDidLoad()
Task {
async let animals = self.requester.getAnimals(waitingFor: 1, throwError: true)
async let people = self.requester.getPeople(waitingFor: 2, throwError: true)
let start = Date()
do {
// let (_, _) = try await (people, animals)
let (_, _) = try await (animals, people)
print("No error")
} catch {
print("error: ", error)
}
print(Date().timeIntervalSince(start))
}
}
}
For simplicity, from now on I'll just past the relevant lines of code and output.
Scenario 1:
async let animals = self.requester.getAnimals(waitingFor: 1, throwError: true)
async let people = self.requester.getPeople(waitingFor: 2, throwError: true)
let (_, _) = try await (animals, people)
Results in:
error: animal
1.103397011756897
Error waiting CancellationError()
This one works as expected. The slower request, takes 2 seconds, but was cancelled after 1 second (when the fastest one throws)
Scenario 2:
async let animals = self.requester.getAnimals(waitingFor: 2, throwError: true)
async let people = self.requester.getPeople(waitingFor: 1, throwError: true)
let (_, _) = try await (animals, people)
Results in:
error: animal
2.2001450061798096
Now this one is not expected for me. The people request takes 1 second to throw an error and we still wait 2 seconds and the error is animal.
My expectation is that this should have been 1 second and people error.
Scenario 3:
async let animals = self.requester.getAnimals(waitingFor: 2, throwError: true)
async let people = self.requester.getPeople(waitingFor: 1, throwError: true)
let (_, _) = try await (people, animals)
Results in:
error: person
1.0017549991607666
Error waiting CancellationError()
Now this is expected. The difference here is that I swapped the order of the requests but changing to try await (people, animals).
It doesn't matter which method throws first, we always get the first error, and the time spent also depends on that order.
Is this behaviour expected/normal? Am I seeing anything wrong, or am I testing this wrong?
I'm surprised this isn't something people are not talking about more. I only found another question like this in developer forums.
Please help. :)
From https://github.com/apple/swift-evolution/blob/main/proposals/0317-async-let.md
async let (l, r) = {
return await (left(), right())
// ->
// return (await left(), await right())
}
meaning that the entire initializer of the async let is a single task,
and if multiple asynchronous function calls are made inside it, they
are performed one-by one.
Here is a more structured approach with behavior that makes sense.
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.padding()
.task {
let requester = Requester()
let start = Date()
await withThrowingTaskGroup(of: Void.self) { group in
let animalTask = Task {
try await requester.getAnimals(waitingFor: 1, throwError: true)
}
group.addTask { animalTask }
group.addTask {
try await requester.getPeople(waitingFor: 2, throwError: true)
}
do {
for try await _ in group {
}
group.cancelAll()
} catch ApiError.animal {
group.cancelAll()
print("animal threw")
} catch ApiError.person {
group.cancelAll()
print("person threw")
} catch {
print("someone else")
}
}
print(Date().timeIntervalSince(start))
}
}
}
The idea is to add each task to a throwing group and then loop through each task.
Cora hit the nail on the head (+1). The async let of a tuple will just await them in order. Instead, consider a task group.
But you do not need to cancel the other items in the group. See “Task Group Cancellation” discussion in the withThrowingTaskGroup(of:returning:body:) documentation:
Throwing an error in one of the tasks of a task group doesn’t immediately cancel the other tasks in that group. However, if you call
next() in the task group and propagate its error, all other tasks are
canceled. For example, in the code below, nothing is canceled and the
group doesn’t throw an error:
withThrowingTaskGroup { group in
group.addTask { throw SomeError() }
}
In contrast, this example throws SomeError and cancels all of the tasks in the group:
withThrowingTaskGroup { group in
group.addTask { throw SomeError() }
try group.next()
}
An individual task throws its error in the corresponding call to Group.next(), which gives you a chance to handle the individual error or to let the group rethrow the error.
Or you can waitForAll, which will cancel the other tasks:
let start = Date()
do {
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { let _ = try await self.requester.getAnimals(waitingFor: 1, throwError: true) }
group.addTask { let _ = try await self.requester.getPeople(waitingFor: 2, throwError: true) }
try await group.waitForAll()
}
} catch {
print("error: ", error)
}
print(Date().timeIntervalSince(start))
Bottom line, task groups do not dictate the order in which the tasks are awaited. (They also do not dictate the order in which they complete, either, so you often have to collating task group results into an order-independent structure or re-order the results.)
You asked how you would go about collecting the results. There are a few options:
You can define group tasks such that they do not “return” anything (i.e. child of Void.self), but update an actor (Creatures, below) in the addTask calls and then extract your tuple from that:
class ViewModel1 {
let requester = Requester()
func fetch() async throws -> ([Animal], [Person]) {
let results = Creatures()
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask { try await results.update(with: self.requester.getAnimals(waitingFor: animalsDuration, throwError: shouldThrowError)) }
group.addTask { try await results.update(with: self.requester.getPeople(waitingFor: peopleDuration, throwError: shouldThrowError)) }
try await group.waitForAll()
}
return await (results.animals, results.people)
}
}
private extension ViewModel1 {
/// Creatures
///
/// A private actor used for gathering results
actor Creatures {
var animals: [Animal] = []
var people: [Person] = []
func update(with animals: [Animal]) {
self.animals = animals
}
func update(with people: [Person]) {
self.people = people
}
}
}
You can define group tasks that return enumeration case with associated value, and then extracts the results when done:
class ViewModel2 {
let requester = Requester()
func fetch() async throws -> ([Animal], [Person]) {
try await withThrowingTaskGroup(of: Creatures.self) { group in
group.addTask { try await .animals(self.requester.getAnimals(waitingFor: animalsDuration, throwError: shouldThrowError)) }
group.addTask { try await .people(self.requester.getPeople(waitingFor: peopleDuration, throwError: shouldThrowError)) }
return try await group.reduce(into: ([], [])) { previousResult, creatures in
switch creatures {
case .animals(let values): previousResult.0 = values
case .people(let values): previousResult.1 = values
}
}
}
}
}
private extension ViewModel2 {
/// Creatures
///
/// A private enumeration with associated types for the types of results
enum Creatures {
case animals([Animal])
case people([Person])
}
}
For the sake of completeness, you don't have to use task group if you do not want. E.g., you can manually cancel earlier task if prior one canceled.
class ViewModel3 {
let requester = Requester()
func fetch() async throws -> ([Animal], [Person]) {
let animalsTask = Task {
try await self.requester.getAnimals(waitingFor: animalsDuration, throwError: shouldThrowError)
}
let peopleTask = Task {
do {
return try await self.requester.getPeople(waitingFor: peopleDuration, throwError: shouldThrowError)
} catch {
animalsTask.cancel()
throw error
}
}
return try await (animalsTask.value, peopleTask.value)
}
}
This is not a terribly scalable pattern, which is why task groups might be a more attractive option, as they handle the cancelation of pending tasks for you (assuming you iterate through the group as you build the results).
FWIW, there are other task group alternatives, too, but there is not enough in your question to get too specific in this regard. For example, I can imagine some protocol-as-type implementations if all of the tasks returned an array of objects that conformed to a Creature protocol.
But hopefully the above illustrate a few patterns for using task groups to enjoy the cancelation capabilities while still collating the results.

How to wait for a bunch of async calls to finish to return a result?

I understand the basic usage of async/await but I'm a bit confused of what I should do in this specific example. I have an async function called save(url: URL) which mimics a function that would take a local URL as its parameter, and asynchronously return a String which would be, say the new remote URL of this file:
struct FileSaver {
// In this example I'll simulate a network request
// with a random async time and return the original file URL
static func save(_ url: URL) async throws -> String {
try await Task.sleep(
seconds: Double(arc4random_uniform(10)) / 10
)
return url.absoluteString
}
}
extension Task where Success == Never, Failure == Never {
public static func sleep(
seconds: Double
) async throws {
return try await sleep(
nanoseconds: UInt64(seconds) * 1_000_000_000
)
}
}
Now say I have 4 local files, and I want to save these files in parallel, but only return when they are all done saving. I read the documentation but still I'm a bit confused if I should use an array or a TaskGroup.
I would like to do something like this:
// in FileSaver
static func save(
files: [URL]
) async throws -> [String] {
// how to call save(url) for each file in `files`
// in parallel and only return when every file is saved?
}
Thank you for your help
We use task group to perform the requests in parallel and then await the whole group.
The trick, though, is that they will not finish in the same order that we started them. So, if you want to preserve the order of the results, we can return every result as a tuple of the input (the URL) and the output (the string). We then collect the group result into a dictionary, and the map the results back to the original order:
static func save(files: [URL]) async throws -> [String] {
try await withThrowingTaskGroup(of: (URL, String).self) { group in
for file in files {
group.addTask { (file, try await save(file)) }
}
let dictionary = try await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
return files.compactMap { dictionary[$0] }
}
}
There are other techniques to preserve the order of the results, but hopefully this illustrates the basic idea.
I think withThrowingTaskGroup is what you are looking for:
static func save(
files: [URL]
) async throws -> [String] {
try await withThrowingTaskGroup(of: String.self) { group in
for file in files {
group.addTask { try await save(file) }
}
var strings = [String]()
for try await string in group {
strings.append(string)
}
return strings
}
}

Cannot convert return expression of type 'Task<[String], Error>' to return type '[String]' when using Task.init{}

I'm learning how to iterate with async and await in swift.
My current stage is on:
import Foundation
import SwiftUI
import Darwin
enum MyError: Error {
case genError
}
let myString : String = """
https://httpbin.org/anything
https://httpbin.org/ip
https://httpbin.org/user-agent
https://httpbin.org/headers
https://httpbin.org/get
https://httpbin.org/post
https://httpbin.org/put
https://httpbin.org/delete
https://httpbin.org/gzip
https://httpbin.org/status/:code
https://httpbin.org/response-headers?key=val
https://httpbin.org/redirect/:n
https://httpbin.org/relative-redirect/:n
https://httpbin.org/cookies
https://httpbin.org/cookies/set/:name/:value
https://httpbin.org/basic-auth/:user/:passwd
https://httpbin.org/hidden-basic-auth/:user/:passwd
https://httpbin.org/digest-auth/:qop/:user/:passwd
https://httpbin.org/stream/:n
https://httpbin.org/delay/:n
"""
func fetchInfo(for url: String, with index:Int) async throws -> String {
let request = URLRequest(url: URL(string: url)!)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
print("error found\n" + String(index) + "\n" + (url))
throw MyError.genError }
let thisOutput = String(data: data, encoding: .utf8)!
return thisOutput
}
func fetchOnebyOne(urls: String) async throws -> [String] {
var count : Int = 0
var finalArray : [String] = []
for item in myString.components(separatedBy: "\n") {
count += 1
do {
let thisThis: String = try await fetchInfo(for: item, with: count)
finalArray.append(thisThis)
}
catch {
print("\(count) ---------------------------- error found\n\n\n\n")
}
} // : for
return finalArray
}
Task{
let finalOutput = try await fetchOnebyOne(urls: myString)
print(finalOutput)
}
For the fetchOneByOne(), on a webpage, I know I can use async to get the same results, so I rewrite this function:
func fetchOnebyOne(urls: String) {
async {
var count : Int = 0
var finalArray : [String] = []
for item in myString.components(separatedBy: "\n") {
count += 1
do {
let thisThis: String = try await fetchInfo(for: item, with: count)
finalArray.append(thisThis)
}
catch {
print("\(count) ---------------------------- error found\n\n\n\n")
}
} // : for
} //: async
}
fetchOnebyOne(urls: myString)
I'm successful to get the same output. But I got a yellow warning in Xcode, the async should be replaced with Task.init. So I change the async to Task.init. The output still same.
But actually you can see, the fetchOnebyOne() doesn't return a [String] anymore. Because I cannot solve the warnings if I make it return [String]. I tried the below code:
func fetchOnebyOne(urls: String) -> [String] {
Task.init {
var count : Int = 0
var finalArray : [String] = []
for item in myString.components(separatedBy: "\n") {
count += 1
do {
let thisThis: String = try await fetchInfo(for: item, with: count)
finalArray.append(thisThis)
}
catch {
print("\(count) ---------------------------- error found\n\n\n\n")
}
} // : for
return finalArray
} // : Task
}
the warning is:
No 'init' candidates produce the expected contextual result type
'[String]'
and I did some research and change the first line to
func fetchOnebyOne(urls: String) -> [String] {
Task.init {() async throws -> [String] in
var count : Int = 0
var finalArray : [String] = []
for item in myString.components(separatedBy: "\n") {
count += 1
do {
let thisThis: String = try await fetchInfo(for: item, with: count)
finalArray.append(thisThis)
}
catch {
print("\(count) ---------------------------- error found\n\n\n\n")
}
} // : for
return finalArray
} // : Task
}
I got warning:
Cannot convert return expression of type 'Task<[String], Error>' to
return type '[String]'
I stuck here and cannot find useful information about Task.init, especially about the error - 'Task<[String], Error>' on internet.
I did all of this for knowledge, for learning swift. No practical use. Hope people here could help. Thanks.