【包教包会】Promise?async?await?终于有人把异步解释清楚了

演示demo:https://gitee.com/szrpf/AsyncDemo


image
以上2个方法,都是等待duration秒后,触发一个回调
区别是:delay是同步写法,sleep是异步写法

有一个需求,等待N秒后打印:“第N步”(一共打印4步)


同步写法
image
回调地狱,如果要求打印100步呢?
image


异步写法
image
异步方法会卡住进程,直到触发resolve后,才会执行下一行代码
image
如果要求打印100步呢?异步方法的好处显而易见
image
这就是为什么现代JavaScript标准中,推荐用异步写法
image


巩固一下,同步改异步,5个步骤:


有返回值的版本:


回家作业:请把这个获取资源的方法改成异步
loadAsset(path: string, type: any, callback: (asset: any) => void): void {
let id = path.indexOf(’/’);
let bundleName = path.slice(0, id);
let assetPath = path.slice(id + 1);
assetManager.loadBundle(bundleName, (err: Error, bundle: AssetManager.Bundle) => {
if (err) { callback(null); return; }
bundle.load(assetPath, type, (err: Error, asset: any): void => {
callback(err ? null : asset);
});
});
}

Gitee地址:https://gitee.com/szrpf

EMail地址:27185709@qq.com

推荐链接:
1、【包教包会】CocosCreator3.x——重写Sprite

2、【包教包会】3分钟学会贝塞尔曲线

3、【包教包会】130行代码实现多语言,i18n扔了扔了

4、【包教包会】CocosCreator3.x全局单例最优解

5、【包教包会】节点扩展(支持原生,附引擎源码查询教程、skew翻页教程)

mi

4赞

66666!

做大做强~yeah

1赞

还是你会取标题,看见包教包会,我就进来了

Promise 时序差异

由于 iOS JavaScriptCore 的限制,iOS 15 及以下的 Promise 是一个使用 setTimeout 模拟的 Polyfill。这意味着 Promise 触发的任务为普通任务,而非微任务,进而导致 在 iOS15 及以下的 Promise 时序会和标准存在差异

iOS 16 及以上不存在差异。

var arr = []

setTimeout(() => arr.push(6), 0)
arr.push(1)
const p = new Promise(resolve => {
  arr.push(2)
  resolve()
})
arr.push(3)
p.then(() => arr.push(5))
arr.push(4)
setTimeout(() => arr.push(7), 0)

setTimeout(() => {
  // 应该输出 [1,2,3,4,5,6,7]
  // 在 iOS15 小程序环境,这里会输出 [1,2,3,4,6,5,7]
  console.log(arr)
}, 1000)

关于普通任务和微任务的区别可以查看这篇文章

把这一节教懂学懂,才属于包教包会

1赞

可以,你的帖子我都不用在收藏找,太好搜了。(吐槽下,收藏的功能要用的时候基本找不到 :rofl:

跟营销号学的,还可以吧? :sunglasses:

可以可以,我这个是入门版,你是进阶版

必须的,相当于品牌商标

学到了,学到了。以后资源加载这块就很nice 了

1赞

继续对应下具体业务中会遇到什么问题吗? 感觉遇到就是个坑.

最近正在用Promise做一个加载模块图集中素材的接口,做了作业练习了一下。

/**
 * 异步加载指定路径的资源并执行回调函数
 * @template LoadType 资源类型构造函数,必须继承自 cc.Asset
 * @template CallbackResult 回调函数的返回类型
 * @param {string} path 资源路径,格式为"bundle名称/资源路径"
 * @param {LoadType} type 要加载的资源类型(构造函数)
 * @param {(asset: InstanceType<LoadType>) => CallbackResult} callback 加载完成后执行的回调函数(参数为资源实例)
 * @returns {Promise<CallbackResult>} 返回包含回调结果的Promise
 * @throws {Error} 当加载bundle或资源失败时抛出错误
 * @deprecated 未对资源进行引用计数管理
 * @example
 * loadAsset2('resources/Texture/Images/imageName', cc.SpriteFrame, (loadAssert): void => {
            this.testLoadSprite.spriteFrame = loadAssert
    })
 */
async function loadAsset2<LoadType extends typeof Asset, CallbackResult>(
    path: string,
    type: LoadType,
    callback: (asset: InstanceType<LoadType>) => CallbackResult
): Promise<CallbackResult> {
    const slashIndex: number = path.indexOf('/');
    const bundleName: string = path.slice(0, slashIndex);
    const assetPath: string = path.slice(slashIndex + 1);

    // 加载bundle
    const bundle: AssetManager.Bundle = await new Promise<AssetManager.Bundle>((resolve, reject) => {
        assetManager.loadBundle(bundleName, (err: Error, bundle: AssetManager.Bundle): void => {
            if (err) reject(err);
            else resolve(bundle);
        });
    });

    // 加载资源
    const asset: InstanceType<LoadType> = await new Promise<InstanceType<LoadType>>((resolve, reject) => {
        bundle.load(assetPath, type, (err: Error, loadedAsset: InstanceType<LoadType>): void => {
            if (err) reject(err);
            else resolve(loadedAsset);
        });
    });

    // 执行回调
    return callback(asset);
}

优点:做了泛型,类型更安全
缺点:这个方法取的是资源因此不需要resolve(bundle),异步方法无需callback直接resolve(asset)

如果是要显示加载进度还是需要回调的

加载单个资源进度获取不到吧,加载bundle可以取到的

其实使用await也要小心,当你的view被干掉了,但是你的delay还没有被清掉,时间到了会继续执行后面的代码,这个this已经无效了

const weakMap = new WeakMap(); // 全局弱引用映射
if (CC_DEV) {
    window["my_weakMap "] = weakMap 
}
const thenFunc = Promise.prototype.then
Promise.prototype.then = function (onFulfilled, onRejected) {
    const that = this
    return thenFunc.call(this,
        function (value) {
            if (typeof onFulfilled === 'function') {
                const targetRef = weakMap .get(that);
                if (targetRef && !cc.isValid(targetRef)) {
                    console.warn('Promise liftTarget Component is invalid, skip then callback')
                    // onRejected?.("liftTarget this is valid")
                    return;
                }
                return onFulfilled(value)
            }
            return value;
        },
        onRejected)
}

Promise.prototype.isValid= function (target: cc.Node | cc.Component) {
    if (!target) return this;
    weakMap .set(this, target); // 键是 Promise 实例,值是被弱引用的 target
    return this;
}

declare global {
   /**全局hook */
    interface Promise<T> {
        /**处理异步后生命周期问题 */
        isValid: (target: cc.Component | cc.Node) => Promise<any>
    }
}

//使用示例
await this.delay(100).isVaild(this) //当this 无效后 不会执行后面代码了

后来为了弄这个,我新增了isVaild方法,AI说尽量不这样扩展,但是这是改动已有代码的无二选择了,不然得在所有的await地方去判断 this是不是null,是不是还有效
该代码已经上线运行了,目前没什么问题

嗯,异步是会有这个问题

当初这样写了,AI这样说过 改动太多了 莫法 :joy: 受限当前业务中 只有一层的then,所以还好 可以在失效的时候 return value出去, 业务底层都是使用的resolve(null) 来处理错误 所以不存在reject的问题,可以根据自己的情况改动

还是因为js的promise没有中断,不然全局处理下就行了~