将递归异步函数转换为 Promise
Posted
技术标签:
【中文标题】将递归异步函数转换为 Promise【英文标题】:Convert recursive async function to 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
设置为返回nextPageToken
和files(id)
字段,service
是GTLRDriveService
的一个实例,而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
意味着我将不得不使用错误处理代码来处理找不到文件的完全正常情况。这似乎更像是一种扭曲,而不是在找到文件的情况下测试承诺是否兑现。我宁愿留下错误处理代码来处理网络故障、权限问题等问题。
【参考方案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 目的的模型对象。
但希望这足以说明一组递归承诺背后的想法。
【讨论】:
以上是关于将递归异步函数转换为 Promise的主要内容,如果未能解决你的问题,请参考以下文章