【cocos-creator】依次运行多个action/异步操作

使用场景

在游戏制作过程中,特别是UI部分,经常会遇到需要多个不同节点依次执行不同action的情况。例如有NodeA,NodeB,NodeC3个节点,分别执行ActionA,ActionB,ActionC三个不同的action,执行顺序为A-B-C(A执行完之后才执行B)。
延伸场景:需要依次执行多个异步操作的,在cocos中,异步操作并不多,例如cc.loader.loadResDir()

先介绍一下我的方法:使用Promise封装action

Promise是ES6中针对异步编程的一种解决方案,《ES6标准入门》中的解释为:

所谓Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

因此可以有以下伪代码:

// 封装Promise
let PromiseA = () => {
    return new Promise((resolve, reject) => {
        NodeA.runAction(cc.sequence(
            ActionA,
            cc.callFunc(() => { resolve() }),
        ))
    })
}
let PromiseB = () => {
    return new Promise((resolve, reject) => {
        NodeB.runAction(cc.sequence(
            ActionB,
            cc.callFunc(() => { resolve() }),
        ))
    })
}
let PromiseC = () => {
    return new Promise((resolve, reject) => {
        NodeC.runAction(cc.sequence(
            ActionC,
            cc.callFunc(() => { resolve() }),
        ))
    })
}
// 执行Promise
PromiseA().then(()=>{
    PromiseB().then(()=>{
        PromiseC()
    })
})

以上代码实现了顺序执行ABC。

使用Promise带来的问题

  • 【主要问题】需要多层封装,可读性差。首先,Promise在新建的时候会自动执行,因此需要使用箭头函数进行包装。其次,then()方法需要传入一个方法作为参数,层层调用,如果需要执行的动作很多,则层层回调,非常不直观。(在下一节中会针对这个问题提出优化)
  • 【次要问题】then()方法依然是一个异步操作,只是同步的写法。会导致函数在return的时候return undefined。(异步方法的return都会有这个问题)

优化使用Promise

针对以上问题,有2个优化的方向:

  • 方向1:使用Generator,或者async/await进行优化。笔者对此还没有研究,不再多写。但是需要注意的是,各个平台中对Promise都明确支持,但是对这两个特性的支持情况有所不同,要谨慎使用。
  • 方向2:写一个函数,可以依次执行多个Promise。Promise本身有Promise.all()方法和Promise.race()方法可以执行多个Promise,均是同时执行。具体函数如下所示:
    (声明:参考博客:https://www.cnblogs.com/rusr/p/8488483.html)
/**
 * 依次运行多个promise
 * @param {[()=>{}]} promise_array 由于promise建立后立即执行的特性(坑),因此需要使用一个箭头函数进行包装
 * @returns {Promise}
 */
run_promise_chain(promise_array) {
    let p = Promise.resolve()
    for (let promise of promise_array) {
        p = p.then(promise)
    }
    return p
}
/**
 * 依次运行多个promise(有缺点)
 * - 使用递归算法
 * - 【注意】 异步操作无法使用尾递归优化
 * - 【注意】 异步操作无法返回一个正常的返回值(异步函数会直接返回undefined),应该无法在此函数后使用then()
 * @param {[()=>{}]} promise_array
 * @returns {undefined}
 */
run_promise_chain_with_recursive(promise_array) {
    if (promise_array.length === 0) { return }
    let a = promise_array.shift()
    a().then(() => {
        this.run_promise_chain_with_recursive(promise_array)
    })
}

以上2种封装函数,第1种其实我也没有搞懂为什么就可以实现,还在不断学习中,但是总之能实现,并且返回一个Promise。第2种是我采用递归函数做的,有1个缺点就是异步操作没有返回值(我查询了一些资料,了解到异步操作也是可以有返回值的,所以这部分我还在不断改进),导致无法使用尾递归优化和判定全部Promise执行完毕。因此目前推荐使用第1种。

补充介绍:非Promise方法,“传统的”cocos方法

方法1:ScheduleOnce()

获取到ActionA,ActionB,ActionC的完成时间,设为TimeA,TimeB,TimeC,伪代码如下:

NodeA.runAction(ActionA)
this.scheduleOnce(() => {
    NodeB.runAction(ActionB)
}, TimeA)
this.scheduleOnce(() => {
    NodeC.runAction(ActionC)
}, TimeA + TimeB)

方法2:标志位

这个方法太蠢了,不建议使用。思路就是给ActionA设置一个标志位,然后每隔一段时间检测一次标志位,检测成功则执行ActionB。

最后总结

  • 尽量少使用异步操作。
  • 如果非要使用,则尽量避免陷入“依次执行异步操作”这个坑,写起来比较麻烦,理解起来也比较麻烦。

你可能感兴趣的:(【cocos-creator】依次运行多个action/异步操作)