在使用firebase函数时逃离回调地狱



我对JavaScript的承诺很熟悉,但我是swift和Firebase的新手,我的团队里没有人可以问。我试过研究不同的方法来处理异步操作没有回调地狱,但我不明白如何使它与firebase函数一起工作。现在我正在使用一堆非常复杂的DispatchGroups和回调来让代码工作,但我真的想让它更干净,更易于维护。

我的代码看起来像这样(为了简洁,删除了错误处理):

var array = []
let dispatch = DispatchGroup()
db.collection("documentA").getDocuments() { (querySnapshot, err) in
for document in querySnapshot.documents 
dispatch.enter()
let dataA = document.data()["dataA"]
...
db.collection("documentB").documents(dataA).getDocuments() { (document, error) in 
let dataB = document.data()["dataB"]
...
db.collection("documentC").documents(dataB).getDocuments() { (document, error) in
let dataC = document.data()["dataC"]
let newObject = NewObject(dataA,dataB,dataC)
self.array.append(newObject)
dispatch.leave()

} 
}
}
//Use dispatch group to notify main queue to update tableView using contents of this array

有没有人有任何推荐的学习资源或建议我如何解决这个问题?

我建议您考虑引入RxFirebase库。Rx是清理嵌套闭包(回调地狱)的好方法。

在查看示例代码时,您必须了解的第一件事是只能做这么多。这个问题本身具有许多本质上的复杂性。此外,这段代码中还有很多可以拆分的内容。一旦你这样做了,你就可以把问题归结为以下内容:

import Curry // the Curry library is in Cocoapods
func example(db: Firestore) -> Observable<[NewObject]> {
let getObjects = curry(getData(db:collectionId:documentId:))(db)
let xs = getObjects("documentA")("dataA")
let xys = xs.flatMap { parentsAndChildren(fn: getObjects("documentB"), parent: { $0 }, xs: $0) }
let xyzs = xys.flatMap { parentsAndChildren(fn: getObjects("documentC"), parent: { $0.1 }, xs: $0) }
return xyzs.mapT { NewObject(dataA: $0.0.0, dataB: $0.0.1, dataC: $0.1) }
}

请注意,这里大量使用了高阶函数,因此理解这些函数会有很大帮助。如果你不想使用高阶函数,你可以使用类来代替,但是你必须编写的代码量至少会翻倍,而且你可能会遇到内存周期的问题。


要使上面的内容如此简单,需要一些支持代码:

func getData(db: Firestore, collectionId: String, documentId: String) -> Observable<[String]> {
return db.collection(collectionId).rx.getDocuments()
.map { getData(documentId: documentId, snapshot: $0) }
}
func parentsAndChildren<X>(fn: (String) -> Observable<[String]>, parent: (X) -> String, xs: [X]) -> Observable<[(X, String)]> {
Observable.combineLatest(xs.map { x in
fn(parent(x)).map { apply(x: x, ys: $0) }
})
.map { $0.flatMap { $0 } }
}
extension ObservableType {
func mapT<T, U>(_ transform: @escaping (T) -> U) -> Observable<[U]> where Element == [T] {
map { $0.map(transform) }
}
}

getData(db:collectionId:documentId:)函数请求与文档相关的集合中的字符串。

parentsAndChildren(fn:parent:xs:)函数可能是最复杂的。它将从通用X类型中提取适当的父对象,从服务器中获取子对象,并将它们卷成父和子的单维数组。例如,如果父元素是["a", "b"],则"a"是["w", "x"]和"b"= ["y", "z"],则该函数将输出[("a", "w"), ("a", "x"), ("b", "y"), ("b", "z"](包含在Observable中)

Observable.mapT(_:)函数允许我们映射对象的Observable数组并对它们做一些事情。当然,您可以直接使用xyzs.map { $0.map { NewObject(dataA: $0.0.0, dataB: $0.0.1, dataC: $0.1) } },但我觉得这样更简洁。

以下是上述函数的支持代码:

extension Reactive where Base: CollectionReference {
func getDocuments() -> Observable<QuerySnapshot> {
Observable.create { [base] observer in
base.getDocuments { snapshot, error in
if let snapshot = snapshot {
observer.onNext(snapshot)
observer.onCompleted()
}
else {
observer.onError(error ?? RxError.unknown)
}
}
return Disposables.create()
}
}
}
func getData(documentId: String, snapshot: QuerySnapshot) -> [String] {
snapshot.documents.compactMap { $0.data()[documentId] as? String }
}
func apply<X>(x: X, ys: [String]) -> [(X, String)] {
ys.map { (x, $0) }
}

Reactive.getDocuments()函数实际发出firebase请求。它的工作是将回调闭包转换为对象,以便您可以更轻松地处理它。这是RxFirebase应该为您提供的部分,但正如您所看到的,您自己编写它非常容易。

getData(documentId:snapshot:)函数只是从快照中提取适当的数据。

app(x:ys:)函数通过复制每个子元素的X值,将整个元素保存在一个单维数组中。


最后,请注意,上面的大多数函数都很容易独立地进行单元测试,而那些不是单元测试的函数则异常简单…

最新更新