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

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.

Related

Creating CRUD Functions Using Async-Await with Vapor-Fluent - Swift 5.6

I'm trying to understand how to create CRUD functions with Vapor. It appears first() is optional, but cannot be unwrapped as an optional type
func first() -> EventLoopFuture<Token?>
Here's my Token.swift
import Foundation
import Fluent
import Vapor
final class Token: Model {
// 1
static let schema = "tokens"
// 2
#ID(key: .id)
var id: UUID?
// 3
#Field(key: "token")
var token: String
#Field(key: "debug")
var debug: Bool
// 4
init() { }
init(token: String, debug: Bool) {
self.token = token
self.debug = debug
}
}
Here's my TokenController.swift
import Foundation
import Fluent
import Vapor
struct TokenController {
func create(req: Request) async throws -> HTTPStatus {
let token = try req.content.decode(Token.self)
try await token.create(on: req.db)
return .created
}
func delete(req: Request) async throws -> HTTPStatus {
guard let token = req.parameters.get("token") else {
return .badRequest
}
try await Token.query(on: req.db)
.filter(\.$token == token)
.first()
try await delete(req: req.db)
return .noContent
}
}
#available(macOS 12, *)
extension TokenController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let tokens = routes.grouped("token")
tokens.post(use: create)
tokens.delete(":token", use: delete)
}
}
The return token might not be there, so it should be optional, but I get the familiar error
Initializer for conditional binding must have Optional type, not 'EventLoopFuture<Token?>'
I'm using async-await obviously and not EventLoopFuture<> except maybe under the hood, if I understand the documentation correctly.
Based on a tutorial I'm following, this was suggested as a solution to delete rows by getting the first row in the table
func delete(req: Request) async throws -> HTTPStatus {
guard let token = req.parameters.get("token") else {
return .badRequest
}
guard let row = try await Token.query(on: req.db)
.filter(\.$token == token)
.first()
else {
return .notFound
}
try await row.delete(on: req.db)
return .noContent
}
But this gives me the same error for token, including another error for the row.delete(on:_) method
Value of type 'EventLoopFuture<Token?>' has no member 'delete'
What am I missing?
• Pouring over the documentation
• Retracing my steps from the tutorial
• Holding my breath until the computer worked
You probably need to give the compiler a hand and force it to use the async version of .first():
guard let row: Token = try await Token.query(on: req.db)
.filter(\.$token == token)
.first()
Because the functions have the same signature it can occasionally pick the future version, especially if there are errors elsewhere

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() ?? []
}
}

Swift async method call for a command line app

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.

How to Return HTTP Request Data in a Function Call in Swift?

I want to make a generic HTTP request function. The code I saw does not return data to the caller. Instead it prints out the error code or the parsed JSON object within the function. In my case I would like to return (data, response, error) to the caller.
func performHTTPRequest(urlString: String) -> (Data, URLResponse, Error) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) {(data, response, error) in
// some logic
}
task.resume()
}
}
The problem is the three variables (data, response, error) are not available outside the closure. If I assign them to global variables within the closure, compiler complains the global variables are not in scope.
Also, where would I put the return (data, response, error) statement? Before or after task.resume()? Thanks
The short answer is, you can't. You can us the new async/await syntax in Swift 5.5 to simulate a synchronous network call. (I haven't had a chance to use async/await in my own projects yet, so I'd have to look that up in order to guide you.)
Without async/await, you will need to refactor your function to take a completion handler. You'd then call the completion handler with the results once the data task completes.
This question comes up all the time on SO. You should be able to find dozens of examples of writing a completion handler-based function for async networking.
For example you can do that
struct Message: Decodable {
var username: String
var message: String
}
enum RequestError: Error {
case invalidURL
case missingData
}
func performHTTPRequest(urlString: String) async throws -> Message{
guard let url = URL(string: urlString) else {throw RequestError.invalidURL}
guard let (data, response) = try? await URLSession.shared.data(from: url) else{throw RequestError.invalidURL}
guard (response as? HTTPURLResponse)?.statusCode == 200 else {throw RequestError.invalidURL}
let decoder = JSONDecoder()
guard let jsonResponse = try? decoder.decode(Message.self, from: data) else {throw RequestError.missingData}
return jsonResponse
}
and call the fonction
do {
try await performHTTPRequest(urlString: "wwww.url.com")
} catch RequestError.invalidURL{
print("invalid URL")
} catch RequestError.missingData{
print("missing data")
}

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.