I would like to simulate async and await request from Javascript to Swift 4. I searched a lot on how to do it, and I thought I found the answer with DispatchQueue
In iOS 13 and up, you can now do this using Combine. Future
is analogous to async
and the flatMap
operator on publishers (Future
is a publisher) is like await
. Here's an example, loosely based on your code:
Future<Feature, Error> { promise in
directions.calculate(options) { (waypoints, routes, error) in
if let error = error {
promise(.failure(error))
}
promise(.success(routes))
}
}
.flatMap { routes in
// extract feature from routes here...
feature
}
.receiveOn(DispatchQueue.main) // UI updates should run on the main queue
.sink(receiveCompletion: { completion in
// completion is either a .failure or it's a .success holding
// the extracted feature; if the process above was successful,
// you can now add feature to the map
}, receiveValue: { _ in })
.store(in: &self.cancellables)
Edit: I went into more detail in this blog post.
You can use this framework for Swift coroutines - https://github.com/belozierov/SwiftCoroutine
Unlike DispatchSemaphore, when you call await it doesn’t block the thread but only suspends coroutine, so you can use it in the main thread as well.
func awaitAPICall(_ url: URL) throws -> String? {
let future = URLSession.shared.dataTaskFuture(for: url)
let data = try future.await().data
return String(data: data, encoding: .utf8)
}
func load(url: URL) {
DispatchQueue.main.startCoroutine {
let result1 = try self.awaitAPICall(url)
let result2 = try self.awaitAPICall2(result1)
let result3 = try self.awaitAPICall3(result2)
print(result3)
}
}
(Note: Swift 5 may support await as you’d expect it in ES6!)
What you want to look into is Swift's concept of "closures". These were previously known as "blocks" in Objective-C, or completion handlers.
Where the similarity in JavaScript and Swift come into play, is that both allow you to pass a "callback" function to another function, and have it execute when the long-running operation is complete. For example, this in Swift:
func longRunningOp(searchString: String, completion: (result: String) -> Void) {
// call the completion handler/callback function
completion(searchOp.result)
}
longRunningOp(searchString) {(result: String) in
// do something with result
}
would look like this in JavaScript:
var longRunningOp = function (searchString, callback) {
// call the callback
callback(err, result)
}
longRunningOp(searchString, function(err, result) {
// Do something with the result
})
There's also a few libraries out there, notably a new one by Google that translates closures into promises: https://github.com/google/promises. These might give you a little closer parity with await
and async
.
You can use semaphores to simulate async/await.
func makeAPICall() -> Result <String?, NetworkError> {
let path = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: path) else {
return .failure(.url)
}
var result: Result <String?, NetworkError>!
let semaphore = DispatchSemaphore(value: 0)
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
result = .success(String(data: data, encoding: .utf8))
} else {
result = .failure(.server)
}
semaphore.signal()
}.resume()
_ = semaphore.wait(wallTimeout: .distantFuture)
return result
}
And here is example how it works with consecutive API calls:
func load() {
DispatchQueue.global(qos: .utility).async {
let result = self.makeAPICall()
.flatMap { self.anotherAPICall($0) }
.flatMap { self.andAnotherAPICall($0) }
DispatchQueue.main.async {
switch result {
case let .success(data):
print(data)
case let .failure(error):
print(error)
}
}
}
}
Here is the article describing it in details.
And you can also use promises with PromiseKit and similar libraries
Thanks to vadian's comment, I found what I expected, and it's pretty easy. I use DispatchGroup()
, group.enter()
, group.leave()
and group.notify(queue: .main){}
.
func myFunction() {
let array = [Object]()
let group = DispatchGroup() // initialize
array.forEach { obj in
// Here is an example of an asynchronous request which use a callback
group.enter() // wait
LogoRequest.init().downloadImage(url: obj.url) { (data) in
if (data) {
group.leave() // continue the loop
}
}
}
group.notify(queue: .main) {
// do something here when loop finished
}
}