【问题标题】:Convert recursive async function to promise将递归异步函数转换为 Promise
【发布时间】:2017-06-27 19:03:34
【问题描述】:

我有一个递归异步函数,它使用 REST api 和完成处理程序在 Google Drive 中查询文件 ID:

func queryForFileId(query: GTLRDriveQuery_FilesList,
                    handler: @escaping FileIdCompletionHandler) {
    service.executeQuery(query) { ticket, data, error in
        if let error = error {
            handler(nil, error)
        } else {
            let list = data as! GTLRDrive_FileList 
            if let pageToken = list.nextPageToken {
                query.pageToken = pageToken
                self.queryForFileId(query: query, handler: handler)
            } else if let id = list.files?.first?.identifier {
                handler(id, nil)
            } else {
                handler(nil, nil) // no file found
            }
        }
    }
}

这里,query 设置为返回nextPageTokenfiles(id) 字段,serviceGTLRDriveService 的一个实例,而FileIdCompletionHandler 只是一个typealias

typealias FileIdCompletionHandler = (String?, Error?) -> Void

我已经阅读了如何将异步函数转换为 Promise(如 this thread),但我不知道如何将其应用于递归的异步函数。我想我可以将整个方法包装为Promise

private func fileIdPromise(query: GTLRDriveQuery_FilesList) -> Promise<String?> {
    return Promise { fulfill, reject in
        queryForFileId(query: query) { id, error in
            if let error = error {
                reject(error)
            } else {
                fulfill(id)
            }
        }
    }
}

不过,我希望能更直接一点:

private func queryForFileId2(query: GTLRDriveQuery_FilesList) -> Promise<String?> {
    return Promise { fulfill, reject in
        service.executeQuery(query) { ticket, data, error in
            if let error = error {
                reject(error)
            } else {
                let list = data as! GTLRDrive_FileList
                if let pageToken = list.nextPageToken {
                    query.pageToken = pageToken
                    // WHAT DO I DO HERE?
                } else if let id = list.files?.first?.identifier {
                    fulfill(id)
                } else {
                    fulfill(nil) // no file found
                }
            }
        }
    }
}

那么:当我需要对executeQuery 进行另一个异步调用时,我该怎么办?

【问题讨论】:

  • 在找不到文件时使用reject 意味着我将不得不使用错误处理代码来处理找不到文件的完全正常情况。这似乎更像是一种扭曲,而不是在找到文件的情况下测试承诺是否兑现。我宁愿留下错误处理代码来处理网络故障、权限问题等问题。

标签: swift asynchronous recursion google-drive-api promisekit


【解决方案1】:

如果您想满足一组递归的承诺,那么您的“我在这里做什么?”行,您将创建一个新的promise.then {...}.else {...} 模式,在then 子句中调用fulfill,在else 子句中调用reject。显然,如果不需要递归调用,您只需直接fulfill

我不知道 Google API,而且你没有分享你的代码来满足对文件列表的承诺,所以我必须保持这个答案有点笼统:假设你有一些 retrieveTokens例程返回一个只有在所有文件的所有承诺都完成时才满足的承诺。让我们想象一下顶层调用是这样的:

retrieveTokens(for: files).then { tokens in
    print(tokens)
}.catch { error in
    print(error)
}

然后,您将拥有一个 retrieveTokens,它返回一个仅在满足单个文件的承诺时才满足的承诺。如果您正在处理一个简单的 File 对象数组,您可能会执行以下操作:

func retrieveTokens(for files: [File]) -> Promise<[Any]> {
    var fileGenerator = files.makeIterator()
    let generator = AnyIterator<Promise<Any>> {
        guard let file = fileGenerator.next() else { return nil }
        return self.retrieveToken(for: file)
    }

    return when(fulfilled: generator, concurrently: 1)
}

(我知道这不是你的样子,但我需要这个框架来显示我对下面你的问题的回答。但是将这个“返回给定级别的所有承诺”封装在一个函数中很有用,如它使您可以使递归代码保持一定的优雅,而无需重复代码。)

然后,为单个文件返回承诺的例程将查看是否需要返回一组递归承诺,并将其 fulfill 放入新递归创建的承诺的 then 子句中:

func retrieveToken(for file: File) -> Promise<Any> {
    return Promise<Any> { fulfill, reject in
        service.determineToken(for: file) { token, error in
            // if any error, reject

            guard let token = token, error == nil else {
                reject(error ?? FileError.someError)
                return
            }

            // if I don't have to make recursive call, `fulfill` immediately.
            // in my example, I'm going to see if there are subfiles, and if not, `fulfill` immediately.

            guard let subfiles = file.subfiles else {
                fulfill(token)
                return
            }

            // if I got here, there are subfiles and I'm going to start recursive set of promises

            self.retrieveTokens(for: subfiles).then { tokens in
                fulfill(tokens)
            }.catch { error in
                reject(error)
            }
        }
    }
}

再次,我知道以上内容不是您问题的直接答案(因为我不熟悉 Google Drive API,也不熟悉您的顶级承诺逻辑)。因此,在我的示例中,我创建了足以满足the demonstration 目的的模型对象。

但希望这足以说明一组递归承诺背后的想法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-15
    • 2016-01-06
    • 1970-01-01
    • 2015-10-24
    • 1970-01-01
    • 1970-01-01
    • 2018-06-05
    • 2018-12-30
    相关资源
    最近更新 更多