So I'm trying to call a second endpoint in one of my Vapor endpoints. I have one endpoint that is just a get and works good:
router.get("status") { req -> Future<ConnectionResponse> in
let client = try req.make(Client.self)
let response = client.get("https://url.com/endpoint/")
return response.flatMap(to: ConnectionResponse.self, { response in
return try response.content.decode(ConnectionResponse.self)
})
}
This returns correctly a ConnectionResponse json. When I try to do the same, but in a POST that needs some parameters, I can't figure out why the compiler doesn't let me run:
router.post("purchase") { req -> Future<ConnectionResponse> in
return try req.content.decode(PurchaseRequest.self).map(to: ConnectionResponse.self, { response in
let client = try req.make(Client.self)
let response = client.get("https://url.com/endpoint/")
return response.flatMap(to: ConnectionResponse.self, { response in
return try response.content.decode(ConnectionResponse.self)
})
})
}
It fails on the flatMap saying Cannot convert return expression of type 'EventLoopFuture<ConnectionResponse>' to return type 'ConnectionResponse'.
As you can see, the purchase GET call is the same as the status apart from the initial decoding of the POST parameters. What am I doing wrong?
Simply replace
return try req.content.decode(PurchaseRequest.self).map(to: ConnectionResponse.self, { response in
with
return try req.content.decode(PurchaseRequest.self).flatMap(to: ConnectionResponse.self, { response in
Complete working code may look like this
router.post("purchase") { req -> Future<ConnectionResponse> in
return try req.content.decode(PurchaseRequest.self).flatMap { response in
let client = try req.make(Client.self)
let response = client.get("https://url.com/endpoint/")
return response.flatMap { response in
return try response.content.decode(ConnectionResponse.self)
}
}
}
So what is the difference between map and flatMap?
flatMap is used if next result is Future<Something> e.g.:
someDatabaseCall(on: container).flatMap { databaseResult1 in
/// it will return Future<DatabaseResult>
/// that's why we use `flatMap` above instead of `map`
return anotherDatabaseCall(on: container)
}
map is for non-future results e.g.:
someDatabaseCall(on: container).map { databaseResult1 in
/// it will return non-future result
/// that's we use `map` above instead of `flatMap`
return "hello world" // just simple string
}
What is Future<> ? It's just a promise, like an object with callback.
Related
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed last year.
I'm new at Swift and that's why i need your help. So I have a function which should send request and return a value
func getAnswer() -> String? {
var answer: String?
guard let url = URL(string: "https://8ball.delegator.com/magic/JSON/_") else { return nil }
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
return
}
guard let response = response as? HTTPURLResponse else { return }
guard response.statusCode == 200 else { return }
do {
let model = try JSONDecoder().decode(Answer.self, from: data)
DispatchQueue.main.async {
answer = model.magic.answer
}
} catch let error {
fatalError(error.localizedDescription)
}
}.resume()
return answer
}
but it always returns nil.
I suppose problem is here
DispatchQueue.main.async {
answer = model.magic.answer
}
How can I fix it?
In order to know what is happening here, you need to learn about #escaping functions in swift, here is some link1 together with taking function as another functions parameter link2 written in part "Function Types as Parameter Types" , closures in Swift link3 and
Here is what is happening simplified and explained step by step :
you call getAnswer()
variable answer gets initialized with value nil by declaring answer: String?
URLSession.shared.dataTask is called and it is taking as an argument another function - closure (Data?, URLResponse?, Error?) -> Void . Also URLSession.shared.dataTask is executed on different thread and is not returning yet, but will return right after it receives response from server, which can take any time (but usually milliseconds) and will basically happen after your getAnswer() function is returning value.
your getAnswer() immediately returns value of answer which is currently nil
if you get any data from server, or server could not be reached, your URLSession.shared.dataTask function executes your code in closure. This is the code it will execute:
guard let data = data, error == nil else {
return
}
guard let response = response as? HTTPURLResponse else { return }
guard response.statusCode == 200 else { return }
do {
let model = try JSONDecoder().decode(Answer.self, from: data)
DispatchQueue.main.async {
answer = model.magic.answer
}
} catch let error {
fatalError(error.localizedDescription)
}
Your problem lies in how swift executes closures. When you call
URLSession.shared.dataTask(with: url) {
// Closure code here
}
return answer
Your "Closure code here" doesn't get called until the endpoint "https://8ball.delegator.com/magic/JSON/_" actually gives a response. However, you've promised swift that your function will return an optional string immediately after the serial code of your function has completed. For this reason, by the time your "Closure code here" has run, and your "answer" variable has been updated with the correct value, your function is long gone, and has already returned a value (which in this case is whatever you've set it to at the beginning - nil).
You can fix this issue in one of two ways.
Swift's new concurrency system
By defining your own closure.
Swift's new concurrency system
You can define your function as async, meaning that the function won't have to return a value in serial, as follows.
enum GetAnswerError: Error {
case invalidURL
}
func getAnswer() async throws -> String {
var answer: String?
guard let url = URL(string: "https://8ball.delegator.com/magic/JSON/_") else {
throw GetAnswerError.invalidURL
}
// Your function will suspend here and probably be moved to a different thread. It will resume once a response has been received from the endpoint.
let (data, _) = try await URLSession.shared.dataTask(with: url)
let parsedData = try JSONDecoder().decode(Answer.self, from: data)
return parsedData.magic.answer
}
When you call this function, you'll have to do so from an environment which swift can suspend. This means you'll call the function from either another async function like so
func anotherFunction() async throws -> Bool {
let answer = try await getAnswer()
// Run some code here
return answer == "YES" // Return some useful value
}
or from a Task object like so
Task {
// Note that because the function getAnswer() can throw errors, you'll have to handle them when you call the function. In this case, I'm handling them by using try?, which will simply set answer to nil if an error is thrown.
let answer = try? await getAnswer()
}
Note that when you call code in a task, you must be using the return value's from within the scope of the task. If you try to do something like this
func getAnswerTheSecond() -> String? {
var answer: String? = nil
Task {
let receivedAnswer = try? await getAnswer()
answer = receivedAnswer
}
return answer
}
You'll just end up back where you started, where swift immediately returns the nil value because your code is ran in serial. To fix this, run the relevant code on the "answer" from wherever it is needed within the task. If you are using the "answer" to update a SwiftUI view that might look like this.
struct ContentView: View {
#State var answer: String = ""
// This is the function that I've written earlier
func getAnswer() async throws -> String {
// Make URL Request
// Return the value
}
var body: some View {
Text(self.answer)
.onAppear{
Task{
let result = try? await self.getAnswer()
self.answer = result
}
}
}
}
Defining your own closure
You can define your own closure to handle the URL response; however, because of swift's new concurrency framework, this is probably not the right way to go.
If you'd like to go this way, do a google search for "Swift closures", and you'll find what you need.
In Vapor 4, I'm processing a post request by calling a request on a 3rd party API and returning a value based on the result I get back. The following code results in the error: "Invalid conversion from throwing function ... to non-throwing function"
app.post("activate") { req -> EventLoopFuture<ActivationRequestResponse> in
return req.client.post("https://api.example.com/activation", headers: HTTPHeaders(), beforeSend: { (req) in
try req.content.encode(RequestBody(value: someValue), as: .json)
})
.map { (response) -> ActivationRequestResponse in
let response = try response.content.decode(ResponseModel.self)
return ActivationRequestResponse(success: true, message: "success")
}
}
I can't seem to use try in my chained map() after getting the API result. The above code will work if I add a ! to the try in let response = try response.content.decode(ResponseModel.self) inside the map, but ideally I want to catch this error. The first try used when creating the response body seems to be implicitly passed back up the chain, but not the second.
What am I doing wrong? How do I catch the error when decoding the response content? Why is the first try caught but not the second?
The property of map is that it will just transform a value on the “success path”. Your transformation may however fail which means that you presumably want the future to fail too.
Whenever you want to transform a value with a function that either succeeds or fails you need to use one of the flatMap* functions.
In your case, try replacing map with flatMapThrowing and then it should work.
To expand on Johannes Weiss' answer, to have a throwing closure that returns a future, you need something like:
future.flatMap {
do {
return try liveDangerously()
} catch {
future.eventLoop.makeFailedFuture(error)
}
}
After doing this too many times, I decided to roll my own (though the name is a bit dubious):
extension EventLoopFuture {
#inlinable
public func flatterMapThrowing<NewValue>(file: StaticString = #file,
line: UInt = #line,
_ callback: #escaping (Value) throws -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
return self.flatMap(file: file, line: line) { (value: Value) -> EventLoopFuture<NewValue> in
do {
return try callback(value)
} catch {
return self.eventLoop.makeFailedFuture(error)
}
}
}
}
That way you can just write:
future.flatterMapThrowing {
return try liveDangerously()
}
I'm trying to write a function using Swift and Vapor but I don't understand why one statement gets printed before the other:
// Logout user
func logout(_ req: Request) throws -> Future<APIResponseMessage> {
let userID = self.checkAccessToken(req: req)
// Delete access token here
let apiResponseMessage = APIResponseMessage()
apiResponseMessage.message = "success"
apiResponseMessage.userID = userID
return apiResponseMessage.create(on: req)
}
func checkAccessToken(req: Request) -> Int {
let bearerAuthorization = req.http.headers.bearerAuthorization
guard let _bearerAuthorization = bearerAuthorization else {
// Works fine
print("no bearer incluced")
return 0
}
let _ = AccessToken.query(on: req).filter(\.accessToken == _bearerAuthorization.token).first().map(to: Int.self) { queriedAccessToken in
// This should be first
print("This gets printed second")
return queriedAccessToken!.userID!
}
// This should be second
print("This gets printed first")
return 0
}
Can anyone tell me how to make the second print statement wait until the first one is completed?
Right now it's causing my logout function to run with userID == 0 when this shouldn't be the case
As #nathan said, this is due to your code being async. Your .map callback is like the closure you pass into to a URLSession.dataTask when making request's to an external API for an iOS app.
Vapor uses a slightly different async model then what you use in iOS though, using promises and futures instead of callback closures. You can read about them in the docs.
In your case, you want to return the userID you get from the AccessToken query. To do this, you first need to change your method's return type from Int to Future<Int>. Then, instead of assigning the result of the .map call to _, you can return it from the method:
func checkAccessToken(req: Request) -> Future<Int> {
let bearerAuthorization = req.http.headers.bearerAuthorization
guard let _bearerAuthorization = bearerAuthorization else {
return req.future(0)
}
return AccessToken.query(on: req).filter(\.accessToken == _bearerAuthorization.token).first().map(to: Int.self) { queriedAccessToken in
return queriedAccessToken!.userID!
}
}
I would suggest you look into error handling for your queriedAccessToken and userID values so you aren't force-unwrapping them.
I have a recursive, async function that queries Google Drive for a file ID using the REST api and a completion handler:
func queryForFileId(query: GTLRDriveQuery_FilesList,
handler: #escaping FileIdCompletionHandler) {
service.executeQuery(query) { ticket, data, error in
if let error = error {
handler(nil, error)
} else {
let list = data as! GTLRDrive_FileList
if let pageToken = list.nextPageToken {
query.pageToken = pageToken
self.queryForFileId(query: query, handler: handler)
} else if let id = list.files?.first?.identifier {
handler(id, nil)
} else {
handler(nil, nil) // no file found
}
}
}
}
Here, query is set up to return the nextPageToken and files(id) fields, service is an instance of GTLRDriveService, and FileIdCompletionHandler is just a typealias:
typealias FileIdCompletionHandler = (String?, Error?) -> Void
I've read how to convert async functions into promises (as in this thread) but I don't see how that can be applied to a recursive, async function. I guess I can just wrap the entire method as a Promise:
private func fileIdPromise(query: GTLRDriveQuery_FilesList) -> Promise<String?> {
return Promise { fulfill, reject in
queryForFileId(query: query) { id, error in
if let error = error {
reject(error)
} else {
fulfill(id)
}
}
}
}
However, I was hoping to something a little more direct:
private func queryForFileId2(query: GTLRDriveQuery_FilesList) -> Promise<String?> {
return Promise { fulfill, reject in
service.executeQuery(query) { ticket, data, error in
if let error = error {
reject(error)
} else {
let list = data as! GTLRDrive_FileList
if let pageToken = list.nextPageToken {
query.pageToken = pageToken
// WHAT DO I DO HERE?
} else if let id = list.files?.first?.identifier {
fulfill(id)
} else {
fulfill(nil) // no file found
}
}
}
}
}
So: what would I do when I need to make another async call to executeQuery?
If you want to satisfy a recursive set of promises, at where your "WHAT DO I DO HERE?" line, you'd create a new promise.then {...}.else {...} pattern, calling fulfill in the then clause and reject in the else clause. Obviously, if no recursive call was needed, though, you'd just fulfill directly.
I don't know the Google API and you didn't share your code for satisfying a promise for a list of files, so I'll have to keep this answer a bit generic: Let's assume you had some retrieveTokens routine that returned a promise that is satisfied only when all of the promises for the all files was done. Let's imagine that the top level call was something like:
retrieveTokens(for: files).then { tokens in
print(tokens)
}.catch { error in
print(error)
}
You'd then have a retrieveTokens that returns a promise that is satisfied only when then promises for the individual files were satisfied. If you were dealing with a simple array of File objects, you might do something like:
func retrieveTokens(for files: [File]) -> Promise<[Any]> {
var fileGenerator = files.makeIterator()
let generator = AnyIterator<Promise<Any>> {
guard let file = fileGenerator.next() else { return nil }
return self.retrieveToken(for: file)
}
return when(fulfilled: generator, concurrently: 1)
}
(I know this isn't what yours looks like, but I need this framework to show my answer to your question below. But it’s useful to encapsulate this “return all promises at a given level” in a single function, as it allows you to keep the recursive code somewhat elegant, without repeating code.)
Then the routine that returns a promise for an individual file would see if a recursive set of promises needed to be returned, and put its fulfill inside the then clause of that new recursively created promise:
func retrieveToken(for file: File) -> Promise<Any> {
return Promise<Any> { fulfill, reject in
service.determineToken(for: file) { token, error in
// if any error, reject
guard let token = token, error == nil else {
reject(error ?? FileError.someError)
return
}
// if I don't have to make recursive call, `fulfill` immediately.
// in my example, I'm going to see if there are subfiles, and if not, `fulfill` immediately.
guard let subfiles = file.subfiles else {
fulfill(token)
return
}
// if I got here, there are subfiles and I'm going to start recursive set of promises
self.retrieveTokens(for: subfiles).then { tokens in
fulfill(tokens)
}.catch { error in
reject(error)
}
}
}
}
Again, I know that the above isn't a direct answer to your question (as I'm not familiar with Google Drive API nor how you did your top level promise logic). So, in my example, I created model objects sufficient for the purposes of the demonstration.
But hopefully it's enough to illustrate the idea behind a recursive set of promises.
How i can return value from function used Alamofire . i try to print outside .responseJSON the value in ArrData is not set but i try print inside it work
this code:
func getDept()->NSMutableArray
{
var ArrData:NSMutableArray = []
let url = "http://www.xxxxxxxxxxxxx.com"
Alamofire.request(.GET, url).responseJSON { response in
let json = JSON(response.result.value!)
let count = json.count
for var index = 0; index < count;index++
{
ArrData.addObject(json[index]["dept"].stringValue)
}
}
return ArrData
}
it i good idea to check at least README.md of the framework which you are going to use in your code
Networking in Alamofire is done asynchronously. Asynchronous
programming may be a source of frustration to programmers unfamiliar
with the concept, but there are very good reasons for doing it this
way.
Rather than blocking execution to wait for a response from the server,
a callback is specified to handle the response once it's received. The
result of a request is only available inside the scope of a response
handler. Any execution contingent on the response or data received
from the server must be done within a handler.
Try to use a handler like this and a callback:
func getopt(callback:(array: [String]) -> void ){
func completion(request: NSURLRequest?, response:NSHTTPURLResponse?,result:Result<AnyObject>){
if let rdata = result.value{
let data = JSON(rdata)
print(data)
let myArray = [String]
let objects = data.array
for object in objects{
myArray.append(object)
}
callback(myArray)
}
}
let url = "http://www.xxxxxxxxxxxxx.com"
Alamofire.request(.GET,url),
encoding: .JSON).responseJSON(completionHandler: completion)
}
You pass the array to your callback So when you call getopt where you call it you can print the array. Some like this:
func something (){
getopt(callback)
}
func callback(array:[String]){
print array[0]
}