原文: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进行封装(二)的更多相关文章

  1. 【转】Unity中的协同程序-使用Promise进行封装(一)

    原文:http://gad.qq.com/program/translateview/7170767 译者:陈敬凤(nunu)    审校:王磊(未来的未来) 每个Unity的开发者应该都对协同程序非 ...

  2. 【转】Unity中的协同程序-使用Promise进行封装(三)

    原文:http://gad.qq.com/program/translateview/7170967 译者:崔国军(飞扬971)    审校:王磊(未来的未来) 在这个系列的最后一部分文章,我们要通过 ...

  3. Unity 中的协同程序

    今天咱就说说,协同程序coroutine.(这文章是在网吧敲的,没有unity,但是所有结论都被跑过,不管你信得过我还是信不过我,都要自己跑一下看看,同时欢迎纠错)先说说啥是协程:协同程序是一个非常让 ...

  4. Lua中的协同程序

    [前言] 协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈.局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西.从概念上讲,线程与协同程序的主要区别在于,一个具有多个线程的 ...

  5. Lua中的协同程序 coroutine

    Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换.不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时 ...

  6. Lua中的协同程序 coroutine(转)

    Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换.不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时 ...

  7. 小程序 请求Promise简单封装

    最近做小程序在调用后台接口的时候感觉总写很长一串,很冗杂.非常想念vue中promise封装的写法,于是自己初步封装了一下. 1.url 接口地址 2.headers请求头 3. params 请求参 ...

  8. 关于Unity中的模型描边与Shader切换(专题二)

    模型描边 1: LOL里面的模型描边效果,点击防御塔会有描边的效果,被攻击的时候模型也要描边凸显一下2: 网上可以找到模型描边的Shader,可以直接下载使用,一组第三方的Shader, 帮我们解决了 ...

  9. 【转】关于Unity协同程序(Coroutine)的全面解析

    http://www.unity.5helpyou.com/2658.html 本篇文章我们学习下unity3d中协程Coroutine的的原理及使用 1.什么是协调程序 unity协程是一个能暂停执 ...

随机推荐

  1. HDU5737 : Differencia

    注意到$b$不变,考虑用归并树来维护这个$b$序列,对于每个节点有序地维护$b$,同时在归并的时候预处理出每个元素在左右儿子里的排名. 在归并树上额外维护区间内$a\geq b$的个数以及赋值标记. ...

  2. HDU1853 Cyclic Tour(最小费用最大流)

    题目大概说给一张有向图,每条边都有权值,要选若干条边使其形成若干个环且图上各个点都属于且只属于其中一个环,问选的边的最少权值和是多少. 各点出度=入度=1的图是若干个环,考虑用最小费用最大流: 每个点 ...

  3. 关于dialog置于底层的问题

    我今天开发的一个dialog,引用代码中已有的,发现是居中的 接下来,我要把它置为底部 然后,我的思路就是从dialog的view设置入手,还用了setgravity,最后还是不成功 所以,后来,我休 ...

  4. BZOJ4573 : [Zjoi2016]大森林

    扫描线,从左到右依次处理每棵树. 用set按时间顺序维护影响了这棵树的所有操作,那么一个点的父亲就是它前面第一个操作1. 用Splay维护树的括号序列,那么两点间的距离就是括号数量减去匹配的括号个数. ...

  5. word多级编号,如何让第一级为大写“一”,其他级别均为小写1.

    自定义里面设置了第一级为大写,2级.3级首字跟着变为大写,是因为2.3级没有勾选"正规形式编号",如图:

  6. Zend studio 10.6 配置XDEBUG

    1. 查看PHP版本,下载XDebug     然后去网站http://xdebug.org/download.php下载相应的XDEBUG, 把下载好的文件放到相应的PHP下的EXT目录下,替换之前 ...

  7. 利用ngxtop实时监控nginx的访问情况

    关于对nginx web server的实时访问的实时监控问题,我很久之前就想实现的,现在虽有nginx自带的status扩展,但那是全局的,无法细分到vhost,并且提供的metric也很少,加之目 ...

  8. [Cocos2D-x For WP8]Tile Map创建地图

    在Cocos2D-x里面创建Tile Map地图是需要用到.tmx的地图文件的,那么创建Tile Map地图文件,我们可以通过地图编辑器来创建,地图编辑器可以在网站:http://www.mapedi ...

  9. eclipse安装color theme插件

    为Eclipse添加Color.Theme的插件 这样可以方便一键更换主题,再也不用一个一个设置BackgroundColor了,同时也方便回退到default默认主题配置. 方法一: 打开Eclip ...

  10. 深入浅出 - Android系统移植与平台开发(九)- JNI介绍

    作者:唐老师,华清远见嵌入式学院讲师. JNI是在学习Android HAL时必须要面临一个知识点,如果你不了解它的机制,不了解它的使用方式,你会被本地代码绕的晕头转向,JNI作为一个中间语言的翻译官 ...