将递归异步函数转换为 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 设置为返回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 意味着我将不得不使用错误处理代码来处理找不到文件的完全正常情况。这似乎更像是一种扭曲,而不是在找到文件的情况下测试承诺是否兑现。我宁愿留下错误处理代码来处理网络故障、权限问题等问题。 【参考方案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的主要内容,如果未能解决你的问题,请参考以下文章

将函数从递归转换为迭代

如何将递归函数转换为使用堆栈?

将混淆代码转换为递归函数

您如何将其转换为迭代函数而不是使用嵌套循环进行递归?

将嵌套数组转换为嵌套 html 块的递归 php 函数

将包含数学的字符串转换为整数的递归函数