Swift async method call for a command line app - swift

I'm trying to reuse some async marked code that works great in a SwiftUI application in a simple Swift-Command line tool.
Lets assume for simplicity that I'd like to reuse a function
func fetchData(base : String) async throws -> SomeDate
{
let request = createURLRequest(forBase: base)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw FetchError.urlResponse
}
let returnData = try! JSONDecoder().decode(SomeData.self, from: data)
return returnData
}
in my command line application.
A call like
let allInfo = try clerk.fetchData("base")
in my "main-function" gives the error message 'async' call in a function that does not support concurrency.
What is the correct way to handle this case.
Thanks
Patrick

To call an async method the call must take place inside an async method or wrapped in a Task.
Further the method must be called wirh await
Task {
do {
let allInfo = try await clerk.fetchData("base")
} catch {
print(error)
}
}
You can make the entry point of the CLI async with this syntax
#main
struct CLI {
static func main() async throws {
let args = CommandLine.arguments
...
}
The name of the struct is arbitrary.

If you are using Argument Parser framework then from version 1.0 it supports async context. You have to make your struct conforms to protocol AsyncParsableCommand. It creates context in which async function can be run safely.

Related

How to ignore async let throws error when save response in tuple?

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)
}
}
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.
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.

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:))

I am building an app with Swift and SwiftUI. In MainViewModel I have a function who call Api for fetching JSON from url and deserialize it. this is made under async/await protocol.
the problem is the next, I have received from xcode the next comment : "Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates." in this part of de code :
func getCountries() async throws{
countries = try await MainViewModel.countriesApi.fetchCountries() ?? []
}
who calls this one:
func fetchCountries() async throws -> [Country]? {
guard let url = URL(string: CountryUrl.countriesJSON.rawValue ) else {
print("Invalid URL")
return nil
}
let urlRequest = URLRequest(url: url)
do {
let (json, _) = try await URLSession.shared.data(for: urlRequest)
if let decodedResponse = try? JSONDecoder().decode([Country].self, from: json) {
debugPrint("return decodeResponse")
return decodedResponse
}
} catch {
debugPrint("error data")
}
return nil
}
I would like to know if somebody knows how I can fix it
First fetch the data asynchronously and then assign the result to the property on the main thread
func getCountries() async throws{
let fetchedData = try await MainViewModel.countriesApi.fetchCountries()
await MainActor.run {
countries = fetchedData ?? []
}
}
Off topic perhaps but I would change fetchCountries() to return an empty array rather than nil on an error or even better to actually throw the errors since it is declared as throwing.
Something like
func fetchCountries() async throws -> [Country] {
guard let url = URL(string: CountryUrl.countriesJSON.rawValue ) else {
return [] // or throw custom error
}
let urlRequest = URLRequest(url: url)
let (json, _) = try await URLSession.shared.data(for: urlRequest)
return try JSONDecoder().decode([Country].self, from: json)
}
There are two ways to fix this. One, you can add the #MainActor attribute to your functions - this ensures they will run on the main thread. Docs: https://developer.apple.com/documentation/swift/mainactor. However, this could cause delays and freezing as the entire block will run on the main thread. You could also set the variables using DispatchQueue.main.async{} - see this article from Hacking With Swift. Examples here:
#MainActor func getCountries() async throws{
///Set above - this will prevent the error
///This can also cause a lag
countries = try await MainViewModel.countriesApi.fetchCountries() ?? []
}
Second option:
func getCountries() async throws{
DispatchQueue.main.async{
countries = try await MainViewModel.countriesApi.fetchCountries() ?? []
}
}

How to refactor Swift API call to use Swift 5.5 async/await?

I want to refactor some API calls to use Swift 5.5's new async/await in my SwiftUI project. However, it's unclear to me how to replace or accomodate the completions.
Here's an example function which I want to refactor:
static func getBooks(completion: #escaping ([Book]?) -> Void) {
let request = getRequest(suffix: "books")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
fatalError("Error: \(error)")
}
if let data = data {
if let books = try? JSONDecoder().decode([Book].self, from: data) {
DispatchQueue.main.async {
print("books.count: \(books.count)")
completion(books)
}
return
} else {
fatalError("Unable to decode JSON")
}
} else {
fatalError("Data is nil")
}
}.resume()
}
I beleve the new function signature would look something like this:
static func getBooks() async throws -> ([Book]?) {
// ...
}
However, I have no idea what to do with the URLSession.shared.dataTask, DispatchQueue.main.async and completion, etc.
Anyone know what the new function body should look like?
Thanks
func getBooks() async throws -> [Book] {
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode([Book].self, from: data)
}
This will throw if the request fails, and if the response cannot be decoded. Since the function is marked as throwing, then the calling function has to handle the raised errors.
You don't need to declare the returned [Book] to be optional, because it will either return an honest array, or throw an error.
In your additional code, you had to call your completion handler on the main queue, because you were calling it from within the completion block of the request. You don't need to do that here.

Swift Playground with async/await "cannot find 'async' in scope"

I'm trying to run this async function on Xcode Playground:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum NetworkingError: Error {
case invalidServerResponse
case invalidCharacterSet
}
func getJson() async throws -> String {
let url = URL(string:"https://jsonplaceholder.typicode.com/todos/1")!
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkingError.invalidServerResponse
}
guard let result = String(data: data, encoding: .utf8) else {
throw NetworkingError.invalidCharacterSet
}
return result
}
let result = try! await getJson()
print(result)
and I'm receiving this error message:
error: ForecastPlayground.playground:27:25: error: 'async' call in a function that does not support concurrency
let result = try! await getJson()
^
So I tried to create am async block in my func call:
async{
let result = try! await getJson()
print(result)
}
And then I received this error message:
Playground execution failed:
error: ForecastPlayground.playground:27:1: error: cannot find 'async' in scope
async{
^~~~~
I tried to annotated my function with the new #MainActor attribute and it doesn't work.
What I'm doing wrong?
Add an import to _Concurrency (underscore because this is supposed to be included by default) or a library that makes use of the library, such as UIKit or SwiftUI.
It appears that Swift Playgrounds don't have access to all concurrency features at the moment though, such as async let.
In summary
import _Concurrency
For Xcode 13, to use async and await in a Playground, just import Foundation and wrap the code in a detached task.
import Foundation
Task.detached {
func borat() async -> String {
await Task.sleep(1_000_000_000)
return "great success"
}
await print(borat())
}
And this is because there are only 3 places your code can call asynchronous functions: (1) in the body of an asynchronous function or property; (2) in the static main() method of a class, structure, or enumeration that’s marked with #main; (3) in a detached child task.

'async' call in a function that does not support concurrency

I'm trying to use async/await with Swift 5.5. I have my async function, but whenever I try to call it, I get this error:
'async' call in a function that does not support concurrency
Here's the code sample:
class TryThis {
func getSomethingLater(_ number: Double) async -> String {
// test - sleep for 3 seconds, then return
Thread.sleep(forTimeInterval: 3)
return String(format: ">>>%8.2f<<<", number)
}
}
let tryThis = TryThis()
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
What's the solution for this??
The answer here is that the "await getSomethingLater" must be called from an async context. Literally that means changing this:
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
into this:
Task {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
}
So the whole thing becomes:
class TryThis {
func getSomethingLater(_ number: Double) async -> String {
// test - sleep for 3 seconds, then return
Thread.sleep(forTimeInterval: 3)
return String(format: ">>>%8.2f<<<", number)
}
}
let tryThis = TryThis()
Task {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
}
Here's the output:
result: >>> 3.14<<<
There's great info in the Meet async/await in Swift video from WWDC21.
When calling async code (including actor fields) from non-async code, you have to wrap it in a Task:
Task {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
}
If you need it to escape that async context, you can use traditional callbacks:
func awaitedResult(callback: (String) -> Void) {
Task {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
callback(result)
}
}
awaitedResult { result in
print("result: \(result)")
}
Swift by Sundell also offers some alternatives involving Combine
Further reading: Concurrency — The Swift Programming Language (Swift 5.5)
The original error is produced because "top-level" await is not yet supported in Swift (that is, code at the global scope).
The error just means you need to provide the async function with an asynchronous context, like using Task, Task.detached or a TaskGroup, depending on the behaviour you want.
Task.detached {
let result = await tryThis.getSomethingLater(3.141592653589793238462)
print("result: \(result)")
}
However, your code snippet should work in the future when top-level await is eventually proposed, implemented and supported.
Top-level await support was mentioned as part of the original proposal for async/await SE-0296.
In my case I just refactored the:
async { some-code-which-can-take-some-time }
with:
Task { some-code-which-can-take-some-time }
This error occurs when you’ve tried to call an async function from a synchronous function, which is not allowed in Swift – asynchronous functions must be able to suspend themselves and their callers, and synchronous functions simply don’t know how to do that.
func doAsyncWork() async {
print("Doing async work")
}
func doRegularWork() {
Task {
await doAsyncWork()
}
}
doRegularWork()
Reference here