【转】Unity中的协同程序-使用Promise进行封装(二)
原文:http://gad.qq.com/program/translateview/7170970
译者:王磊(未来的未来) 审校:崔国军(飞扬971)
在上一篇文章中,我们的注意力主要是放在Unity的协同程序的内部机制以及深入讨论了下它们是如何工作的。我们涉及了IEnumerator接口和迭代器模块来了解了下引擎是如何实现协同程序功能的。我们还列出了几个在实现中会遇到的问题以及在需要编写更复杂的协同程序的时候,可能会偶然发现的缺点。
在今天这篇文章中,我们将向你展示Promise到底是一个什么样子的概念以及它们最初起源自什么地方。
回调函数
Javascript API中的大多数异步操作在完成工作的时候会返回一个回调函数作为结果(用c#术语来表示的话:就是一个Action的委托)。你可以显式地为那些异步操作提供一个函数,但遵循这些异步代码的规则使用匿名函数实现回调会容易许多。理论上它似乎是我们的协同程序的一个像样的升级版本。让我们试着举出一个如何在C #程序中做这个事情的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
StartCoroutine(RequestData( (error, message) => { if (error) { Debug.Log( string .Format( "We got an error: {0}" , message)); } else { StartCoroutine(RequestSomeOtherData(message, (error2, message2) => { if (error2) { Debug.Log( string .Format( "We got an error: {0}" , message2)); } else { Debug.Log( string .Format( "Success: {0}" , message2)); } })); } })); // ... IEnumerator RequestData(Action< bool , string = "" > onComplete) { // Do some real work yield return new WaitForEndOfFrame(); //-- // No errors onComplete( false , "Data 1 working" ); } IEnumerator RequestSomeOtherData( string dataArgument, Action< bool , string = "" > onComplete) { // Do some real work with dataArgument yield return new WaitForEndOfFrame(); //-- // No errors onComplete( false , "Data 2 woohoo" ); }</ bool ,></ bool ,> |
这并不太坏! 正如你所看到的那样,这个程序的效果相当好。它给了我们一个可能,就是在返回一个值的同时报告错误和对这个错误进行反应。但是,这个问题在于你堆栈协同程序的时候会有更多水平的代码缩进。此外,错误检查必须在每一个地方都要进行一次。最终,这个更复杂的函数通过一个回调函数结束了。那到底是什么?好吧,让我们复制协同程序并把这四个坏男孩放入堆栈:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
StartCoroutine(RequestData( (error, message) => { if (error) { Debug.Log( string .Format( "We got an error: {0}" , message)); } else { StartCoroutine(RequestSomeOtherData(message, (error2, message2) => { if (error2) { Debug.Log( string .Format( "We got an error: {0}" , message2)); } else { StartCoroutine(RequestSomeOtherData(message, (error3, message3) => { if (error3) { Debug.Log( string .Format( "We got an error: {0}" , message3)); } else { StartCoroutine(RequestSomeOtherData(message, (error4, message4) => { if (error4) { Debug.Log( string .Format( "We got an error: {0}" , message4)); } else { Debug.Log( string .Format( "Success: {0}" , message4)); } })); } })); } })); } })); |
看下这段程序里面的金字塔形状以及在程序结尾的所有的} }));。呵呵!你还需要得到一些真正有创意的名字,因为它们必须是唯一的。关于错误处理,如果你把错误处理移动到另一个函数的话,你可以稍微整理一下代码。但它仍然看起来不怎么吸引人,这里我们只有四个协同程序放入了堆栈。当编写更复杂的函数或者为你的后端服务编写api的时候,这一数字可以有极大的增加。那么,解决方案是什么?
Promise
Promise在ES6中本地化实现之前,就已经通过许多第三方的Javascript库得到了推广。有趣的是,这个想法本身实际上已经在80年代和70年代进行过研究和介绍。我们今天所知道的Promise是芭芭拉 李斯柯夫和柳吧谢拉在1988年首次引入的,但类似的想法来自于很多年前的函数式编程语言。
一个符合Promise和A +规范的c#库几年前被正式推出,这对于管理异步操作来说是一个很大的游戏规则的改变。让我们深入代码来了解下。
一个Promise就是一个你从异步函数立即返回的对象,所以这是一个调用者可以等待决议(或者错误)的操作。所以从本质上说,它代表了当Promise创建的时候对于一个不确定知道的值的代理。一旦你完成你的行动以后,这个Promise可以决议或者被拒绝。这允许异步方法返回值并像同步方法那样运行:除了得到的不是最终的结果以外,它们返回的是一个在未来某一个时间点有有一个确定值得承诺。然而,这么做最好的部分在于用一个简单的接口进行简单的叠加。下面是我们如何重新实现我们的第一个例子并且不用纯粹的Action委托来实现我们的Promise:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
using RSG; //... RequestData() .Then(result => { return RequestSomeOtherData(result); }) .Then(result => { Debug.Log( string .Format( "Success: {0}" , result)); }) .Catch(error => { // Exception error Debug.Log( string .Format( "We got an error: {0}" , error.Message)); }); //... IPromise RequestData() { var promise = new Promise(); StartCoroutine(_RequestData(promise)); return promise; } IEnumerator _RequestData(Promise promise) { // Do some real work yield return new WaitForEndOfFrame(); //-- // No errors promise.Resolve( "Data 1 working" ); } IPromise RequestSomeOtherData( string dataArgument) { var promise = new Promise(); StartCoroutine(_RequestSomeOtherData(dataArgument, promise)); return promise; } IEnumerator _RequestSomeOtherData( string dataArgument, Promise promise) { // Do some real work with dataArgument yield return new WaitForEndOfFrame(); //-- // No errors promise.Resolve( "Data 2 woohoo" ); } |
这种方法对于调用者来说更棒! 你需要隐藏和封装协同程序的行为从而获得Promise这种方法的所有优势。通过.Then()函数,你可以很容易地用一个逻辑顺序来将新的Promise放入堆栈并且处理不同的结果类型。此外,通过.Catch()函数,你可以捕获所有发生在你的Promise里面的异常。如果任何上述Promise被拒绝的话,那么整个Promise的链会立即终止,然后代码将跳转到最近的catch语句。不仅如此,catch函数实际使用的是异常类型,所以你可以很容易地抛出自己的异常类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
class MyException : Exception { private readonly int foo; public int Foo { get { return foo; } } public MyException( int foo) { this .foo = foo; } public MyException( int foo, string message) : base (message) { this .foo = foo; } public MyException( int foo, string message, Exception inner) : base (message, inner) { this .foo = foo; } } IEnumerator _RequestData(Promise promise) { // Do some real work yield return new WaitForEndOfFrame(); //-- // Error! promise.Reject( new MyException(100, "Error error!" )); } |
只要你从异常类继续,就可以编写自己的异常类来模仿try。。。catch行为。像下面这样进行处理:
1
2
3
4
5
6
7
|
.Catch(error => { // Exception error if (error is MyException) { var myError = error as MyException; Debug.Log( string .Format( "We got an error: {0} {1}" , myError.Message, myError.Foo)); } }); |
总结
正如你可以看到的那样,Promise提供了一个伟大的机制来处理你的异步操作。你可以很容易地把错误从你的堆栈中的任何Promise里面抛出,如果你使用这个机制并与Unity合作的话,你可以在处理返回值的同时隐藏协同程序的实现。你在保留协同程序所有强大的方面的同时,还大大提高了你的复杂api和服务的接口。这个库提供了很多方面的内容,我们强烈建议你看下相关的文档来看看这个库还提供了哪些内容。
在这个系列的最后一部分之中,我们将向你展示一个真实的例子,通过使用Unity的协同程序以及Promise来编写一个REST API接口来得到尽可能最干净的代码。
我们会在这个系列的第三篇文章里面继续这个话题。
【转】Unity中的协同程序-使用Promise进行封装(二)的更多相关文章
- 【转】Unity中的协同程序-使用Promise进行封装(一)
原文:http://gad.qq.com/program/translateview/7170767 译者:陈敬凤(nunu) 审校:王磊(未来的未来) 每个Unity的开发者应该都对协同程序非 ...
- 【转】Unity中的协同程序-使用Promise进行封装(三)
原文:http://gad.qq.com/program/translateview/7170967 译者:崔国军(飞扬971) 审校:王磊(未来的未来) 在这个系列的最后一部分文章,我们要通过 ...
- Unity 中的协同程序
今天咱就说说,协同程序coroutine.(这文章是在网吧敲的,没有unity,但是所有结论都被跑过,不管你信得过我还是信不过我,都要自己跑一下看看,同时欢迎纠错)先说说啥是协程:协同程序是一个非常让 ...
- Lua中的协同程序
[前言] 协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈.局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西.从概念上讲,线程与协同程序的主要区别在于,一个具有多个线程的 ...
- Lua中的协同程序 coroutine
Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换.不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时 ...
- Lua中的协同程序 coroutine(转)
Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换.不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时 ...
- 小程序 请求Promise简单封装
最近做小程序在调用后台接口的时候感觉总写很长一串,很冗杂.非常想念vue中promise封装的写法,于是自己初步封装了一下. 1.url 接口地址 2.headers请求头 3. params 请求参 ...
- 关于Unity中的模型描边与Shader切换(专题二)
模型描边 1: LOL里面的模型描边效果,点击防御塔会有描边的效果,被攻击的时候模型也要描边凸显一下2: 网上可以找到模型描边的Shader,可以直接下载使用,一组第三方的Shader, 帮我们解决了 ...
- 【转】关于Unity协同程序(Coroutine)的全面解析
http://www.unity.5helpyou.com/2658.html 本篇文章我们学习下unity3d中协程Coroutine的的原理及使用 1.什么是协调程序 unity协程是一个能暂停执 ...
随机推荐
- MFC 打开其他程序
WinExec("程序路径",SW_SHOW);
- Shell 编程基础之括号的作用
一.小括号() 单小括号 命令组.括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用.括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必 ...
- Code Rush插件
code rush 是微软推出的一款VS2008上的插件.他有强大的文件和代码导航功能,易于访问的重构和代码创建功能.一组编辑器.选择.剪贴板工具等. 教程链接 http://www.devexpre ...
- Linux下使用vsftp
参考网址: http://wenku.baidu.com/view/4339434bc850ad02de80419c.html?re=view root用户无法ftp登录,显示530 Permissi ...
- BZOJ4369 : [IOI2015]teams分组
将分组计划按照$k$从小到大排序,维护一个单调栈,每个元素为一个矩形,按最底下元素从高到低排列,栈顶最低. 每次加入一个矩形可选区域,维护单调栈,可以往回合并. 然后将所有最低点不满足的矩形取出,合并 ...
- ORACLE11g JDBC Driver
http://blog.163.com/z_rx/blog/static/276363762011312947507/ ORACLE服务器端安装程序找到相应目录"x$\app\Adminis ...
- React.js 常用技术要点
最近在公司的一个移动端WEB产品中使用了React这个框架(并不是React-Native),记录一下在开发过程中遇到的各种问题以及对应的解决方法,希望能对读者有所帮助. React原则 React不 ...
- 经验分享:多屏复杂动画CSS技巧三则
当下CSS3应用已经相当广泛,其中重要成员之一就是CSS3动画.并且,随着CSS动画的逐渐深入与普及,更复杂与细腻的动画场景也如雨后春笋般破土而出.例如上个月做的「企业QQ-新年祝福」活动: 感谢sh ...
- hdu 2099 整除的尾数
Problem Description 一个整数,只知道前几位,不知道末二位,被另一个整数除尽了,那么该数的末二位该是什么呢? Input 输入数据有若干组,每组数据包含二个整数a,b(0< ...
- fuser 命令的介绍
fuser 可以显示出当前哪个程序在使用磁盘上的某个文件.挂载点.甚至网络端口,并给出程序进程的详细信息. fuser显示使用指定文件或者文件系统的进程ID.默认情况下每个文件名后面跟一个字母表示访 ...