【转】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协程是一个能暂停执 ...
随机推荐
- 餐厅点餐APP总结
总结:经过这几个月的时间里,我们通过学习.讨论一起做出了餐厅点餐这个APP,我们在做这个APP的过程中,每个人都有自己的想法,也通过讨论最后做出了这个app,虽然做的不是很好,但是我们也尽自己的努力尽 ...
- DP(记忆化搜索) + AC自动机 LA 4126 Password Suspects
题目传送门 题意:训练指南P250 分析:DFS记忆化搜索,范围或者说是图是已知的字串构成的自动机图,那么用 | (1 << i)表示包含第i个字串,如果长度为len,且st == (1 ...
- 百度地图API使用记录
用户数据图层的总教程: 就是把用户数据存到LBS云里面,应用从云里面读数据 http://developer.baidu.com/map/jsdevelop-9.htm 上传数据的地方: http:/ ...
- Linux下使用vim的tips
1.如果用户不能确定vi所处的状态,可以按Esc键两次返回初始状态. 2.Fedora 17下安装与配置ssh:http://blog.csdn.net/ashuai81/article/detail ...
- jquerymobile页面跳转和参数传递
http://blog.csdn.net/chen052210123/article/details/7481578 页面跳转: 页面跳转时pagebeforechange事件会被触发两次,通过$(d ...
- The Result of an Interesting and Useful Program on Floating Numbers
The program: class Program { static void Main() { const double infinity = double.PositiveInfinity; c ...
- 如何伪装成为一名前端(JS方向)
作为一个菜鸟级别的.NET开发者,在连服务器都没搞定的情况下,要研究前端,这是在扯淡,不过,迫于工作的需要,时常需要去前端打杂,所以经常伪装成为一名前端,有时候竟产生错觉,去应聘Y一份前端work吧. ...
- UVA 11609 - Teams(二项式系数)
题目链接 想了一会,应该是跟二项式系数有关系,无奈自己推的式子,构不成二项式的系数. 选1个人Cn1*1,选2个人Cn2*2....这样一搞,以为还要消项什么的... 搜了一下题解,先选队长Cn1,选 ...
- 【JAVA】Math.Round()函数常见问题“四舍5入”
java.lang.Math.Round()使用时候,处理方式整理,方便以后查找 /** * 测试函数 2014-01-10 */ public class TestMath { pu ...
- 51nod算法马拉松14
这次太丢人了只搞出来了A到D,那就将就写一写A到D... A 棋盘问题 脑筋急转弯题,不难发现每一次两个人只能染白奇数个格子,所以数数有奇数还是偶数个白格子就行了. #include<cstdi ...