Why must I return a promise? - swift

Here is the code...
login().then {
// our login method wrapped an async task in a promise
return API.fetchKittens()
}.then { fetchedKittens in
// our API class wraps our API and returns promises
// fetchKittens returned a promise that resolves with an array of kittens
self.kittens = fetchedKittens
self.tableView.reloadData()
}.catch { error in
// any errors in any of the above promises land here
UIAlertView(…).show()
}
See how the then method is not returning anything.
When I use then the compiler is saying I must return a promise. Why don't I have the choice not to?
The error goes away when I add a catch clause straight after. huh?

Answer here.
I added -> Void after my closure parameter.
.then { fetchedKittens -> Void in }

You should use .done{} to finish promise chain, like this:
.done { fetchedKittens -> Void in }
.then{} with -> Void is not working anymore, because .then{} always need to return promise.
Documentation

Related

How to implement a function returning an AsyncSequence, whose results relies on another AsyncSequence

I'm trying to implement a function which returns an AsyncStream of BlurredImage objects. The functions relies on another function sourceImages(), which itself is an AsyncStream.
I get this error on line 2 of my snippet:
Cannot pass function of type '(AsyncStream<BlurredImage>.Continuation) async -> Void' to parameter expecting synchronous function type
What's the correct way to implement this?
func blurredFaces() -> AsyncStream<BlurredImage> {
return AsyncStream<BlurredImage> { continuation in
for await image in sourceImages() {
blurrImage(image) { blurredImage in
continuation.yield(blurredImage)
}
}
continuation.finish()
}
}
func sourceImages() -> AsyncStream<SourceImage> {
...
}
I'm on Xcode 13.4.1
You seem to be doing an mapping operation here. In that case, you can use map and return an AsyncMapSequence:
func blurredFaces() -> AsyncMapSequence<AsyncStream<SourceImage>, BlurredImage> {
sourceImages().map { image in
await withCheckedContinuation { continuation in
blurrImage(image) { blurred in
continuation.resume(returning: blurred)
}
}
}
}
If you can make blurrImage async, then this can be even shorter:
func blurredFaces() -> AsyncMapSequence<AsyncStream<SourceImage>, BlurredImage> {
// you can even inline this, and get rid of blurredFaces
sourceImages().map(blurrImage)
}
AsyncStream.init expects an input of type (AsyncStream<BlurredImage>.Continuation) → Void, which is not async.
But you can wrap its body in a Task to do async operations.
return AsyncStream<BlurredImage> { continuation in
Task {
for await image in sourceImages() {
blurrImage(image) { blurredImage in
continuation.yield(blurredImage)
}
}
continuation.finish()
}
}
Or as #Sweeper suggested, you can use map. Based on your blurredFaces return type signature, you want to map AsyncStream<SourceImage> (result of sourceImages) to AsyncStream<BlurredImage>.
In case for some reason you want to keep its return type as that, I haven't found a built-in direct map from an AsyncStream<T1> to an AsyncStream<T2>, so I posted this related SO question. In this answer, I shared the AsyncStream extension that I created for this use case. I am still waiting for feedbacks for this solution.

How to cancel an `async` function with cancellable type returned from `async` operation initiation

I need to support cancellation of a function that returns an object that can be cancelled after initiation. In my case, the requester class is in a 3rd party library that I can't modify.
actor MyActor {
...
func doSomething() async throws -> ResultData {
var requestHandle: Handle?
return try await withTaskCancellationHandler {
requestHandle?.cancel() // COMPILE ERROR: "Reference to captured var 'requestHandle' in concurrently-executing code"
} operation: {
return try await withCheckedThrowingContinuation{ continuation in
requestHandle = requester.start() { result, error in
if let error = error
continuation.resume(throwing: error)
} else {
let myResultData = ResultData(result)
continuation.resume(returning: myResultData)
}
}
}
}
}
...
}
I have reviewed other SO questions and this thread: https://forums.swift.org/t/how-to-use-withtaskcancellationhandler-properly/54341/4
There are cases that are very similar, but not quite the same. This code won't compile because of this error:
"Reference to captured var 'requestHandle' in concurrently-executing code"
I assume the compiler is trying to protect me from using the requestHandle before it's initialized. But I'm not sure how else to work around this problem. The other examples shown in the Swift Forum discussion thread all seem to have a pattern where the requester object can be initialized before calling its start function.
I also tried to save the requestHandle as a class variable, but I got a different compile error at the same location:
Actor-isolated property 'profileHandle' can not be referenced from a
Sendable closure
You said:
I assume the compiler is trying to protect me from using the requestHandle before it’s initialized.
Or, more accurately, it is simply protecting you against a race. You need to synchronize your interaction with your “requester” and that Handle.
But I’m not sure how else to work around this problem. The other examples shown in the Swift Forum discussion thread all seem to have a pattern where the requester object can be initialized before calling its start function.
Yes, that is precisely what you should do. Unfortunately, you haven’t shared where your requester is being initialized or how it was implemented, so it is hard for us to comment on your particular situation.
But the fundamental issue is that you need to synchronize your start and cancel. So if your requester doesn’t already do that, you should wrap it in an object that provides that thread-safe interaction. The standard way to do that in Swift concurrency is with an actor.
For example, let us imagine that you are wrapping a network request. To synchronize your access with this, you can create an actor:
actor ResponseDataRequest {
private var handle: Handle?
func start(completion: #Sendable #escaping (Data?, Error?) -> Void) {
// start it and save handle for cancelation, e.g.,
handle = requestor.start(...)
}
func cancel() {
handle?.cancel()
}
}
That wraps the starting and canceling of a network request in an actor. Then you can do things like:
func doSomething() async throws -> ResultData {
let responseDataRequest = ResponseDataRequest()
return try await withTaskCancellationHandler {
Task { await responseDataRequest.cancel() }
} operation: {
return try await withCheckedThrowingContinuation { continuation in
Task {
await responseDataRequest.start { result, error in
if let error = error {
continuation.resume(throwing: error)
} else {
let resultData = ResultData(result)
continuation.resume(returning: resultData)
}
}
}
}
}
}
You obviously can shift to unsafe continuations when you have verified that everything is working with your checked continuations.
After reviewing the Swift discussion thread again, I see you can do this:
...
var requestHandle: Handle?
let onCancel = { profileHandle?.cancel() }
return try await withTaskCancellationHandler {
onCancel()
}
...

Check for certain statements in Swift closure

I wrote a function which takes a closure as an argument like this:
func doSome(work: () -> Void = { print("sleeping...") } ) {
work()
}
Now I would like to investigate the work done.
Therefore I want to check if the given closure contains any print statements.
Somehow like this:
func doSome(work: () -> Void = { print("doing hard work...") } ) {
work()
if work.contains(print) {
print("we did some hard work there and printed something!")
}
}
How can I achieve that?
EDIT: What I am trying to achieve
An async function tries to connect to an http server - let's call it connect. It takes a closure as its parameter - called finally. As its name already indicates: the closure gets executed after the connecting attempt.
If the connecting attempt succeeds (http response code == 200), I need to call another function ONCE - let's call it so: once.
The connect function therefore looks like this:
func connect(finally: () -> Void = {}) {
httpRepsonse = asyncRequestToServer()
if httpResponse.statusCode == 200 {
once()
}
// and finally:
finally()
}
Other functions call connect and pass over their statements that they need for the connect function to execute finally.
And here comes the problem: there is one function that needs once executed every time, therefore it passes it over in the finally closure. If the connecting now succeeds, once gets called twice.
That's why I wanted to check the given closure already contains the once call, so I could avoid calling it twice.
Interrogating a closure for its contents is not easily done as far as I know.
You could do a workaround (depending on your needs and implementation of course) using one or more Boolean arguments which you would assign when calling the function, if relevant.
For example:
func doSome(work: () -> Void = { print("doing hard work...")}, containsPrint: Bool = false) {
// Call your work closure
work()
// Check conditions
if containsPrint {
print("We printed some stuff")
}
}
I am aware that this is a rather simple solution but it might provide the required functionality.
Use a global variable that you change whenever you print to the console, and check it inside you doSome(work:)
Short answer: You can't. As Alexander says, Swift does not support this. You would have to add some sort of housekeeping, as suggested in Carpsen90's answer.

How to set up when(fulfilled:) in PromiseKit?

I have a function set up to return a Promise<PFObject>. I would like to use this function in PromiseKit's when(fulfilled:) functionality, but whenever I try to do so, I get an error. Here is the function which returns the Promise<PFObject>:
func Query() -> Promise<PFObject>{
return Promise{ fulfill, reject in
let linkQueryy = PFUser.query()
linkQueryy?.findObjectsInBackground(block: { (objectss, error) in
if let objects = objectss{
for object in objects{
fulfill(object)
}
}
})
}
}
As you can see, the function returns the Promise upon fulfillment. Thus, I tried to set up a when statement in my viewDidLoad() as follows:
override func viewDidLoad() {
super.viewDidLoad()
when(fulfilled: Query()).then{
//do more asynch stuff
}
}
However, I get the error that xcode cannot "invoke 'when' with an argument list type of '(fulfilled: Promise<PFObject>)'". I do not know how to fix this as I thought I had it set up correctly. The when needs a promise, and I am giving it one so I am not sure what to do.
Try as follows :
when(fulfilled: [linkQueryy()] as! [Promise<Any>]).then { _ in
// do more asynch stuff
}
The parameter fulfilled: needs to be an iterable.
By the way, when(fulfilled:) is necessary only when you have many promises and need wait for all to complete successfully. But in your code, you need to wait for only one promise.
For a single promise, the better way is to form a chain as follows :
firstly {
linkQueryy()
}.then { _ -> Void in
// do more asynch stuff
}.catch { _ in
// error!
}

How to utilize PromiseKit to ensure a queried object has been retrieved before proceeding?

I am programming an app which utilizes a parse-server (hosted by heroku) database. I have several functions which pull information from the DB, but they are all inherently asynchronous (because of the way parse's .findObjectinBackground works.) The issue with this as that the later DB queries require information from previous queries. Since the information being pulled is asynchronous, I decided to implement PromiseKit to ensure that the object has been found from findObjectinBackground from the first query, before running the second query.
The general form of the queries is as follows:
let query = PFQuery(classname: "Hello")
query?.findObjectsInBackground(block: { (objects, error) in
if let objectss = objects{
for object in objectss{ //object needs to be pulled
arrayOfInterest.append(object)
//array must be appended before moving on to next query
}
}
})
I just do not know how exactly to do this. This is the way I would like to implement it:
import PromiseKit
override func viewDidLoad(){
when(/*query object is retrieved/array is appended*/).then{
//perform the next query
}
}
I simply don't know exactly what to put in the when() and the .then{}. I tried making the queries into their own individual functions and calling them inside those two (when and then) functions, but I basically get told that I cannot because they return void. Also, I cannot simply ensure the first query is run in the when() as the query.findObjectinBackground(in the query) being asynchronous is the issue. The object specifically needs to be pulled, not just the query run, before the next one can fire.
Do you want create your promise?
You need write a function that return a Promise<Any>. In your case, need to encapsulate the entire code inside of Promise { fulfill, reject in HERE}. For example:
func foo(className: String) -> Promise<[TypeOfArrayOfInterest]> {
return Promise { fulfill, reject in
let query = PFQuery(classname: className)
query?.findObjectsInBackground(block: { (objects, error) in
if let error = error {
reject(error) // call reject when some error happened
return
}
if let objectss = objects {
for object in objectss{
arrayOfInterest.append(object)
}
fulfill(arrayOfInterest) // call fulfill with some value
}
})
}
}
Then, you call this function in firstly:
firstly {
foo(className: "Hello")
}.then { myArrayOfInterest -> Void in
// do thing with myArrayOfInterest
}.catch { error in
// some error happened, and the reject was called!
}
Also, I wrote a post in my blog about, among other things, PromiseKit and architecture. It may be helpful: http://macalogs.com.br/ios/rails/ifce/2017/01/01/experiencias-eventbee.html
Edit
More complete example:
func foo() -> Promise<Int> {
...
}
func bar(someText: String) -> Promise<String> {
...
}
func baz() -> Promise<Void> {
...
}
func runPromises() {
firstly {
foo()
}.then { value -> Promise<Any> in
if value == 0 {
return bar(someText: "no")
} else {
return bar(someText: "yes")
}
}.then { _ /* I don't want a String! */ -> Promise<Void> in
baz()
}.catch { error in
// some error happened, and the reject was called!
}
}
Or if you don't want a catch:
_ = firstly {
foo()
}.then { _ in
// do some thing
}
Swift have a greate type inference, but, when use PromiseKit, I recommend always write a type in then closure, to avoid erros.