简体   繁体   English

错误处理 - 异步调用

[英]Error Handling - Async Call

I am creating a framework for web services used in my project. 我正在为我的项目中使用的Web服务创建一个框架。 I have uploaded template in GitHub. 我在GitHub上传了模板。 https://github.com/vivinjeganathan/ErrorHandling https://github.com/vivinjeganathan/ErrorHandling

It has various layers. 它有各种层次。 Layer 1 for validation. 第1层用于验证。 Layer 2 for formation of request. 第2层用于形成请求。 Layer 3 for the actual network call. 第3层用于实际网络呼叫。

View Controller <----> Layer 1 <---> Layer 2 <---> Layer 3 查看控制器<---->第1层<--->第2层<--->第3层

Data flows between layers through closures, if error happens at any layer it needs to be gracefully passed to the ViewController. 数据通过闭包在层之间流动,如果在任何层发生错误,则需要将它优雅地传递给ViewController。

I have referred to this link for error handling in async calls - http://appventure.me/2015/06/19/swift-try-catch-asynchronous-closures/ Created a branch in the same repo - name - ErrorHandling-Method1. 我在异步调用中引用了此链接以进行错误处理 - http://appventure.me/2015/06/19/swift-try-catch-asynchronous-closures/在同一个repo中创建了一个分支 - ErrorHandling-Method1 。

I was able to transfer error from layer 3 to layer 2(Single Level - Returning response through functions in closures - as mentioned in the link). 我能够将错误从第3层传输到第2层(单级 - 通过闭包中的函数返回响应 - 如链接中所述)。 But face difficulties in transferring back across multi layers. 但是面对跨多层转移的困难。

Can anyone assist with the sample application provided in public GitHub? 任何人都可以协助公共GitHub提供的示例应用程序吗?

Personally I would use notifications passing the NSError as the object of the notification in the layers and observe the notification in the view controller. 我个人会使用通知NSError作为层中通知对象的通知,并观察视图控制器中的通知。

In the layers: 在图层中:

NSNotificationCenter.defaultCenter().postNotificationName("ErrorEncounteredNotification", object: error)

In the view controller 在视图控制器中

NSNotificationCenter.defaultCenter().addObserver(self, selector: "errorEncountered:", name: "ErrorEncounteredNotification", object: nil)

the selector method: 选择器方法:

func errorEncountered(notification: NSNotification!) {
    let error: NSError! = notification.object as! NSError
    NSLog("error: \(error)")
}

First of all, I don't think it's necessary to stack the layers the way you did, for example, by adding the validation functionality as a layer you are increasing coupling making that layer dependant of the layers below (parsing, networking, etc.), instead, why don't you separate validation to make it only dependant of the data?: 首先,我认为没有必要像你那样堆叠层,例如,通过添加验证功能作为一个层,你正在增加耦合,使得该层依赖于下面的层(解析,网络等)。 ),相反,为什么不单独验证使其仅依赖于数据?:

class ViewController: UIViewController {

   var validator = InputValidator()

   override func viewDidLoad() {
      super.viewDidLoad()

      do {
         try validator.validateInput("INPUT")
         try Fetcher.requestDataWithParams("INPUT")
      }
      catch {
         handleError(error)
      }        
   }
}

Now the validation functionality is not dependant of the other layers, so communication would flow like this: 现在验证功能不依赖于其他层,因此通信将如下所示:

View Controller <---> ParsingLayer <---> NetworkingLayer 查看控制器<---> ParsingLayer <---> NetworkingLayer

I did rename the layers but they are not necessarily have to be like this, you can add or remove layers. 我确实重命名了图层,但它们不一定必须像这样,你可以添加或删除图层。

I think is going to be kind of complicated if I try to explain my approach, so I'm going to give an example using the previous layers, first the bottom layer: 如果我尝试解释我的方法,我认为会有点复杂,所以我将使用前面的图层给出一个例子,首先是底层:

class NetworkingLayer {
   class func requestData(params: AnyObject, completion: (getResult: () throw -> AnyObject) -> Void) -> Void {
      session.dataTaskWithURL(url) { (data, urlResponse, var error) in
         if let error = error {
            completion(getResult: { throw error })
         } else {
            completion(getResult: { return data })
         }
      }
   }
}

I have omitted some sections of code, but the idea is to do any necessary step to make the layer work (create session, etc.) and to always communicate back through the completion closure; 我省略了一些代码部分,但想法是做任何必要的步骤来使图层工作(创建会话等)并始终通过完成闭包进行通信; a layer on top would look like this: 顶部的图层如下所示:

class ParsingLayer {
   class func requestObject(params: AnyObject, completion: (getObject: () throw -> CustomObject) -> Void) -> Void {
      NetworkingLayer.requestData(params, completion: { (getResult) -> Void in
         do {
            let data = try getResult()
            let object = try self.parseData(data)
            completion(getObject: { return object })
         }
         catch {
            completion(getObject: { throw error })
         }
      })
   } 
} 

Notice that the completion closures are not the same, since every layer adds functionality, the returned object can change, also notice that the code inside the do statement can fail in two ways, first if the network call fails and then if the data from the networking layer cannot be parsed; 请注意,完成闭包不一样,因为每个层都添加了功能,返回的对象可以更改,还注意到do语句中的代码可能以两种方式失败,首先是网络调用失败,然后是来自网络层无法解析; again the communication to the layer on top is always done through the completion closure. 再次与顶层的通信总是通过完成闭包完成。

Finally the ViewController can call the next layer using the closure expected by the Parsing layer in this case, and is able to handle errors originated in any layer: 最后,在这种情况下,ViewController可以使用解析层所期望的闭包来调用下一层,并且能够处理源自任何层的错误:

override func viewDidLoad() {
   super.viewDidLoad()
   do {
      try validator.validateInput("INPUT")
      try ParsingLayer.requestObject("INPUT", completion: { (getObject) in
      do {
         let object = try getObject()
         try self.validator.validateOutput(object)
         print(object)
      }
      catch {
         self.handleError(error)
      }
   })
   catch {
      handleError(error)
   }        
}

Notice that there is a do catch inside the completion closure, this is necessary since the call is made asynchronously, now that the response has gone through all the layers and have actually change to be of a more specialised type you can even validate the result without having the necessity to make a layer for the validation functionality. 请注意,在完成闭包中有一个do catch ,这是必要的,因为调用是异步调用的,现在响应已遍历所有层,并且实际上已更改为更专用的类型,您甚至可以验证结果有必要为验证功能创建一个层。

Hope it helps. 希望能帮助到你。

Why declare your method throws if you never throw or even try to catch? 如果你从不抛出甚至试图捕获,为什么声明你的方法抛出? You could throw the errors using the throwable declaration through all the layers, and even change the throwable type at each level. 您可以使用throwable声明在所有层中抛出错误,甚至可以在每个级别更改throwable类型。

UPDATE: Didnt think of throwing dont work in async operations. 更新:没想到抛弃不在异步操作中工作。 Using NSNotification is one good route, or you could take a look at RXSwift or similar to solve it too. 使用NSNotification是一条很好的路线,或者你可以看一下RXSwift或类似的解决方案。 My personal recommendation would be to use RxSwift. 我个人的建议是使用RxSwift。 This keeps you out of callback hell, which you are currently travelling down into. 这可以让你摆脱目前正在进行的回调地狱。

You correctly identified a nasty problem with error handling in asynchronous code. 您正确地发现了异步代码中的错误处理问题。

It seems to be easy with synchronous functions - which just return an error code, or have an extra error parameter, or use the new Swift throws syntax. 使用同步函数似乎很容易 - 它只返回错误代码,或者有一个额外的错误参数,或者使用新的Swift throws语法。 Here is an synchronous function: 这是一个同步功能:

func computeSome() throws -> Some

And this is a viable function signature for an asynchronous function: 这是异步函数的可行函数签名:

func computeSomeAsync(completion: (Some?, NSError?) -> ())

The asynchronous function returns Void and does not throw. 异步函数返回Void并且不抛出。 If it fails, it calls its completion function with the error parameter set. 如果失败,则使用错误参数集调用其完成函数。

However, completion handlers become quickly cumbersome, especially in nested code. 但是,完成处理程序变得非常麻烦,尤其是在嵌套代码中。

The solution is to use a Future : 解决方案是使用Future

func computeSomeAsync() -> Future<Some>

This function is asynchronous and does not throw - and returns a Future . 此函数是异步的,不会抛出 - 并返回Future So, what's a future? 那么,未来是什么?

Well a future represents the eventual result of an asynchronous function. 未来代表了异步函数的最终结果。 When you call the asynchronous function, it immediately returns and you get a placeholder for the result. 当您调用异步函数时,它会立即返回并获得结果的占位符 This, called a future , will be eventually completed by the underlying background task that computes the value. 这称为未来 ,最终将由计算值的基础后台任务完成

When the underlying task eventually succeeded, this future contains the computed value of the function. 当底层任务最终成功时,此future将包含函数的计算值。 When it failed it will contain the error. 失败时,它将包含错误。

Depending on the actual implementation and the API of a Future Library, you can obtain the result by registering continuations : 根据实际实现和Future Library的API,您可以通过注册continuation来获得结果:

let future = computeSomeAsync()

future.onSuccess { value in
    print("Value: \(value)")
}


future.onFailure { error in
    print("Error: \(error)")
}

It may look weird at first, but you can do awesome things with futures: 一开始可能看起来很奇怪,但你可以用期货来做很棒的事情:

fetchUser(id).flatMap { user in
    fetchProfileImage(user.profileImageUrl).flatMap { image in
        cacheImage(image)
    }
}
.onFailure { error in
    print("Something went wrong: \(error)")
}

The above statement is asynchronous - as well as function fetchUser , fetchProfileImage and cacheImage . 上面的语句是异步的 - 以及函数fetchUserfetchProfileImagecacheImage Error handling included. 包括错误处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM