【问题标题】:Swift equivalent of await Promise.allSwift 等价于 await Promise.all
【发布时间】:2021-09-10 17:41:38
【问题描述】:

我来自 JS,学习 Swift 来构建一个 iOS 原生版本的应用程序。

在 JS 中,我一直使用以下模式:

...
async function doAyncFunction(item) {
  try {
    // do async call to fetch data using item
    return Promise.resolve(data);
  } catch (error) {
    return Promise.reject(error);
  }
}

const promises = items.map((item) => doAyncFunction(item));
const results = await Promise.all(promises);
...

我已经开始研究 PromiseKit,但我想知道 Swift 的方法是什么?

谢谢。

【问题讨论】:

  • 从 Swift 5.5 开始,您将能够使用提供非常相似功能的内置 Swift 并发(async-await 模式、AsyncSequence 等...)。等待的时候可以查看Combine framework

标签: swift async-await promise promisekit


【解决方案1】:

您可以查看 PromiseQ 它是 Swift 的 javascript 样式承诺。它实现了所有 javascript 的 Promise 功能:resolve/rejectthenfinallyfetch 等,并附加了一些附加功能:suspend/resumecancelretrytimeout 等。

它还支持allraceany 例如:

// Fetch avatars of first 30 GitHub users.

struct User : Codable {
    let login: String
    let avatar_url: String
}

async {
    let response = try fetch("https://api.github.com/users").await()
    guard response.ok else {
        throw response.statusCodeDescription
    }

    guard let data = response.data else {
        throw "No data"
    }

    let users = try JSONDecoder().decode([User].self, from: data)

    let images =
        try Promise.all(
            users
            .map { $0.avatar_url }
            .map { fetch($0) }
        ).await()
        .compactMap { $0.data }
        .compactMap { UIImage(data: $0) }

    async(.main) {
        print(images.count)
    }
}
.catch { error in
    print(error.localizedDescription)
}

Swift 的并发如 Dispatch queues、Combine 和最新的 async\await (Swift 5.5) 与 javascript Promises 不同,你找不到很多你以前使用的方便的方法。

【讨论】:

    【解决方案2】:

    Xcode 13 中即将推出的 Swift 5.5(此时仍处于测试阶段)使用非常相似的 async-await 模式。见The Swift Programming Language: Concurrency

    在此期间,不幸的是,有许多令人眼花缭乱的替代方案。例如,有各种各样的第三方 Promise/Future 框架。或者是声明性的Combine 框架,它是在几年前随着SwiftUI 的非命令模式的出现而推出的。

    所有这些都说了,您将在 Swift 代码中看到的最常见模式是使用转义“closures”,它们实际上是作为参数传递给函数的代码单元,并且函数在异步任务完成时调用。在该模式中,您不需要await,而只是指定异步任务完成时要执行的操作。例如,在这个函数中,它有一个名为completion 的参数,这是一个在异步任务完成时调用的闭包:

    func fetch(using value: Int, completion: @escaping (Result<Foo, Error>) -> Void) {
       let url = …
       let task = URLSession.shared.dataTask(with: url) { data, response, error in
           // handle errors, if any, e.g.:
    
           if let error == error else {
               completion(.failure(error))
               return
           }
     
           // parse data into `Foo` here, and when done, call the `completion closure:
    
           …
    
           completion(.success(foo))
        }
        task.resume()
    }
    

    然后你会这样称呼它:

    fetch(using: 42, completion: { result in
        // this is called when the fetch finishes
       
        switch result {
        case .failure(let error): // do something with `error`
        case .success(let foo):   // do something with `foo`
        }
    })
    
    // note, execution will continue here, and the above closure will
    // be called later, so do not try to use `foo` here
    

    或者,使用更简洁的“尾随闭包”语法:

    fetch(using: 42) { result in
        // this is called when the fetch finishes
       
        switch result {
        case .failure(let error): // do something with `error`
        case .success(let foo):   // do something with `foo`
        }
    }
    
    // note, execution will continue here, and the above closure will
    // be called later, so do not try to use `foo` here
    

    如果您想在一系列通话结束时收到通知,您可以使用DispatchGroup,例如

    let group = DispatchGroup()
    for value in values {
        group.enter()
        fetch(using: value) { result in
            // do something with result
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        // this is called when every `enter` call is matched up with a `leave` Call
    }
    

    您是否坚持使用非常熟悉的async-await 模式的 Swift 5.5 测试版,使用第三方的 future/promise 库,使用 Combine,还是使用传统的闭包 -基于模式,如上所示。

    至少,我建议您熟悉后一种模式,因为它是目前 Swift 中的主要技术。但请放心,熟悉的 async-await 模式即将推出,因此如果您愿意等待它完成测试过程(或加入该测试过程),请检查一下。

    【讨论】:

      【解决方案3】:

      我在这里用一个解决方案回答自己,使用 PromiseKit,以防它可能对某人有所帮助。

      以下显然不是一个完整的实现,但它展示了如何实现该模式。

      func doManyAsyncRequests(userIds: [String], accessToken: String) -> Promise<Void> {
        Promise { seal in
          let promises = spotifyUserIds.map {
            doSingleAsyncRequest(userId: $0.id, accessToken: accessToken) // this function returns a promise
          }
          when(fulfilled: promises).done { results in
            print("Results: \(results)")
            // process results
          }.catch { error in
            print("\(error)")
            // handle error
          }
        }
      }
      

      【讨论】:

        【解决方案4】:

        目前有一个最接近 async/await 的优秀框架,它是 SwiftCoroutine https://github.com/belozierov/SwiftCoroutine(比 promiseKit 好得多,我测试了 2..)

        Swift 协程与您的示例:

        func doFutureFunction() -> CoFuture<Int> {
          CoFuture { promise in
              myRequest { error, data in
                  if let error = error {
                      promise(.failure(error))
                  } else {
                      promise(.success(data))
                  }
              }
          }
        }
        
        let futures = items.map { item in doFutureFunction(item) } // [CoFuture<Int>]
        
        DispatchQueue.main.startCoroutine {
            let results = promises.compactMap { try? $0.await() } // [Int]
        }
        

        相当于

        consts value = await future.value
        consts value1 = await future.value
        consts value2 = await future.value
        console.log("Value " + value + ", value1 " + value1 + ", value2 " + value2)
        

        DispatchQueue.main.startCoroutine {
            do {
               let value = try future.await()
               let value1 = try future.await()
               let value2 = try future.await()
               print("Value \(value), value1 \(value1), value2 \(value2)")
            } catch {
               print(error.localizedDescription)
            }
        }
        

        在等待 swift 5.5 和 Apple 的官方 async/await 时

        【讨论】:

          【解决方案5】:

          使用上述内置的Combine 框架,您有多种选择。你可能想要的是Publishers.Merge

          let publishers = ... // multiple functions that implement the Publisher protocol
          let combined = Publishers.MergeMany(publishers) 
          

          在设置发布者数量时,MergeMany 的替代方案是 MergeMerge3Merge4Merge8。如果输出数量可变,请使用MergeMany

          其他选项包括发布者自己的merge

          let publisher1 = ...
          let publisher2 = ...
          
          publisher1.merge(publisher2)
          

          CombineLatest 或者,对于立即完成的发布者,Zip 可用于在一切完成后接收元组:

          let publisher1 = ...
          let publisher2 = ...
          
          Publishers.CombineLatest(publisher1, publisher2)
          

          【讨论】:

            猜你喜欢
            • 2013-05-08
            • 1970-01-01
            • 2020-09-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-11-28
            相关资源
            最近更新 更多