packages/core/props-manager/old/PropsManagerAsync.ts (106 lines of code) (raw):

/** * Copyright (C) 2021 Alibaba Group Holding Limited * All rights reserved. */ // ** this is not stable // todo 异步回调是潜在bug,如果 调用 set,然后立即又调用,async listener 中 多次 get 的结果会不一致 import { deepDiffProps } from './utils' // eslint-disable-next-line @typescript-eslint/ban-types export class PropsManager<TProps extends Record<string, any>> { /** * type of callback function */ private _callbackTemplate: (event: { changedKeys: Array<keyof TProps> trigger: Array<keyof TProps> // @deprecated rename as changedKeys currentProps: Partial<TProps> }) => Promise<void> | void private _props: TProps = {} as TProps // init, safe here private _listeners = new Map<keyof TProps, Set<typeof this._callbackTemplate>>() /** * Partially update props. Only pass changed or added properties. * * 初始化/更新属性,可以是增量更新 * * @note removed properties will be ignored. * * @changed 移除 callback 的 done 参数,和 promise.then 实质上重复 * @changed callback 接受的参数中,changedKeys 只会包含自己 listen 的,而不是全部变化的key * @changed callback 调用顺序会变化 * * @return A Promise indicating if all the listeners has been trigged */ set(props: Partial<TProps>): Promise<void> { const changedKeys = deepDiffProps(props, this._props) if (changedKeys.length === 0) { return new Promise((resolve, reject) => { resolve() }) } this._props = { ...this._props, ...props, } Object.freeze(this._props) // @note key 与 callback 是多对多的,这里需要整理出 那些 key 触发了 哪些 callback // callback -> changed keys that this callback is listening const callbackKeys = new Map<typeof this._callbackTemplate, Array<keyof TProps>>() for (let i = 0; i < changedKeys.length; i++) { const key = changedKeys[i] const listeners = this._listeners.get(key) if (!listeners) continue listeners.forEach((listener) => { let listenedChangedKeys = callbackKeys.get(listener) if (listenedChangedKeys === undefined) { listenedChangedKeys = [] as Array<keyof TProps> callbackKeys.set(listener, listenedChangedKeys) } listenedChangedKeys.push(key) }) } const promises = [] as Array<Promise<void> | void> callbackKeys.forEach((listenedChangedKeys, callback) => { const res = callback({ changedKeys: listenedChangedKeys, trigger: listenedChangedKeys, currentProps: this._props, }) promises.push(res) }) // it's allowed to pass a array of non-promises element // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all#return_value // @note do not use async/await so that above code can be executed synchronously return new Promise<void>((resolve, reject) => { Promise.all(promises) .then(() => { resolve() }) .catch((e) => { reject(e) }) }) } /** * 获取属性对应的值 */ get<TKey extends keyof TProps>(key: TKey): TProps[TKey] | undefined { return this._props[key] } /** * 注册属性变化监听器 * * If any of these keys changed. callback will be called. * Actual changed keys (in this listening list) will be passed to the callback. * * @note **using async functions as callback is not thread safe!** * * if you intend to do so, either: * - use `version` to check if props changed again * - or just make sure `.set` won't be called again until last return promise fulfilled * * @note initial callback will be fired immediately. set `immediately` false to disable */ listen<TKeys extends Array<keyof TProps>>( keys: TKeys, callback: (event: { changedKeys: TKeys /** * @deprecated renamed as changedKeys */ trigger: TKeys /** * frozen object */ currentProps: Partial<TProps> }) => Promise<void> | void, immediately?: boolean ): Promise<void> { for (let i = 0; i < keys.length; i++) { const key = keys[i] let listeners = this._listeners.get(key) if (!listeners) { listeners = new Set() this._listeners.set(key, listeners) } listeners.add(callback as any) } const currentProps = this._props return (async () => { if (immediately) { return await callback({ changedKeys: [] as any, trigger: [] as any, currentProps, }) } else { return } })() } /** * stop listening for certain keys * @note will not cancel fired callbacks */ stopListen<TKeys extends Array<keyof TProps>>(keys: TKeys, callback: (event) => any): void { for (let i = 0; i < keys.length; i++) { const key = keys[i] const listeners = this._listeners.get(key) if (!listeners) continue listeners.delete(callback as any) } } dispose(): void { this._props = {} as TProps this._listeners.clear() } } // test const a = new PropsManager<{ cc: 'dd' | 'ee'; ff: 'gg' | 'kk' }>() a.listen(['cc'], async (event) => {}) a.listen(['cc', 'ff'], async (event) => { event.trigger }) // a.listen(['cc', 'hh'], async (event) => {})