【转】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协程是一个能暂停执 ...
随机推荐
- BZOJ3566 : [SHOI2014]概率充电器
选个根把无根树转化成有根树, 设f[i]表示i不通电的概率 则 答案为对于枚举树根root进行DP后1-f[root]的和 直接算是O(n^2)的,但是n有500000,所以不能过. 对于这样一棵以1 ...
- BZOJ 1053 & 反素数
题意: 反素数,膜一篇GOD's Blog...http://blog.csdn.net/ACdreamers/article/details/25049767 此文一出,无与争锋... CODE: ...
- NOIp 2014 #3 寻找道路 Label:图论
题目描述 在有向图G 中,每条边的长度均为1 ,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件: 1 .路径上的所有点的出边所指向的点都直接或间接与终点连通. 2 .在满足条 ...
- [BZOJ2795][Poi2012]A Horrible Poem
2795: [Poi2012]A Horrible Poem Time Limit: 50 Sec Memory Limit: 128 MBSubmit: 261 Solved: 150[Subm ...
- C#_简单实用的翻页
简单实用的生成翻页HTML辅助类 C# using System.Text; namespace ClassLibrary { /// <summary> /// /// </sum ...
- 纪念逝去的岁月——C/C++交换排序
交换排序 代码 #include <stdio.h> void printList(int iList[], int iLen) { ; ; i < iLen; i++) { pri ...
- DBLink创建 ORA-12154: TNS: 无法解析指定的连接标识符
因为对oracle不了解,这个问题可TM的搞了好久! 走的弯路: 1. 在客服端的PLSQL连接工具上折腾,而不是在服务器的PLSQL解决 2. 配置的tnsnames.org文件在环境变量path( ...
- 开源工作流引擎CCFlow 学习专区
http://bbs.ccflow.org/index.aspx 官网:http://www.ccflow.org/
- asp.net中验证控件的使用方法
用于检查是否有输入值 :RequiredFieldValidator(必须字段验证) 按设定比较两个输入 :CompareValidator(比较验证) 输入是否在指定范围 :RangeValidat ...
- jquery插件之表格隔行变色并鼠标滑过高亮显示
该插件乃本博客作者所写,目的在于提升作者的js能力,也给一些js菜鸟在使用插件时提供一些便利,老鸟就悠然地飞过吧. 此插件旨在实现表格隔行变色,且鼠标移动在表格的某一行上时,该行能高亮显示.整体代码如 ...