Skip to content
This repository has been archived by the owner on Apr 2, 2022. It is now read-only.

Commit

Permalink
Merge branch 'dev' of github.com:rxliuli/rx-util into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
rxliuli committed May 17, 2019
2 parents 8b3095e + e9b2558 commit 3d540dd
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 40 deletions.
6 changes: 4 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ import { listToTree } from './module/tree/listToTree'
import { bridge } from './module/function/bridge'
import { treeToList } from './module/tree/treeToList'
import { treeMapping } from './module/tree/treeMapping'
import { INodeBridge } from './module/tree/NodeBridge'
import { NodeBridge } from './module/tree/NodeBridge'
import { nodeBridgeUtil } from './module/tree/nodeBridgeUtil'
import { getObjectEntries } from './module/obj/getObjectEntries'
import { getObjectKeys } from './module/obj/getObjectKeys'
Expand All @@ -120,6 +120,7 @@ import { Locker } from './module/function/Locker'
import { trySometime } from './module/function/trySometime'
import { trySometimeParallel } from './module/function/trySometimeParallel'
import { compare } from './module/obj/compare'
import { sleep } from './module/function/sleep'

/**
* 全局导出的对象,用于浏览器中使用的全局变量 rx
Expand Down Expand Up @@ -184,6 +185,7 @@ export {
returnReasonableItself,
safeExec,
singleModel,
sleep,
StateMachine,
throttle,
timing,
Expand Down Expand Up @@ -233,7 +235,7 @@ export {
floatEquals,
listToTree,
treeMapping,
INodeBridge,
NodeBridge,
nodeBridgeUtil,
treeToList,
}
4 changes: 2 additions & 2 deletions src/module/cache/CacheOption.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ export class CacheOption {
/**
* @field 超时时间,以毫秒为单位
*/
this.timeStart = timeStart
this.timeout = timeout
/**
* @field 缓存开始时间
*/
this.timeout = timeout
this.timeStart = timeStart
/**
* @field 缓存序列化
*/
Expand Down
42 changes: 32 additions & 10 deletions src/module/cache/LocalStorageCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CacheVal } from './CacheVal'
import { CacheOption, TimeoutInfinite } from './CacheOption'
import { assign } from '../obj/assign'
import { safeExec } from '../function/safeExec'
import { wait } from '../function/wait'

/**
* 使用 LocalStorage 实现的缓存
Expand All @@ -18,6 +19,34 @@ export class LocalStorageCache extends ICache {
* 缓存对象,默认使用 localStorage
*/
this.localStorage = window.localStorage
// 创建后将异步清空所有过期的缓存
this.clearExpired()
}
/**
* 清空所有过期的 key
* 注: 该函数是异步执行的
*/
async clearExpired () {
const local = this.localStorage
const len = local.length
const delKeys = []
for (let i = 0; i < len; i++) {
await wait(0)
const key = local.key(i)
const str = local.getItem(key)
const cacheVal = safeExec(JSON.parse, null, str)
if (cacheVal === null) {
continue
}
const { timeStart, timeout } = cacheVal.cacheOption
// 如果超时则删除并返回 null
// console.log(i, cacheVal, Date.now(), Date.now() - timeStart > timeout)
if (timeout !== TimeoutInfinite && Date.now() - timeStart > timeout) {
delKeys.push(key)
}
// console.log(i, key, local.getItem(key))
}
await delKeys.forEach(async key => local.removeItem(key))
}
/**
* 根据 key + value 添加
Expand All @@ -28,10 +57,7 @@ export class LocalStorageCache extends ICache {
* @override
*/
add (key, val, cacheOption) {
const result = this.get(
key,
assign({ timeStart: Date.now() }, cacheOption)
)
const result = this.get(key, cacheOption)
if (result !== null) {
return
}
Expand All @@ -55,18 +81,14 @@ export class LocalStorageCache extends ICache {
* @override
*/
set (key, val, cacheOption = new CacheOption()) {
const option = assign(
this.cacheOption,
{ timeStart: Date.now() },
cacheOption
)
const option = assign(this.cacheOption, cacheOption)
this.localStorage.setItem(
key,
JSON.stringify(
new CacheVal({
key,
val: option.serialize(val),
cacheOption: option,
cacheOption: { ...option, timeStart: option.timeStart || Date.now() },
})
)
)
Expand Down
63 changes: 53 additions & 10 deletions src/module/cache/LocalStorageCache.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { LocalStorageCache } from './LocalStorageCache'
import { wait } from '../function/wait'
import { repeatedCall } from '../function/repeatedCall'
import { TimeoutInfinite } from './CacheOption'
import { sleep } from '../function/sleep'

/**
* @test {LocalStorageCache}
Expand Down Expand Up @@ -30,16 +31,16 @@ describe('test LocalStorageCache', () => {
const v1 = '1'
// @ts-ignore
cache.add(k, v1, {
timeout: 100,
timeout: 10,
})
expect(cache.get(k)).toBe(v1)
await wait(120)
await wait(20)
expect(cache.get(k)).toBe(null)
// @ts-ignore
cache.add(k, v1, {
timeout: 100,
timeout: 10,
})
await wait(120)
await wait(20)
expect(cache.touch(k)).toBe(null)
})
it('test touch', async () => {
Expand All @@ -48,40 +49,82 @@ describe('test LocalStorageCache', () => {
const v1 = '1'
// @ts-ignore
cache.add(k, v1, {
timeout: 100,
timeout: 10,
})
await repeatedCall(4, async () => {
await wait(50)
await wait(5)
expect(cache.touch(k)).toBe(v1)
})
expect(cache.touch(k)).toBe(v1)
})
it('test global default CacheOption', async () => {
// @ts-ignore
const cache = new LocalStorageCache({
timeout: 100,
timeout: 10,
})
const k = '1'
const v1 = '1'
cache.add(k, v1)
expect(cache.get(k)).toBe(v1)
await wait(200)
await wait(20)
expect(cache.get(k)).toBe(null)
})
it('test use set CacheOption override global default CacheOption', async () => {
// @ts-ignore
const cache = new LocalStorageCache({
timeout: 100,
timeout: 10,
})
const k = '1'
const v1 = '1'
cache.add(k, v1, {
timeout: TimeoutInfinite,
})
expect(cache.get(k)).toBe(v1)
await wait(200)
await wait(20)
expect(cache.get(k)).toBe(v1)
})
it('test auto clear expired', async () => {
const cache = new LocalStorageCache()
// @ts-ignore
cache.set('1', 1, {
timeout: 10,
})
// @ts-ignore
cache.set('2', 1, {
timeout: 10,
})
await wait(10)
// 即便过了超时时间只要没有调用 get 依然存在于缓存中
expect(window.localStorage.getItem('1')).not.toBeNull()
expect(window.localStorage.getItem('2')).not.toBeNull()
// eslint-disable-next-line no-new
new LocalStorageCache()
await wait(10)
// 然而现在获取不到了
expect(window.localStorage.getItem('1')).toBeNull()
expect(window.localStorage.getItem('2')).toBeNull()
})
it('test auto clear expired for sleep', async () => {
const cache = new LocalStorageCache()
// @ts-ignore
cache.set('1', 1, {
timeout: 10,
})
// @ts-ignore
cache.set('2', 1, {
timeout: 10,
})
await wait(10)
// 即便过了超时时间只要没有调用 get 依然存在于缓存中
expect(window.localStorage.getItem('1')).not.toBeNull()
expect(window.localStorage.getItem('2')).not.toBeNull()
// eslint-disable-next-line no-new
new LocalStorageCache()
sleep(10)
// 然而现在还能获取到,因为 sleep 阻塞了主线程,使得构造函数中的清理过期缓存函数 clearExpired 没有机会运行(异步)
expect(window.localStorage.getItem('1')).not.toBeNull()
expect(window.localStorage.getItem('2')).not.toBeNull()
})

describe('test error', () => {
it('get error cache', () => {
Expand Down
9 changes: 9 additions & 0 deletions src/module/function/sleep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* 阻塞主线程指定时间
* 注: 和 {@link wait} 不同,该函数会真的阻塞住主线程,即这段时间内其他的代码都无法执行,例如用户的点击事件!
* @param {Number} time 阻塞毫秒数
*/
export function sleep (time) {
const start = performance.now()
while (performance.now() - start <= time) {}
}
20 changes: 20 additions & 0 deletions src/module/function/sleep.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { timing } from './timing'
import { sleep } from './sleep'
import { wait } from './wait'

/**
* @test {sleep}
*/
describe('test sleep', () => {
it('simple example', () => {
expect(timing(() => sleep(100))).toBeGreaterThanOrEqual(100)
})
it('test async queue', async () => {
let i = 0
wait(0).then(() => i++)
sleep(10)
expect(i).toBe(0)
await wait(0)
expect(i).toBe(1)
})
})
2 changes: 1 addition & 1 deletion src/module/tree/NodeBridge.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* 桥接对象为标准的树结构 {@link INode}
*/
export class INodeBridge {
export class NodeBridge {
/**
* 构造函数
* @param {Object} [options] 桥接对象
Expand Down
10 changes: 5 additions & 5 deletions src/module/tree/NodeBridgeUtil.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { bridge } from '../function/bridge'
import { INodeBridge } from './NodeBridge'
import { NodeBridge } from './NodeBridge'
// eslint-disable-next-line no-unused-vars
import { INode } from './Node'
import { treeMapping } from './treeMapping'
Expand All @@ -11,16 +11,16 @@ import { treeMapping } from './treeMapping'
export class NodeBridgeUtil {
/**
* 桥接对象为标准的树结构
* @param {INodeBridge} [nodeBridge=new INodeBridge()] 桥接对象
* @param {NodeBridge} [nodeBridge=new NodeBridge()] 桥接对象
* @returns {Function} 代理函数
*/
bridge (nodeBridge) {
return bridge(Object.assign(new INodeBridge(), nodeBridge))
return bridge(Object.assign(new NodeBridge(), nodeBridge))
}
/**
* 桥接一棵完整的树
* @param {INode} tree 树节点
* @param {INodeBridge} [nodeBridge=new INodeBridge()] 桥接对象
* @param {NodeBridge} [nodeBridge=new INodeBridge()] 桥接对象
* @returns {INode} 代理后的树对象
*/
bridgeTree (tree, nodeBridge) {
Expand All @@ -31,7 +31,7 @@ export class NodeBridgeUtil {
/**
* 桥接一个树节点列表
* @param {Array.<INode>} list 树节点列表
* @param {INodeBridge} [nodeBridge=new INodeBridge()] 桥接对象
* @param {NodeBridge} [nodeBridge=new NodeBridge()] 桥接对象
* @returns {Array.<INode>} 代理后的树节点列表
*/
bridgeList (list, nodeBridge) {
Expand Down
14 changes: 11 additions & 3 deletions src/module/tree/listToTree.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { returnItself } from '../function/returnItself'

/**
* 将列表转换为树节点
* 注: 该函数默认树的根节点只有一个,如果有多个,则返回一个数组
* @param {Array.<Object>} list 树节点列表
* @param {Object} [options] 其他选项
* @param {Function} [options.isRoot] 判断节点是否为根节点。默认根节点的父节点为空
* @param {Function} [options.bridge=returnItself] 桥接函数,默认返回自身
* @returns {Object|Array.<String>} 树节点,或是树节点列表
*/
export function listToTree (list, { isRoot = node => !node.parentId } = {}) {
const res = list.reduce((root, sub) => {
list.forEach(parent => {
export function listToTree (
list,
{ isRoot = node => !node.parentId, bridge = returnItself } = {}
) {
const res = list.reduce((root, _sub) => {
const sub = bridge(_sub)
list.forEach(_parent => {
const parent = bridge(_parent)
if (sub.parentId === parent.id) {
(parent.child = parent.child || []).push(sub)
}
Expand Down
18 changes: 18 additions & 0 deletions src/module/tree/listToTree.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,22 @@ describe('test listToTree', () => {
// @ts-ignore
expect(listToTree(list.map(bridgeNode))).toEqual(result)
})
it('custom field for node', () => {
const list = [
new CustomNode(1),
new CustomNode(2, 1),
new CustomNode(3, 2),
new CustomNode(4, 2),
new CustomNode(5, 1),
new CustomNode(6, 5),
new CustomNode(7, 5),
]

const bridge = nodeBridgeUtil.bridge({
id: 'uid',
parentId: 'parent',
})
// 转换时使用 bridge 代理 CustomNode 类
expect(listToTree(list, { bridge })).toEqual(result)
})
})

0 comments on commit 3d540dd

Please sign in to comment.