2

我有一个递归的异步函数,它使用 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设置为返回nextPageTokenandfiles(id)字段,service是 的一个实例GTLRDriveService,并且FileIdCompletionHandler只是一个typealias

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

我已经阅读了如何将异步函数转换为承诺(如在这个线程中),但我不知道如何将其应用于递归的异步函数。我想我可以将整个方法包装为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

4

1 回答 1

1

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

我不知道 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,也不熟悉您的顶级承诺逻辑)。因此,在我的示例中,我创建了足以用于演示目的的模型对象。

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

于 2017-06-27T21:10:22.390 回答