监听多个组件渲染完成的解决方案

前言

最近入职了新公司,进来之后就一直在开发低代码平台的内容,一直在各个模块中反复横跳。

但是最近遇到一个比较棘手的问题 ------------ 我需要知道什么时候这些模块都初始化结束了。

问题是:模块的初始化是由组件自身完成,假如我的低代码平台中配置了很多模块,我并不知道什么时候这些模块都初始化完成了,里面也有各种各样的特殊逻辑,太分散了,没办法直接监听什么时候模块都初始化结束

但是这并难不倒我,经过半个上午的思考我得到一个不错的解决方案。

思路

我的思路是先维护两个map: promiseMap,promiseAllMap

ts 复制代码
__promiseMap: {
    } as {[key: string]: {
        [targetId: string | number]: {
            resolve
            reject
            promise
        }
    }},
__promiseAllMap: {
    } as {[key: string]: any}

在考虑到使用场景的多样化,初始化的时候先给 promiseMap, promiseAllMap 设置一个key

js 复制代码
initPromiseMap (key) {
    if (this.__promiseMap[key]) this.__promiseMap[key] = {}
    if (this.__promiseAllMap[key]) this.__promiseAllMap[key] = {}
},

使用时,需要先拿到对应的模块列表,这些模块需要有一个标识自身的id,我们通过key 和 id给每一个模块创建一个 pending 的promise

并且暂存这个promise的res,rej函数,并最终返回一个,包含res,rej,promise 的对象 ,给到promiseMapkey 和 id 映射的模块

ts 复制代码
setPromise (key: string, id: string | number) {
    if (!this.__promiseMap[key]) {
        this.__promiseMap[key] = {}
    }
    // 注意:这里我暂存了promise的 resolve, reject 函数
    let res
    let rej
    const promise = new Promise((resolve, reject) => {
        res = resolve
        rej = reject
    })
    this.__promiseMap[key][id] = {
        resolve: res,
        reject: rej,
        promise
    }
 },

这样当我们执行遍历一遍模块列表之后就可以得到一个map,里面每一个模块都有自己对应的pending状态的promise。

紧接着我们去创建一个针对当前key下所有pending PromiseallSettled Promise

ts 复制代码
launchPromiseAll (key: string) {
    let promiseList = []
    if (Object.keys(this.__promiseMap[key]).length) {
        promiseList = Object.values(this.__promiseMap[key]).map(item => {
            return item.promise
        })
    }
    this.__promiseAllMap[key] = Promise.allSettled(promiseList)
},

由于promiseList中的promise都是我们创建的pending状态的promise

所以this.__promiseAllMap[key] 也是一个pending的 Promise

到这一步我们就可以拿到这个 存储起来的 allSettled Promise,并设置其回调,但是由于默认是pending的因此不会直接执行。

ts 复制代码
 targetPromiseAll (key: string) {
    return this.__promiseAllMap[key]
}

上面的内容完成之后基础部分的操作就完成了,那我们怎么监听模块是否加载完呢?

我们知道 Promise.allSettled 需要里面所有的Promise都结束才会到下一步。

然后里面的promise都是我们从 this.__promiseMap[key] 中拿出来的 pending状态的Promise,

那么我们就可以在模块渲染完成之后的生命周期中 读到 this.__promiseMap[key] 中对应模块的promise对象

ts 复制代码
getPromise (key: string, id: string) {
    return this.__promiseMap[key][id] || { resolve: () => {}, reject: () => {} }
},

拿到的结果实际上是一个包含resolve 和 reject函数的对象,其中 resolve 和 reject函数 是我们缓存下来的 模块对应的promise的 resolve 和 reject函数,调用之后可以更新这个 promise的状态

ts 复制代码
{
    resolve: res,
    reject: rej,
    promise
}

当所有的promise状态都被更新之后,这个 allSettled Promise 的promiselist就全部都确定了,那么回调就会执行,我们就知道什么时候模块加载完了。

完整代码

注意:这里为了方便用的小菠萝,但是并不强管理,单独写个文件也行

ts 复制代码
export const usePromiseStore = defineStore('promise', {
    state () {
        return {
            __promiseMap: {
               
            } as {[key: string]: {
                [targetId: string | number]: {
                    resolve
                    reject
                    promise
                }
            }},
            __promiseAllMap: {

            } as {[key: string]: any}
        }
    },
    actions: {
        initPromiseMap (key) {
            if (this.__promiseMap[key]) this.__promiseMap[key] = {}
            if (this.__promiseAllMap[key]) this.__promiseAllMap[key] = {}
        },
        getKeys () {
            return Object.keys(this.__promiseMap)
        },
        setPromise (key: string, id: string | number) {
            if (!this.__promiseMap[key]) {
                this.__promiseMap[key] = {}
            }
            let res
            let rej
            const promise = new Promise((resolve, reject) => {
                res = resolve
                rej = reject
            })
            this.__promiseMap[key][id] = {
                resolve: res,
                reject: rej,
                promise
            }
        },
        getPromise (key: string, id: string) {
            return this.__promiseMap[key][id] || { resolve: () => {}, reject: () => {} }
        },
        launchPromiseAll (key: string) {
            let promiseList = []
            if (Object.keys(this.__promiseMap[key]).length) {
                promiseList = Object.values(this.__promiseMap[key]).map(item => {
                    return item.promise
                })
            }
            this.__promiseAllMap[key] = Promise.allSettled(promiseList)
        },
        targetPromiseAll (key: string) {
            return this.__promiseAllMap[key]
        }
    }
})

使用

主文件

ts 复制代码
// 创建promiseMap,用于获取所有模块渲染标识
this.initPromiseMap('test')

for... {
     // 创建当前卡片的promise
     this.setPromise('test', id)
}
// 创建监听promise
this.launchPromiseAll('test')

// 监听promise回调
this.targetPromiseAll('test').then(() => {
    console.log('结束')
})

组件侧

ts 复制代码
// 获取promise对象
const p = this.getPromise('test', id)

// 更新promise状态
p.resolve('xxx') 

总结

主要通过注册很多pending状态的普通promise

并将其传入到promise.allSettled的大promise中,业务侧通过手动的调用__promiseMap中对象的resolve函数更新promise状态

当全部更新promise.allSettled的大promise就会结束

相关推荐
木木黄木木9 分钟前
css炫酷的3D水波纹文字效果实现详解
前端·css·3d
美食制作家11 分钟前
【无标题】Threejs第一个3D场景
javascript·three
郁大锤32 分钟前
Flask与 FastAPI 对比:哪个更适合你的 Web 开发?
前端·flask·fastapi
HelloRevit1 小时前
React DndKit 实现类似slack 类别、频道拖动调整位置功能
前端·javascript·react.js
ohMyGod_1232 小时前
用React实现一个秒杀倒计时组件
前端·javascript·react.js
eternal__day2 小时前
第三期:深入理解 Spring Web MVC [特殊字符](数据传参+ 特殊字符处理 + 编码问题解析)
java·前端·spring·java-ee·mvc
醋醋2 小时前
Vue2源码记录
前端·vue.js
艾克马斯奎普特2 小时前
Vue.js 3 渐进式实现之响应式系统——第四节:封装 track 和 trigger 函数
javascript·vue.js
江耳2 小时前
从10秒到无限流:我用Vercel+NextJS实现AI流式对话遇到的超时问题及解决方案
前端
总之就是非常可爱2 小时前
三分钟让你看懂alien-signals computed基本原理
前端