全部文章
以下是对
PromiseKit
的README.md
的翻译。
编译错误
涉及 PromiseKit 的 99% 的编译问题,都可以通过以下的解决方案进行修复。
检查你的处理程序
return firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
}.then { dict in
User(dict: dict)
}
Swift 提示的信息没有任何意义:
Cannot convert value of type '([String : Any]) -> User' to expected argument type '([String : Any]) -> _'
真正的问题是什么呢? then
中必须返回一个 Promise
,而不是其他的类。这里应该使用的是 map
:
return firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
}.map { dict in
User(dict: dict)
}
指定闭包的参数和返回值类型
例如:
return firstly {
foo()
}.then { user in
//…
return bar()
}
上面的代码在指定 user 的类型之后应该就可以编译了:
return firstly {
foo()
}.then { (user: User) in
//…
return bar()
}
如果依然无法编译,你可以尝试一下指定返回值类型:
return firstly {
foo()
}.then { (user: User) -> Promise<Bar> in
//…
return bar()
}
在 PromiseKit 6 中,我们已经尽最大努力来减少需要指定具体类型的场景。在 Swift 中有些方法依然需要指定具体的类型(比如 Array.map 方法),所以当闭包的内容超过一行时,有可能你还得指定具体的类型。
提示:有时,你可以使用分号将多行变成一行。
确认所有的闭包参数
在 Swift 中,不允许静默的忽略闭包的参数。举例来说:
func _() -> Promise<Void> {
return firstly {
proc.launch(.promise) // proc: Foundation.Process
}.then {
when(fulfilled: p1, p2) // both p1 & p2 are `Promise<Void>`
}
}
产生的编译错误为:
Cannot invoke 'then' with an argument list of type '(() -> _)
什么原因呢? Process.launch(.promise)
返回的类型为 Promise<(String, String)>
,但是我们在 then 的闭包中忽略了这个值。只有使用 $0 或者其他参数来引用这个参数,Swift 才可以顺利编译。
假设我们确实需要忽略这个参数,解决方法是:将这个参数命名为 “_”
。表示的意思是,“我知道这里有个参数,但是我不需要它”。
func _() -> Promise<Void> {
return firstly {
proc.launch(.promise)
}.then { _ in
when(fulfilled: p1, p2)
}
}
用这种方法,就不会再收到上述类似的错误信息了。有时候,省略掉闭包的参数会让 Swift 无法进行类型推断。当它无法推断出所有的类型时,它可能将错误指定到其他的闭包上,然后给出一条毫无意义的错误信息。
面对这种莫名其妙的问题是,比较好的解决方法就是:仔细检查参数和返回值的类型。如果找不到其他问题,那就先添加上上述的显示类型信息,来排除由类型推断产生的错误。
尝试使用内部函数
尝试将闭包中的代码移动到独立的函数中。然后,Swift 就能给出正确的错误提醒。例如:
func doStuff() {
firstly {
foo()
}.then {
let bar = bar()
let baz = baz()
when(fulfilled: bar, baz)
}
}
修改为:
func doStuff() {
func fluff() -> Promise<…> {
let bar = bar()
let baz = baz()
when(fulfilled: bar, baz)
}
firstly {
foo()
}.then {
fluff()
}
}
你要做的就是使用这样的一个内部函数。还有一个问题就是,你还得在最后一行加上 return。因为闭包超过一样,所以必须得加上。
从网络上复制的代码不起作用
最近几年 Swift 一直在不断地调整,PromiseKit 也不得不跟着修改。你复制的代码可能是旧版本的 PromiseKit。这时,请查看方法的定义。在 Xcode 中,点击方法的选项或命令就可以方便的查看了。所有的 PromiseKit 都有详细的文档和实例说明。
闭包参数的上下文需要用一个参数来接收,怎样隐式的忽略掉它?
用 done 替换掉 then。
调用时缺少一个属性参数
这是 Swift 4 中的 “tuplegate”。
你必须制定一个 Void 参数。
seal.fulfill(())
我们也希望在 Swift 5 中能恢复这个改动。
firstly(execute:) 造成的歧义
移除 firstly,比如:
firstly {
foo()
}.then {
//…
}
变成:
foo().then {
//…
}
再重新编译,Switch 可能就会正确的提示错误信息。
其他问题
释放未完成的 Promise(Pending Promise Deallocated!
)
你看到这个错误信息是因为,在 Promise 初始化方法中,有些路径没有设置 Promise 的执行结果。
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)
}
}
}
这里漏了两种情况,任何一种情况的发生,都会导致 promise 在没有处理前被释放。这应该是你的应用程序的 bug,而且可能会引起严重的后果。
完整的代码如下:
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)
}
}
}
这看起来很繁琐,其实不是。即使不使用 Promise,你也应该考虑所有的情况。如果不使用 Promise,唯一的差别是:在控制台不会有这个错误提醒而已。
编译慢或者编译超时
在闭包中添加返回值的类型。
创建的 Promise 不会结束
有几个潜在的原因:
- 检查异步任务,确保这些任务已经开始
这个是常见的原因。
例如,在使用 URLSession 时,如果没有使用我们已有的扩展(尽量使用我们扩展,因为我们知道所有可能出错的情况),那就检查一下是否在任务上调用了 resume。如果没有调用,任务就不会开始,所以 promise 也不会结束。
- 检查自定义的 Promise 初始化方法,看是否所有情况都被处理了?
查看上面的 “Pending Promise Deallocated” 问题。通常,有些情况没有处理,就会看到这个警告。你只有将所有情况都处理了,才不会看到这个警告。
有未处理的情况,就会导致 promise 无法结束。
- 确保执行 Promise 处理程序的队列没有被阻塞
如果执行处理程序的线程被阻塞了,那处理程序就无法被执行。通常,你看一下是否使用了我们提供的 wait() 方法。关于 wait() 使用建议和注意事项,请查看文档。
Result of call to 'done(on:_:)' is unused
, Result of call to 'then(on:_:)' is unused
PromiseKit 特意避免使用 @discardableResult 标记,因为未使用的警告提醒你调用链中有错误没有处理。你可以选取以下的一个操作来解决:
- 添加 catch
- return 一个 promise (从而让调用者来负责处理错误)
- 使用 cauterize() 来关闭警告
显而易见,前两种方法要由于第三种方法。