Swift 等价于 await Promise.all

问题描述

我来自 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 的做法是什么?

谢谢。

解决方法

目前有一个很棒的框架,最接近 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 和官方的 async/await 时

,

使用上述内置 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)
,

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

在此期间,不幸的是,有大量令人眼花缭乱的替代方案。例如,有多种第三方承诺/未来框架。或者还有声明式 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,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 模式即将推出,因此如果您愿意等待它完成测试过程(或加入该测试过程),请查看.

,

您可以查看 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 不同,您找不到以前使用的许多方便的方法。

,

我在这里用一个解决方案来回答自己,使用 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
    }
  }
}