如何在循环中链接 Swift 中的 Promise?

Posted

技术标签:

【中文标题】如何在循环中链接 Swift 中的 Promise?【英文标题】:How can I chain promises in Swift inside a loop? 【发布时间】:2016-10-21 03:44:43 【问题描述】:

我正在构建一个基于 Swift 的 ios 应用程序,该应用程序使用 PromiseKit 来处理 Promise(尽管如果它使我的问题更容易解决,我愿意切换 Promise 库)。有一段代码旨在处理有关覆盖文件的问题。

我的代码大致如下:

let fileList = [list, of, files, could, be, any, length, ...]

for file in fileList 
  if(fileAlreadyExists) 
    let overwrite = Promise<Bool>  fulfill, reject in
      let alert = UIAlertController(message: "Overwrite the file?")
      alert.addAction(UIAlertAction(title: "Yes", handler:  action in 
        fulfill(true)
      
      alert.addAction(UIAlertAction(title: "No", handler:  action in 
        fulfill(false)
      
     else 
      fulfill(true)
    
  

  overwrite.then  result -> Promise<Void> in
    Promise<Void>  fulfill, reject in
      if(result) 
        // Overwrite the file
       else 
        // Don't overwrite the file
      
  

但是,这并没有达到预期的效果; for 循环“完成”的速度与迭代列表的速度一样快,这意味着 UIAlertController 在尝试将一个问题覆盖在另一个问题上时会感到困惑。我想要的是链接承诺,以便只有在用户选择“是”或“否”(以及随后的“覆盖”或“不覆盖”代码已执行)后,for 的下一次迭代循环发生。本质上,我希望整个序列是连续的。

考虑到数组的长度不确定,我如何链接这些承诺?我觉得好像我错过了一些明显的东西。

编辑:以下答案之一建议递归。这听起来很合理,虽然我不确定如果列表变长对 Swift 的堆栈(这是在 iOS 应用程序内部)的影响。理想的情况是,如果有一个结构可以通过链接到 Promise 来更自然地做到这一点。

【问题讨论】:

这是一个类似问题的答案:***.com/a/60942269/3900270 【参考方案1】:

一种方法:创建一个获取剩余对象列表的函数。将其用作then 中的回调。在伪代码中:

function promptOverwrite(objects) 
    if (objects is empty)
        return
    let overwrite = [...]  // same as your code
    overwrite.then 
        do positive or negative action
        // Recur on the rest of the objects
        promptOverwrite(objects[1:])
    

现在,我们可能也有兴趣在不使用递归的情况下执行此操作,只是为了避免在我们有数以万计的 Promise 时破坏调用堆栈。 (假设 Promise 不需要用户交互,并且它们都在几毫秒的数量级上解析,因此场景是现实的)。

首先请注意,then 中的回调发生在闭包的上下文中,因此它不能与任何外部控制流进行交互,正如预期的那样。如果我们不想使用递归,我们可能不得不利用其他一些原生特性。

您首先使用 Promise 的原因大概是您(明智地)不想阻塞主线程。然后考虑分拆第二个线程,其唯一目的是编排这些承诺。如果您的库允许显式等待一个承诺,只需执行类似的操作

function promptOverwrite(objects) 
    spawn an NSThread with target _promptOverwriteInternal(objects)

function _promptOverwriteInternal(objects) 
    for obj in objects 
        let overwrite = [...]  // same as your code
        overwrite.then(...)    // same as your code
        overwrite.awaitCompletion()
    

如果你的 promises 库不允许你这样做,你可以通过使用锁来解决它:

function _promptOverwriteInternal(objects) 
    semaphore = createSemaphore(0)
    for obj in objects 
        let overwrite = [...]  // same as your code
        overwrite.then(...)    // same as your code
        overwrite.always 
            semaphore.release(1)
        
        semaphore.acquire(1)  // wait for completion
    

【讨论】:

为伪代码道歉——我不懂 Swift。但这是一个很好的问题,我认为解决方案与语言无关。 基本上,我认为您建议使用递归。如果列表很短,这是一个合理的想法。如果它变得更长,我有点担心 Swift 的影响(我也不够 Swift 专家知道这是否会产生严重的影响)。 @AndrewFerrier:是的,它是递归的。但我猜测 UIAlertController 会向用户发出提示。您肯定不是建议连续显示数千条提示,对吧? (澄清一下,也许:这里的递归不会影响性能。递归可能会产生影响的唯一原因是如果你破坏了调用堆栈,而调用堆栈通常处理数千个调用. 即使你因为框架和 GUI 事件有大约 100 次调用,并且每个递归调用由于 Promise 而触发了几帧,这仍然意味着你需要数百个提示来破坏堆栈。那时你有一个严重的用户体验问题!:) ) 好点 :) 现在有点理论,但我仍然想了解如果没有 UI 交互会发生这种情况。

以上是关于如何在循环中链接 Swift 中的 Promise?的主要内容,如果未能解决你的问题,请参考以下文章

每个循环中的有序 JavaScript Promise

链接多个 Promise,使用 map 循环数组并创建 mongoose 文档?

如何使用 PromiseKit 在 Swift 3 中手动触发 Promise

在循环中等待嵌套的 Promise

与 Promise.all() 中的解析相比,为啥在 while 循环中单独解析 Promise 数组时解析更慢? [复制]

如何在 Promise 中使用循环