PromiseKit 故障排除

全部文章

以下是对 PromiseKitREADME.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 不会结束

有几个潜在的原因:

  1. 检查异步任务,确保这些任务已经开始

这个是常见的原因。

例如,在使用 URLSession 时,如果没有使用我们已有的扩展(尽量使用我们扩展,因为我们知道所有可能出错的情况),那就检查一下是否在任务上调用了 resume。如果没有调用,任务就不会开始,所以 promise 也不会结束。

  1. 检查自定义的 Promise 初始化方法,看是否所有情况都被处理了?

查看上面的 “Pending Promise Deallocated” 问题。通常,有些情况没有处理,就会看到这个警告。你只有将所有情况都处理了,才不会看到这个警告。

有未处理的情况,就会导致 promise 无法结束。

  1. 确保执行 Promise 处理程序的队列没有被阻塞

如果执行处理程序的线程被阻塞了,那处理程序就无法被执行。通常,你看一下是否使用了我们提供的 wait() 方法。关于 wait() 使用建议和注意事项,请查看文档。

Result of call to 'done(on:_:)' is unused, Result of call to 'then(on:_:)' is unused

PromiseKit 特意避免使用 @discardableResult 标记,因为未使用的警告提醒你调用链中有错误没有处理。你可以选取以下的一个操作来解决:

  1. 添加 catch
  2. return 一个 promise (从而让调用者来负责处理错误)
  3. 使用 cauterize() 来关闭警告

显而易见,前两种方法要由于第三种方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349