261 lines
7.1 KiB
Markdown
261 lines
7.1 KiB
Markdown
# Troubleshooting
|
||
|
||
## Compilation errors
|
||
|
||
99% of compilation issues involving PromiseKit can be addressed or diagnosed by one of the fixes below.
|
||
|
||
### Check your handler
|
||
|
||
```swift
|
||
return firstly {
|
||
URLSession.shared.dataTask(.promise, with: url)
|
||
}.compactMap {
|
||
JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
|
||
}.then { dict in
|
||
User(dict: dict)
|
||
}
|
||
```
|
||
|
||
Swift (unhelpfully) says:
|
||
|
||
> Cannot convert value of type '([String : Any]) -> User' to expected argument type '([String : Any]) -> _'
|
||
|
||
What’s the real problem? `then` *must* return a `Promise`, and you're trying to return something else. What you really want is `map`:
|
||
|
||
```swift
|
||
return firstly {
|
||
URLSession.shared.dataTask(.promise, with: url)
|
||
}.compactMap {
|
||
JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
|
||
}.map { dict in
|
||
User(dict: dict)
|
||
}
|
||
```
|
||
|
||
### Specify closure parameters **and** return type
|
||
|
||
For example:
|
||
|
||
```swift
|
||
return firstly {
|
||
foo()
|
||
}.then { user in
|
||
//…
|
||
return bar()
|
||
}
|
||
```
|
||
|
||
This code may compile if you specify the type of `user`:
|
||
|
||
|
||
```swift
|
||
return firstly {
|
||
foo()
|
||
}.then { (user: User) in
|
||
//…
|
||
return bar()
|
||
}
|
||
```
|
||
|
||
If it still doesn't compile, perhaps you need to specify the return type, too:
|
||
|
||
```swift
|
||
return firstly {
|
||
foo()
|
||
}.then { (user: User) -> Promise<Bar> in
|
||
//…
|
||
return bar()
|
||
}
|
||
```
|
||
|
||
We have made great effort to reduce the need for explicit typing in PromiseKit 6,
|
||
but as with all Swift functions that return a generic type (e.g., `Array.map`),
|
||
you may need to explicitly tell Swift what a closure returns if the closure's body is
|
||
longer than one line.
|
||
|
||
> *Tip*: Sometimes you can force a one-liner by using semicolons.
|
||
|
||
|
||
### Acknowledge all incoming closure parameters
|
||
|
||
Swift does not permit you to silently ignore a closure's parameters. For example, this code:
|
||
|
||
```swift
|
||
func _() -> Promise<Void> {
|
||
return firstly {
|
||
proc.launch(.promise) // proc: Foundation.Process
|
||
}.then {
|
||
when(fulfilled: p1, p2) // both p1 & p2 are `Promise<Void>`
|
||
}
|
||
}
|
||
```
|
||
|
||
Fails to compile with the error:
|
||
|
||
Cannot invoke 'then' with an argument list of type '(() -> _)
|
||
|
||
What's the problem? Well, `Process.launch(.promise)` returns
|
||
`Promise<(String, String)>`, and we are ignoring this value in our `then` closure.
|
||
If we’d referenced `$0` or named the parameter, Swift would have been satisfied.
|
||
|
||
Assuming that we really do want to ignore the argument, the fix is to explicitly
|
||
acknowledge its existence by assigning it the name "_". That's Swift-ese for "I
|
||
know there's a value here, but I'm ignoring it."
|
||
|
||
|
||
```swift
|
||
func _() -> Promise<Void> {
|
||
return firstly {
|
||
proc.launch(.promise)
|
||
}.then { _ in
|
||
when(fulfilled: p1, p2)
|
||
}
|
||
}
|
||
```
|
||
|
||
In this situation, you won't always receive an error message that's as clear as the
|
||
one shown above. Sometimes, a missing closure parameter sends Swift scurrying off
|
||
into type inference limbo. When it finally concludes that there's no way for it to make
|
||
all the inferred types work together, it may end up assigning blame to some other
|
||
closure entirely and giving you an error message that makes no sense at all.
|
||
|
||
When faced with this kind of enigmatic complaint, a good rule of thumb is to
|
||
double-check your argument and return types carefully. If everything looks OK,
|
||
temporarily add explicit type information as shown above, just to rule
|
||
out mis-inference as a possible cause.
|
||
|
||
### Try moving code to a temporary inline function
|
||
|
||
Try taking the code out of a closure and putting it in a standalone function. Now Swift
|
||
will give you the *real* error message. For example:
|
||
|
||
```swift
|
||
func doStuff() {
|
||
firstly {
|
||
foo()
|
||
}.then {
|
||
let bar = bar()
|
||
let baz = baz()
|
||
when(fulfilled: bar, baz)
|
||
}
|
||
}
|
||
```
|
||
|
||
Becomes:
|
||
|
||
```swift
|
||
func doStuff() {
|
||
func fluff() -> Promise<…> {
|
||
let bar = bar()
|
||
let baz = baz()
|
||
when(fulfilled: bar, baz)
|
||
}
|
||
|
||
firstly {
|
||
foo()
|
||
}.then {
|
||
fluff()
|
||
}
|
||
}
|
||
```
|
||
|
||
An *inline* function like this is all you need. Here, the problem is that you
|
||
forgot to mark the last line of the closure with an explicit `return`. It's required
|
||
here because the closure is longer than one line.
|
||
|
||
|
||
## You copied code off the Internet that doesn’t work
|
||
|
||
Swift has changed a lot over the years and so PromiseKit has had to change to keep
|
||
up. The code you copied is probably for an older PromiseKit. *Read the definitions of the
|
||
functions.* It's easy to do this in Xcode by option-clicking or command-clicking function names.
|
||
All PromiseKit functions are documented and provide examples.
|
||
|
||
## "Context type for closure argument expects 1 argument, which cannot be implicitly ignored"
|
||
|
||
You have a `then`; you want a `done`.
|
||
|
||
## "Missing argument for parameter #1 in call"
|
||
|
||
This is part of Swift 4’s “tuplegate”.
|
||
|
||
You must specify your `Void` parameter:
|
||
|
||
```swift
|
||
seal.fulfill(())
|
||
```
|
||
|
||
Yes: we hope they revert this change in Swift 5 too.
|
||
|
||
## Other issues
|
||
|
||
### `Pending Promise Deallocated!`
|
||
|
||
If you see this warning, you have a path in your `Promise` initializer that allows
|
||
the promise to escape without being sealed:
|
||
|
||
```swift
|
||
Promise<String> { seal in
|
||
task { value, error in
|
||
if let value = value as? String {
|
||
seal.fulfill(value)
|
||
} else if let error = error {
|
||
seal.reject(error)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
There are two missing paths here, and if either occurs, the promise will soon be
|
||
deallocated without resolving. This will manifest itself as a bug in your app,
|
||
probably the awful infinite spinner.
|
||
|
||
So let’s be thorough:
|
||
|
||
```swift
|
||
Promise<String> { seal in
|
||
task { value, error in
|
||
if let value = value as? String {
|
||
fulfill(value)
|
||
} else if let error = error {
|
||
reject(error)
|
||
} else if value != nil {
|
||
reject(MyError.valueNotString)
|
||
} else {
|
||
// should never happen, but we have an `PMKError` for task being called with `nil`, `nil`
|
||
reject(PMKError.invalidCallingConvention)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
If this seems tedious, it shouldn’t. You would have to be this thorough without promises, too.
|
||
The difference is that without promises, you wouldn’t get a warning in the console notifying
|
||
you of your mistake!
|
||
|
||
### Slow compilation / compiler cannot solve in reasonable time
|
||
|
||
Add return types to your closures.
|
||
|
||
### My promise never resolves
|
||
|
||
Check to be sure that your asynchronous task even *starts*. You’d be surprised how
|
||
often this is the cause.
|
||
|
||
For example, if you are using `URLSession` without our extension (but
|
||
don’t do that; *use* our extension! we know all the pitfalls), did you forget
|
||
to call `resume` on the task? If so, the task never actually starts, and so of
|
||
course it never finishes, either.
|
||
|
||
### `Result of call to 'done(on:_:)' is unused`, `Result of call to 'then(on:_:)' is unused`
|
||
|
||
PromiseKit deliberately avoids the `@discardableResult` annotation because the
|
||
unused result warning is a hint that you have not handled the error in your
|
||
chain. So do one of these:
|
||
|
||
1. Add a `catch`
|
||
2. `return` the promise (thus punting the error handling to the caller)
|
||
3. Use `cauterize()` to silence the warning.
|
||
|
||
Obviously, do 1 or 2 in preference to 3.
|