I come from the world of C# where async/await is a wonderful thing. Asynchronous programming is definitely not the simplest thing in Swift. PromiseKit really helps with that, though. I'm really new at using PromiseKit, so please let me know if I have some fundamental misunderstanding here.
I'm wondering why there is no way to specify a DispatchQueue for firstly like you can with then?
I would expect to find an overload like:
firstly(on: DispatchQueue, execute: () throws -> Promise<T>)
to mirror:
then(on: DispatchQueue, execute: () throws -> Promise<T>)
Is there a specific reason there is no such thing?
To clarify my use case:
// cache the username, can't access text field on background thread
let username = self.usernameTextField.text!
// show loading overlay, would like to put this in the promise chain somewhere
LoadingViewHelper.present(overView: self.view, labelText: "Creating...")
firstly {
// creates a [String: Any] to send to server
// Put in a promise because firstly cannot be invoked on background DispatchQueue
Promise(value: self.generateUserCreationParameters())
}
.then(on: DispatchQueue.global(qos: .background)) {
// fetches an auth token from server
self.createUser(newUserParametersDictionary: $0)
}
.then(on: DispatchQueue.global(qos: .background)) {
// fetches a user session object from server, uses the cached username
self.createNewUserSession(username: username, token: $0)
}
.then {
// sets the current session
Session.current = $0
}
.always {
// dismisses the loading overlay
LoadingViewHelper.dismiss()
}
.then { _ -> Void in
// do navigation stuff
}
.catch { (error) in
// do error stuff
}
So, as you can see, I want my server calls to be placed on a background thread. It's a server; who knows when it might finally reply. It's a relatively expensive operation, so I don't want to put it on the main thread. Because I can't call firstly on a background DispatchQueue, I have to go with this trickery.
Am I going about this all wrong? Or, would you argue that I can just let that server call stay on the main thread inside the Promise?
Also, while I am here, do you see any immediate way to put that LoadingViewHelper.present call into the chain besides just making it a statement inside firstly?
We provide a promise function as an extension on DispatchQueue for this usage. eg:
DispatchQueue.global().promise {
return foo()
}.then {
//…
}
firstly cannot take a queue without meaning it has dual behavior, the default case executes the closure immediately, if it also took a queue it would mean in some cases it would immediately execute the closure and in others it would execute it later. This violates the principle of least surprise and allow zalgo to be unleashed.
http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
Also, while I am here, do you see any immediate way to put that LoadingViewHelper.present call into the chain besides just making it a statement inside firstly?
I don't know what this function does so cannot really advise.
I found in the source code that the execute body: () throws -> Promise<T>) -> Promise<T> overload of promise on DispatchQueue is marked unavailable due to Swift compiler issues.
This throws a pretty big wrench into my chain because my server call already returns Promise<String>, since we don't know when the server call will resolve.
This is the code that I want to write:
LoadingViewHelper.present(overView: self.view, labelText: "Creating...")
let username = self.usernameTextField.text!
let parametersDictionary = self.generateUserCreationParameters()
DispatchQueue.global(qos: .background).promise {
self.createUser(newUserParametersDictionary: parametersDictionary) // Server POST -> Promise<String>
}
.then(on: DispatchQueue.global(qos: .background)) {
self.createNewUserSession(username: username, token: $0) // Server GET -> Promise<Session>
}
.then {
Session.current = $0 // Void
}
.always {
LoadingViewHelper.dismiss() // Void
}
.then { _ -> Void in
// navigation stuff
}
.catch { (error) in
// error stuff
}
However, this chain does not compile due to 'promise(group:qos:flags:execute:)' is unavailable. The source comments mention Swift compiler issues being the reason.
I suppose for now I will just keep using the chain I had in the original post. It worked and doesn't seem to be an egregiously bad solution. Though, I would prefer to use the chain in this post since it's easier to reason about.
I don't think that I have a fundamental mis-usage of Promises here. If I do, please let me know. This is my first foray into the world of Promises.
let q = DispatchQueue.global(qos: .background)
q.async {
firstly {
self.createUser(newUserParametersDictionary: parametersDictionary)
}
.then(on: q) {
self.createNewUserSession(username: username, token: $0)
}
.then {
Session.current = $0
}
.always {
//…
}
.then {
//…
}
.catch { error in
// error stuff
}
}
Or with PMK 6:
let q = DispatchQueue.global(qos: .background)
Promise { seal in
q.async {
self.createUser(newUserParametersDictionary: parametersDictionary).pipe(seal.resolve)
}
}.then(on: q) {
self.createNewUserSession(username: username, token: $0)
}
.then {
Session.current = $0
}
.always {
//…
}
.then {
//…
}
.catch { error in
// error stuff
}
Can you add your example above to the documentation? Seems like a common question and task. Thanks!
PR welcome
K added to the appendix, please review the above patch.
Next time open a new ticket :P
Or with PMK 6:
let q = DispatchQueue.global(qos: .background) Promise { seal in q.async { self.createUser(newUserParametersDictionary: parametersDictionary).pipe(seal.resolve) } }.then(on: q) { self.createNewUserSession(username: username, token: $0) } .then { Session.current = $0 } .always { //… } .then { //… } .catch { error in // error stuff }
The line
self.createUser(newUserParametersDictionary: parametersDictionary).pipe(seal.resolve)
should instead be
self.createUser(newUserParametersDictionary: parametersDictionary).pipe(to: seal.resolve)
Xcode 11.3 just made that fix for me.
Most helpful comment
Or with PMK 6: