Don’t do this:
func toggleNetworkSpinnerWithPromise<T>(funcToCall: () -> Promise<T>) -> Promise<T> {
return Promise { seal in
firstly {
setNetworkActivityIndicatorVisible(true)
return funcToCall()
}.then { result in
seal.fulfill(result)
}.always {
setNetworkActivityIndicatorVisible(false)
}.catch { err in
seal.reject(err)
}
}
}
Do this:
func toggleNetworkSpinnerWithPromise<T>(funcToCall: () -> Promise<T>) -> Promise<T> {
return firstly {
setNetworkActivityIndicatorVisible(true)
return funcToCall()
}.always {
setNetworkActivityIndicatorVisible(false)
}
}
You already had a promise, you don’t need to wrap it in another promise.
Mostly when we see Promise<Item?>
it implies a misuse of promises, for
example:
return firstly {
getItems()
}.then { items -> Promise<[Item]?> in
guard !items.isEmpty else {
return .value(nil)
}
return Promise(value: items)
}
The second then
chooses to return nil
in some circumstances. This imposes
the nil
check on the consumer of this promise. Instead create an specific
error type for this condition:
return firstly {
getItems()
}.map { items -> [Item]> in
guard !items.isEmpty else {
throw MyError.emptyItems
}
return items
}
Note see compactMap
when you want to error for conditions where API outside your control returns an Optional and you don’t want nil
.
class MyViewController: UIViewController {
private let ambience: Promise<AVAudioPlayer> = DispatchQueue.global().async(.promise) {
guard let asset = NSDataAsset(name: "CreepyPad") else { throw PMKError.badInput }
let player = try AVAudioPlayer(data: asset.data)
player.prepareToPlay()
return player
}
}
firstly {
UIView.animate(.promise, duration: 0.3) {
self.button1.alpha = 0
}
}.then {
UIView.animate(.promise, duration: 0.3) {
self.button2.alpha = 1
}
}.then {
UIView.animate(.promise, duration: 0.3) {
adjustConstraints()
self.view.layoutIfNeeded()
}
}
It is often convenient to erase the type of a promise to facilitate chaining,
for example UIView.animate(.promise)
returns Guarantee<Bool>
since UIKit’s
completion feeds Bool
, however we usually don’t need it and we can chain
more simply if it were Void
, thus we use asVoid()
:
UIView.animate(.promise, duration: 0.3) {
self.button1.alpha = 0
}.asVoid().done(self.nextStep)
For situations where we are combining many promises into a when
, asVoid()
becomes essential:
let p1 = foo()
let p2 = bar()
let p3 = baz()
//…
let p10 = fluff()
when(fulfilled: p1, p2, p3, /*…*/, p10).then {
let value1 = foo().value! // safe bang since all the promises fulfilled
// …
let value10 = fluff().value!
}.catch {
//…
}
Note the reason you don’t have to do this usually with when
is we do this for
you for when
s with up to 5 parameters.
Sometimes you have to block the main thread, but the task is asynchronous, in
these cases you can (with caution) use wait
:
public extension UNUserNotificationCenter {
var wasPushRequested: Bool {
let settings = Guarantee(resolver: getNotificationSettings).wait()
return settings != .notDetermined
}
}
The task under the promise must not callback onto the current thread or you get deadlock.
firstly
deliberately does not take a queue, (rationale in the ticket tracker).
So if you want to start a chain by dispatching to the background you have to use
DispatchQueue.async
:
DispatchQueue.global().async(.promise) {
return value
}.done { value in
//…
}
However this function cannot return a promise (due to Swift compiler ambiguity issues), thus if you must start a promise on a background queue then you need to do something like this:
Promise { seal in
DispatchQueue.global().async {
seal(value)
}
}.done { value in
//…
}
Or more simply (though with caveats, see the documentation for wait
)
DispatchQueue.global().async(.promise) {
return try fetch().wait()
}.done { value in
//…
}
However, you shouldn't need to do this (often) if you find yourself wanting this
then maybe you should instead go to the function definition for fetch
and make
it do its work on a background thread instead. Promises abstract asynchronicity,
so… abstract that asynchronicity by making it so your consumers don’t care about
the queue your function is called upon.