ActionScript处理异步事件(二)

By:kuwamoto.org Date:2009-04-26

作者: Sho Kuwamoto
译者: Dreamer

在上一篇文章种,我讲了为什么异步编程会令人费解的几个原因。现在,我们来看一下问题的几个简化方法。

使用闭包(closures)

第一项任务就是闭包.Ely Greenfield(Flex架构师之一)向我展示了使用闭包来隐藏复杂的参数传递的一种很酷的方法。

在上一篇文章种,我们通过呼叫对象(call object)将代码中某部分的参数传递到下一部分,像这样:

  1. public function getAlbumInfo(albumId: int) : void   
  2. {   
  3.   // Initiate the call.   
  4.   myService.request = { type: "album", albumId: albumId };   
  5.   var call : Object = myService.send();   
  6.   // Save the albumId for use later.   
  7.   call.albumId = albumId;   
  8.   // Wire up the handler.   
  9.   call.handler = getAlbumInfoResult;   
  10. }   
  11. public function getAlbumInfoResult(event: Event) : void   
  12. {   
  13.   // Deal with the results of the previous call.   
  14.   var artistId: int = event.result.album.artistId;   
  15.   myAlbumList.addAlbum(event.call.albumId, event.result.album);   
  16.   // Initiate the next call.   
  17.   myService.request = { type: "artist", artistId : artistId };  
  18.   var call = myService.send();   
  19.   // Save the artistId for use later.   
  20.   call.artistId = artistId;   
  21.   // Wire up the handler.   
  22.   call.handler = getArtistInfoResult;   
  23. }   
  24. public function getArtistInfoResult(event: Event) : void   
  25. {   
  26.   // Handle the results of the second call.   
  27.   myArtistList.addArtist(event.call.artistId, event.result.artist);   
  28. }  


如果你使用闭包来处理参数传递,代码就会变成这样:

  1. public function getAlbumInfo(albumId: int) : void   
  2. {   
  3.   var call: Object;   
  4.   // Initiate the first call.   
  5.   myService.request = { type: "album", albumId: albumId };   
  6.   call = myService.send();   
  7.   call.handler = function (event: Event)   
  8.   {   
  9.     // Handle the results of the first call.   
  10.     var artistId: int = event.result.album.artistId;   
  11.     // Notice we can use albumId here directly.   
  12.     myAlbumList.addAlbum(albumId, event.result.album);   
  13.     // Initiate the second call.   
  14.     myService.request = { type: "artist", artistId: artistId };   
  15.     call = myService.send();   
  16.     call.handler = function (event: Event)   
  17.     {   
  18.       // Handle the results of the second call.   
  19.       myArtistList.addArtist(artistId, event.result.artist);   
  20.     }   
  21.   }  
  22. }   


这有一点复杂,所以让我们分析一下流程
  1. 设置myService.request。
  2. 发出第一个请求。
  3. 回叫函数(callback)被保存在呼叫对象中并等待http请求。
  4. 最终,结果处理函数被调用。
  5. 查询呼叫对象中的回叫函数,并呼叫它。
  6. 这会导致第一个闭包被调用。
  7. 在第一个闭包中,我可以从外部函数中访问albumId参数,因为闭包就是这样工作的。
  8. 在这里面,第二个http请求被设定。
  9. 一个新的回叫函数(callback)被存储在一个新的呼叫对象中。
  10.最终,结果处理器(handler)被调用。
  11.查询呼叫对象中的回叫函数,并呼叫它。
  12.这会导致第二个闭包被调用。
  
  这种方法为什么很好?在Ely看来, “如果你从另一个角度看,它看起来就像你没有在进行异步编程。”你不需要注册异步事件处理器,并且由于有了神奇的闭包你不需要做一些很复杂的工作来传递参数。

初试清理代码(使用延迟(Deferreds))

清 理要做的第一件事情就是熟悉回叫函数是如何注册的。是的,一旦结果返回系统某处的代码就会呼叫处理器(handler),但是只是我编写的一些代码,它并 不是通过一个类来管理的。此外,如果我们想要添加一个另外的处理器(handler),或者当我们想要为呼叫失败添加一个回叫函数的时候,我们不知道会发 生什么。

Python语言中的Twisted网络引擎又一个叫做“deferred”的概念,那就是一个回叫处理器对象。

在Twisted中,所有的异步呼叫返回一个型别为“Deferred”的对象。一个Deferred对象除了有其它属性外, 还有一些回叫函数和当错误发生时调用的 “错误回馈(errback)”。

Deferred方法的一个好处就是它可以使用一致的方法来处理所有异步操作。为了保证一致性,你应该希望它被编写在framework里面。我做了下面的好东西,自己扩展了framework。

在我的首次尝试中,我创建了一个IDeferred 接口和一个Deferred型别。我决定使用“result”和“fault”这两个名字来代替“callback”和“errback”。

我添加了用来添加和删除处理器的方法, 就像在Python中一样。 我修改了send()方法, 使它返回一个IDeferred实例而不是呼叫对象。 这允许你使用下面的代码:

  1. public function foo() : void   
  2. {   
  3.   var d : IDeferred = myService.send();   
  4.   d.addResultHandler(blah);   
  5.   d.addResultHandler(anotherHandler);   
  6.   d.addFaultHandler(blah2);   
  7. }   


这对于用函数明确描述的结果处理器工作地很好, 但是对上面复杂的闭包工作的不是那么好。

  1. public function ugly() : void   
  2. {   
  3.   var d : IDeferred = myService.send();   
  4.   d.addResultHandler(   
  5.     function(event: Event) : void   
  6.     {   
  7.       // do something   
  8.     }   
  9.   );   
  10. }  


添加 setters

为了使语法更清晰,我又添加了setters。通过为结果处理器创建一个叫做“onResult”的setter并为错误处理器创建一个叫做“onFault”的setter,你现在可以使用赋值了,这是用来处理闭包的很自然的方法。

  1. public function pretty() : void   
  2. {   
  3.   var d : IDeferred = myService.send();   
  4.   d.onResult = function(event: Event) : void   
  5.   {   
  6.     // do something   
  7.   }   
  8.   d.onFault = function(event: Event) : void   
  9.   {   
  10.     // do something else   
  11.   }  
  12. }   


这个操作清除了所有既有的结果处理器并设定结果处理器为一个新的函数。 因为ActionScript3没有重载的 +=运算符,所以不能使用相似的记法以一种自然的方式来表达 “添加处理器”。

隐藏 deferred 的概念

理想状态下,人们不需要指导Deferred除非他们确实需要(比如,如果你需要将deferred从一个函数传递到另一个函数)。一种隐藏Deferred使用的方法是在不把Deferred存储在一个本地变量中的前提下把send得到的结果链接起来。

  1. public function tricky() : void   
  2. {   
  3.   myService.send().onResult = function(event: Event) : void   
  4.   {   
  5.     // do something   
  6.   }   
  7. }   


当然,使用这种方法没有办法附加一个错误处理器。最终我在我自己的HTTPService中定义了一个存储在最近的deferred的一个属性,我把它叫做“lastCall” , 这个名字从某些方面来看比较清除, 从其他方面来看则比较迷惑。

如果你想到一个更好的名字,我会很高兴地聆听。下面是现在的代码:

  1. public function lessTricky() : void   
  2. {   
  3.   myService.send();   
  4.   myService.lastCall.onResult = function(event: Event) : void   
  5.   {   
  6.     // do something   
  7.   }   
  8.   myService.lastCall.onFault = function(event: Event) : void   
  9.   {   
  10.     // do something else   
  11.   }   
  12. }   


回应“lastCall”而不是返回一个 Deferred

当 我添加了“lastCall”属性时,我最终经常使用这个词,并且撤消了以前对send()方法返回值的更改。 这有几分实用因素, 也有几分心理因素。 在实用方面,我发现某些异步方法(比如DataService.commit())返回定义了型别的对象,这意味着从一个子类很难截获到它们。在心理方 面,我对子类化一个类和截获返回值的含义时总是感到心虚。

最终结果

我曾暂时使用过这种方法,并且它工作的非常好。如果这个基础构造只是华而不实我仍然会感到烦心,但是直到现在,我发现它既快又好。这里有我最近写的一些代码的摘录。将控制流像这样内联(inline)地表现出来可以让我更容易理解什么东西正在继续。

  1. public function authenticate():void   
  2. {   
  3.   // Show the dialog.   
  4.   var loginDialog : LoginDialog = LoginDialog.show();   
  5.   loginDialog.onResult = function(event: Event) : void   
  6.   {   
  7.     // If the user was a new user,   
  8.     if (loginDialog.isNewUser())   
  9.     {   
  10.       // Create an account.   
  11.       userManager.create(loginDialog.username,loginDialog.password, loginDialog.email);   
  12.       userManager.lastCall.onResult = function(event:ResultEvent):void   
  13.       {   
  14.         // more logic here.   
  15.       }   
  16.       userManager.lastCall.onFail = function(event: ResultEvent):void   
  17.       {   
  18.         // add failure logic here.   
  19.       }   
  20.     }   
  21.     else   
  22.     {   
  23.       // Otherwise, authenticate existing account.   
  24.       userManager.authenticate(loginDialog.username,loginDialog.password);  
  25.       userManager.lastCall.onResult = function(event:ResultEvent):void   
  26.       {   
  27.         // more logic here.   
  28.       }   
  29.       userManager.lastCall.onFail = function(event: ResultEvent):void   
  30.       {   
  31.         // add failure logic here.   
  32.       }   
  33.     }   
  34.   }   

参与讨论去: 艾睿(Airia) 交朋友去:友吧推荐文章去:网站贴吧
企业招聘

北京中视力天文化传媒有限公司

公司简介:北京中视力天文化传媒有限公司,是CCTV.com央视国际网络有限公司投资的互联网研发团队。我们是天使的使者。。。

招聘FlashAS程序员详情点击

Copyright 2007-2008 51AS.com Extended in kingcms 鲁ICP备06001158号