项目里面用了很多async和await 这样好不好

写了一两年2dx,终于又回来写creator了。
2dx的同步写法用得太爽了,于是乎把很多接口都封装成了async, 比如加载bundle,SpriteFrame,json什么的,
但是要用async 方法 那调用这个方法的方法也得是async 所以async越写越多,甚至感觉有点滥用
不知道这样到底好不好,各位大佬有何高见

当然好用了。你封装好 API ,不用纠结都是 async 的问题。你只需要关心调用的时候是否一定要等待执行完成。

是的话,加 await ,方法加 async。不是的话,直接调用。

那我就放心了

推荐用awaitasync 拒绝用回调。

挺好奇 为啥你推荐的,和官方推荐的 完全相反!

编程风格问题。流程管理更加方便。降低耦合度。

1赞

好 (字数补丁)

个人看法,非泼冷水

  1. async/await用起来爽,但async函数执行后是没有接口让你中断或停止(打个比方:枪杀过的人都知道,开枪这个过程需要填弹、上膛、拉栓、扣扳机,如果开枪动作是固定的,那一旦填弹,扣扳机一定会执行,停都停不了),会需要处理很多临界情况,如高频触摸、频繁前后台、网络环境差等等逻辑需要中断处理。

node/deno这类服务器逻辑里可以放心用,但在客户端希望可以谨慎处理,如果是纯算法非UI模块(比如网络通信、加载、寻路算法等等),async/await用起来问题不大,如果是UI和Component建议控制使用范围,你也不想界面已经移除了,组件的async/await还在跑吧

  1. 如项目代码性能要求高也不建议用,Promise这类东西毕竟有一层转换开销,还有一些小游戏运行环境可能连Promise都没有,引擎层只能给你babel/polyfill一个,连带着代码膨胀和各种co/yield执行(可参考Creator2.x构建后的js代码),这性能能提起来才有鬼。

  2. 运行环境限制,比如开放域排行榜js里面写async/await,在一些iOS系统直接跪,报错都没有,当然原生Native环境没有这个问题。

对异步请保持敬畏

6赞

确实要保持敬畏,真正写过几个项目才知道await和async的问题。
1.首先就是会导致node再await期间被销毁但是后面继续使用
2.js的await无法取消,也会导致很多问题,不希望执行的逻辑又继续执行

很多难查找的bug,都是await/async引起的,不同平台执行的时间不一样,pc很难复现

2赞

所以你可以封装重写,这样就能随时中断了。

1赞

游戏的性能压力从来不会在 promise 这层,要么渲染,要么计算,什么时候也轮不到 promise 背锅。

另外中断不是 promise 的问题,中断导致的问题是在 promise 返回结果后的处理问题。这本质是异步导致的问题,网络请求回调这类的异步,不用 promise 也会出现你说的问题。promise 单词本身的意思就是一定会有返回,你要当中断来用,就要自己封装新概念。

只有认清 promise 的特性,才能发挥它的特长。

4赞

使用promise,这种问题相当于,一个线性的管理团队。底下的成员有可能中途离职,生病。发起的第一个promise相当于管理者。内部promise中断,可以向外传递,到最外层。由最外层管理者处理。所以需要对promise进行封装。

使用promise的好处是,整个项目结构更加倾向于线性的代码结构。不是网格结构。

游戏的性能在于,计算(渲染内存)。其本质就是时间与空间的节约。大多数情况下,就是做时间与空间的平衡

可能我表述不准确

  1. Promise的概念清晰简单,也没法去控制(我也没有说要控制Promise),要是能控制那命名就不叫promise,这个没太多好说的。而async本质是generator函数语法糖,站在人类理解async内部是个队列,里面有一堆yield的状态。ES只是搞了标准,但无控制办法。

  2. 用个例子实际演示 TS async/await编译出来是什么

这是TS代码(CocosCreator3.7.x版本)
image

小游戏构建后的js是这样
image

其中核心_asyncToGenerator是这样(不继续贴asyncGeneratorStep源码了)

如果是CocosCreator2.x版本,上面生成JS代码复杂度更感人。
当然,上面这样的结果来源于Creator的编译策略,如果游戏的js运行环境支持ES7,那 async/await几乎原样编译。

回归Creator引擎这部分,我用async/await是想消除“callback hell”,但也不想是消除callback hell而生成更多的callback和promise【如上图】,毕竟生成的都是对象都要占内存,iOS上面非JIT那孱弱的性能让我不得不注意,多生成一个都是实实在在的开销。

你觉得岁月静好,那一定有人在负重前行。

  1. 关于控制,有人也提到了封装,当然也是有办法的,比如可以直接给generator 额外加个控制接口,但就我个人而言,还是等加了控制接口的ES20XX出来吧,ES6以前JS业界有N种Class的实现方式,各类“大神”群魔乱舞的定义自己的class,看的脑壳疼,真心不想有控制器的async也回到那个时代。

回归到实际需求上,我们也确实需要解决callback hell,但也要对callback链进行控制,这是我在项目中使用异步队列 haroel/AsyncQueue: TypeScript异步任务队列库。
可暂停、恢复、停止、移除,稍加处理就可以跟Component生命周期绑定

  1. 回到问题,async/await用不用在于个人决策,我个人做的内部nodejs、vue也是有大规模使用,但在游戏业务层使用上,我一直不建议大范围去用,比如定义的组件加async函数这种情况,当然用于非GUI模块影响不大,只要控制好就行。
    对于框架成熟度、研发水平足够高的团队,怎么用我认为他们都能处理好,但对于小白或新手,还希望对异步保持敬畏(不扯有的没的,你也不想界面已经移除了,组件的async/await还在跑吧,当然你还可以在每个await后面判断一次组件是否有效,无效return)。

就像大家说的,要知道他的好也要知道特长点在哪,那才用得好,要是只知道好就无脑用,产品数据会教你

3赞

小项目await的时候加个锁不用考虑结点销毁问题,也感受不到性能差异,大项目自有主程给方案

他这个回复简直强词夺理。别说多一层回调了,多二 层三层都没问题。
他的逻辑是: iOS 性能差所以所有调用超过 M 层的,都是错误的。(业务层如果用方法封装了多层调用,那就是影响性能:worried:
第二个说法是,“因为看了构建后的代码嵌套更多了,本来是消除嵌套的,反而多了嵌套”。这简直离谱,我们消除的是构建前的嵌套。提高的是代码的可读性。构建后多了嵌套又如何。
第三个,用 AsyncQueue 是你的事情,人家 async 和 await 是语言标准。你不能说它有一个库。就说这个不行。
逻辑学上,你要证明游戏的性能瓶颈出在 async 和 await 上。而不是让别人证明 async/await 没问题。
目前这个证明没有说服力。

4赞

几个大项目都在用,而且主推用。所以放心用。自己加点封装。

1赞

喜欢这种battle,希望不要变为对骂。

4赞

问题就应该是理性讨论,拒绝贴标签,我只说我的建议,没跟任何人争(就跟有同学说的,不要变成对骂)

我认为我上述所有回复都是温和、理性的观点。

答复:

  • 我有哪句话说超过M层就是错误的?我有夺了别人什么理,究竟是谁在混淆视听、强词夺理?

  • Creator编译代码膨胀产生了非常多的额外对象和回调,这是事实。你认为多嵌套完全无所谓是你的认知,我尊重,但我也有权利认为额外的多回调和对象生成都是性能开销(为什么搞尾调用优化),这没必要给别人贴个强词夺理的标签。

  • 我两次都说了要看使用场景,也说了哪地方我觉得用了无所谓,比如nodejs写服务器业务,我的前提是就Creator/GUI开发环境,用起来会有这样那样问题,能否好好看完不要动肝?

  • 就还能扯到逻辑学,我让谁证明async没问题了? 那从逻辑学上,你除了驳次他人丝毫也没有提供任何有价值建议,是不是也行。

CocosCreator的开发环境中,我表达的点简单说就这些:async编译膨胀、性能、async运行控制,我也希望有大佬提出更好的async优化方案造福JS业界。

你们猜,CocosCreator3.x引擎源码有大量TS,为什么这些TS里面也不怎么用async/await(除tool、webgpu)?

最后,如果有人觉得自己的编程方式哲学容不得半点“质疑”,不驳不喷就浑身难受不舒服,那我删除,过年大家开心就好。

7赞