【问题标题】:How to ensure two asynchronous tasks that 'start' together are completed before running another?如何确保两个“开始”的异步任务在运行另一个任务之前完成?
【发布时间】:2017-07-17 23:21:05
【问题描述】:

我正在设置一个应用程序,它利用promiseKit 作为对异步任务进行排序的一种方式。我目前有一个设置,可确保两个异步函数(称为promises)按顺序完成(我们称它们为 1 和 2),并且另一组函数(3 和 4)按顺序完成。大致:

import PromiseKit
override func viewDidAppear(_ animated: Bool) {


        firstly{
            self.promiseOne() //promise #1 happening first (in relation to # 1 and #2)
            }.then{_ -> Promise<[String]> in
                self.promiseTwo()//promise #2 starting after 1 has completed
            }
            .catch{ error in
                print(error)
        }
        firstly{
            self.promiseThree()//Promise #3 happening first (in relation to #3 and #4)
            }.then{_ -> Promise<[String]> in
                self.promiseFour()//Promise #4 starting after #3 has completed
            }.
            .catch{ error in
                print(error)
        }
}

每个firstly 通过确保第一个在第二个可以启动之前完成,确保其中的功能顺序。使用两个单独的firstly 确保 1 在 2 之前完成,3 在 4 之前完成,(重要的是)1 和 3 大约在同一时间开始(在 @987654327 开始时) @)。这是故意这样做的,因为 1 和 3 彼此不相关,并且可以同时启动而不会出现任何问题(2 和 4 也是如此)。问题是有第五个承诺,我们称之为promiseFive,它必须 2 和4 完成后运行。我可以只链接一个firstly 以确保顺序为 1、2、3、4、5,但由于 1/2 和 3/4 的顺序不相关,以这种方式链接它们会浪费时间。 我不知道如何设置它,以便 promiseFive 仅在完成 ​​2 和 4 时运行。我曾想过在 2 和 4 的末尾都有布尔检查函数调用,确保另一个 @987654331 @ 已完成然后调用 promiseFive 但是,由于它们 开始 异步(1/2 和 3/4),promiseFive 可能会同时被两者调用这种方法,显然会产生问题。解决这个问题的最佳方法是什么?

【问题讨论】:

  • @rmaddy - 如果他只是在 GCD 池和异步 API 的浅端闲逛,那么组是解决这个问题的好方法。但鉴于他使用的是 Promise,在这种环境中引入组是错误的,恕我直言......
  • @Rob 我不熟悉承诺工具包。老实说,当我发表评论时,我掩盖了这一点。

标签: swift asynchronous promisekit


【解决方案1】:

您可以在多个其他承诺完成后使用whenjoin 开始某事。不同之处在于他们如何处理失败的承诺。听起来你想加入。这是一个具体但简单的示例。

第一个代码块是一个示例,说明如何创建 2 个 Promise 链,然后等待它们都完成,然后再开始下一个任务。正在完成的实际工作被抽象为一些功能。关注这段代码,因为它包含您需要的所有概念信息。

片段

let chain1 = firstly(execute: { () -> (Promise<String>, Promise<String>) in
    let secondPieceOfInformation = "otherInfo" // This static data is for demonstration only

    // Pass 2 promises, now the next `then` block will be called when both are fulfilled
    // Promise initialized with values are already fulfilled, so the effect is identical
    // to just returning the single promise, you can do a tuple of up to 5 promises/values
    return (fetchUserData(), Promise(value: secondPieceOfInformation))

}).then { (result: String, secondResult: String) -> Promise<String> in
    self.fetchUpdatedUserImage()
}

let chain2 = firstly {
    fetchNewsFeed() //This promise returns an array

}.then { (result: [String : Any]) -> Promise<String> in

    for (key, value) in result {
        print("\(key) \(value)")
    }
    // now `result` is a collection
    return self.fetchFeedItemHeroImages()
}

join(chain1, chain2).always {
    // You can use `always` if you don't care about the earlier values

    let methodFinish = Date()
    let executionTime = methodFinish.timeIntervalSince(self.methodStart)
    print(String(format: "All promises finished %.2f seconds later", executionTime))
}

PromiseKit 使用 closures 来提供它的 API。闭包的作用域就像if 语句一样。如果您在 if 语句的范围内定义一个值,那么您将无法在该范围之外访问它。

您有多种选择将多条数据传递到下一个then 块。

  1. 使用与所有 Promise 共享作用域的变量(您可能希望避免这种情况,因为它不利于您管理异步数据传播的流程)
  2. 使用自定义数据类型来保存两个(或更多)值。这可以是元组、结构、类或枚举。
  3. 使用集合(如字典),例如chain2
  4. 返回一个 promise 元组,示例包含在 chain1

在选择方法时,您需要做出最佳判断。

完整代码

import UIKit
import PromiseKit

class ViewController: UIViewController {

    let methodStart = Date()

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        <<Insert The Other Code Snippet Here To Complete The Code>>

        // I'll also mention that `join` is being deprecated in PromiseKit
        // It provides `when(resolved:)`, which acts just like `join` and
        // `when(fulfilled:)` which fails as soon as any of the promises fail
        when(resolved: chain1, chain2).then { (results) -> Promise<String> in
            for case .fulfilled(let value) in results {
                // These promises succeeded, and the values will be what is return from
                // the last promises in chain1 and chain2
                print("Promise value is: \(value)")
            }

            for case .rejected(let error) in results {
                // These promises failed
                print("Promise value is: \(error)")
            }

            return Promise(value: "finished")
            }.catch { error in
                // With the caveat that `when` never rejects
        }
    }

    func fetchUserData() -> Promise<String> {
        let promise = Promise<String> { (fulfill, reject) in

            // These dispatch queue delays are standins for your long-running asynchronous tasks
            // They might be network calls, or batch file processing, etc
            // So, they're just here to provide a concise, illustrative, working example
            DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
                let methodFinish = Date()
                let executionTime = methodFinish.timeIntervalSince(self.methodStart)

                print(String(format: "promise1 %.2f seconds later", executionTime))
                fulfill("promise1")
            }
        }

        return promise
    }

    func fetchUpdatedUserImage() -> Promise<String> {
        let promise = Promise<String> { (fulfill, reject) in
            DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
                let methodFinish = Date()
                let executionTime = methodFinish.timeIntervalSince(self.methodStart)

                print(String(format: "promise2 %.2f seconds later", executionTime))
                fulfill("promise2")
            }
        }

        return promise
    }

    func fetchNewsFeed() -> Promise<[String : Any]> {
        let promise = Promise<[String : Any]> { (fulfill, reject) in
            DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
                let methodFinish = Date()
                let executionTime = methodFinish.timeIntervalSince(self.methodStart)

                print(String(format: "promise3 %.2f seconds later", executionTime))
                fulfill(["key1" : Date(),
                         "array" : ["my", "array"]])
            }
        }

        return promise
    }

    func fetchFeedItemHeroImages() -> Promise<String> {
        let promise = Promise<String> { (fulfill, reject) in
            DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
                let methodFinish = Date()
                let executionTime = methodFinish.timeIntervalSince(self.methodStart)

                print(String(format: "promise4 %.2f seconds later", executionTime))
                fulfill("promise4")
            }
        }

        return promise
    }
}

输出

promise3 1.05 秒后
数组 [“我的”,“数组”]
key1 2017-07-18 13:52:06 +0000
promise1 2.04 秒后
promise4 3.22 秒后
promise2 4.04 秒后
所有承诺在 4.04 秒后完成
承诺值是:promise2
承诺值是:promise4

【讨论】:

  • DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) 有必要吗?如果有,为什么?
  • 另外,由于某种原因,我无法访问在 Promise 闭包之外创建的变量。我可以从 promise1 的第一部分传递一个到下一部分,但我需要传递 2 个它不允许我做的变量。无论如何围绕这个?
  • @AlekPiasecki 我更新了我的帖子,希望能解决您的后续问题。我的回答中有很多信息,请仔细阅读并实际运行代码以熟悉它在做什么。我还建议仔细阅读 PromiseKit 源代码,因为那里有一些很好的文档。如果您有任何进一步的跟进,请告诉我。
  • 我的问题非常简单,我实际上在我的应用程序中使用了一个三承诺链和一个六承诺链。我在将您的答案(非常好)直接应用于我使用的承诺时遇到了一些困难。我设法对其进行了变体,导致第一条和第二条链的完成之间延迟了约 3.5 秒。这对我来说似乎有点高,因为使用我在stackoverflow.com/questions/45202663/… 中说明的方法只会产生约 0.3 秒的延迟。无论如何,我们可以更深入地讨论这个问题吗?
【解决方案2】:

细节有点取决于这些各种promise的类型,但你基本上可以将1后2的promise作为一个promise返回,将3后4的promise作为另一个返回,然后使用@987654321 @ 以相对于彼此同时运行这两个承诺序列,但仍然享受这些序列中的连续行为。例如:

let firstTwo = promiseOne().then { something1 in
    self.promiseTwo(something1)
}

let secondTwo = promiseThree().then { something2 in
    self.promiseFour(something2)
}

when(fulfilled: [firstTwo, secondTwo]).then { results in
    os_log("all done: %@", results)
}.catch { error in
    os_log("some error: %@", error.localizedDescription)
}

在这种情况下,您试图保持问题相当笼统可能会使您更难了解如何在您的案例中应用此答案。因此,如果您遇到问题,您可能希望更具体地了解这四个 Promise 正在做什么以及它们相互传递的内容(因为这种将结果从一个传递到另一个是 Promise 的优雅特性之一)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-17
    • 1970-01-01
    • 2013-03-22
    • 1970-01-01
    • 2015-07-18
    相关资源
    最近更新 更多