A tools lib for huya miniapp business development
@hyext/utils> 小程序业务开发工具库
``shell`
$ npm i @hyext/utils -S
`js`
import { memory, createLogger, createSDKPolyfill } from '@hyext/utils'
下述的每个函数都可以参照上方import导入。
#### 入参解析
`js
type CreateLoggerOptions = {
onBefore?: (logInfo: LogInfo) => void // log之前触发,传入一个LogInfo
onAfter?: (logInfo: LogInfo) => void // log之后触发,传入一个LogInfo
prefix?: (() => string) | string // 打印日志前缀
logColor?: LogColor // 每个接口的字体颜色配置
enableLog?: boolean | ((logInfo: LogInfo) => boolean) // 是否打印原生log,传入一个LogInfo
}
type LogInfo = {
prefix: string // CreateLoggerOptions.prefix中获取
msg: string // logger接口传入的msg
data: any // logger接口传入的data
desc: string // desc = prefix + msg
}
type LogColor = {
info?: string // 颜色,可以是颜色英文或hex: 例如:red, #cccccc
warn?: string
error?: string
log?: string
}
`
#### Demo
`jsminiappName * date:${Date.now()} uid: ${uid} sessionId:${sessionId} -
// create
const logger = createLogger({
onAfter(logInfo) {
global.hyext.logger.log(logInfo.desc + ' ' + JSON.stringify(logInfo.data))
},
prefix () {
return
},
enableLog(logInfo) {
// 例如我们在APP只通过onAfter钩子打印sdk的log,
// 禁止打印原生log,提高性能
if (platform !== 'web') return false
return true
}
})
// call
logger.log('oh my god~')
`
#### 参数解析
`js
type SDKPolyfillOptions = {
paths: string[] // 接口访问路径队列,例如: hyExt.advance.sendWup -> paths => ['advance.sendWup']
SDK: SDKModel // 传入global.hyExt
onError?: (errMsg: string, apiName: string, path: string) => void // SDK调用失败时 触发
onCall?: (calledArgs: Array
onSuccess?: (res: any, apiName: string, path: string) => void // SDK调用成功时 触发
}
type SDKModel = {
[key: string]: any
}
`
js
// 与logger配合使用的例子,甩锅神器
const ployfillSDK = createSDKPolyfill({
SDK: global.hyExt,
paths: [
'advance.sendWup'
],
onCall(callArgs, apiName, path) {
logger.log(SDK.${path}开始调用, callArgs)
},
onError(errMsg, apiName, path) {
logger.log(SDK.${path}调用失败, errMsg)
},
onSuccess(res, apiName, path) {
logger.log(SDK.${path}调用成功, res)
},
onPolyfill(polyfillSDK, apiName, apiFN) {
// 补丁前触发, 可以拦截sdk进行二次处理。
polyfillSDK[apiName] = jest.fn(apiFN)
}
})// ployfillSDK的每个接口只会resolve,因为内部已被catch错误,发生错误res是false
ployfillSDK.sendWup(options).then((res) => {
if (!res) return; // 发生错误就跳过了
// do something
})
`高阶函数类
$3
背景: 小程序SDK原来的setMode方法, resolve后小程序马上会被显示. 但是在新版APP上, setMode resolve 并不表示马上会显示, 而是会在恰当的时候通过 hyExt.popup.onNoticeShow 通知小程序. 这个变更会给部分小程序带来影响, 比如你要10秒后关闭浮窗, 并不能在调用setMode之后开始倒计时, 而是在 onNoticeShow 回调中开始倒计时.
本函数封装了SDK中的以下方法:
- setMode
- onNoticeShow/offNoticeShow
- onNoticeHide/offNoticeHide
getSetModeWithQueueFn 每次调用都会取消监听 noticeShow/noticeHide 事件, 再重新进行监听. 调用后返回 setModeWithQueue 函数
setModeWithQueue 函数是对setMode的封装, 用于支持 notice 排队.
对比setMode, 添加两个额外的参数:
- onShow: 如果当前mode是NOTICE, 那么当收到终端的展示事件时, 会调用这个函数. 如果当前mode不是NOTICE, 则在 setMode resolve 后调用这个函数
- onNoticeHide: 当notice超时被终端隐藏, 或者调用了setMode 切换到非NOTICE模式时 , 会调用这个函数.
setModeWithQueue 兼容性:
- 可以兼容老版本APP, 在老版本APP上和原始的 setMode 方式行为一致
- 可以兼容不同的mode, 'NOTICE'/'NORMAL'/'RIGHT_BOTTOM_BTN'
#### Demo
`ts
const setModeWithQueue = getSetModeWithQueueFn(msg => console.log(msg))const App = () => {
// 收到推送后才显示notice
const dataPush = useMainSelector(s => s.dataPush)
const [didShow, setShow] = useState(false)
useEffect(() => {
setModeWithQueue({ mode: dataPush ? 'NOTICE' : 'NORMAL' }, onShow: () => {console.log('show')})
}, [dataPush])
if (!dataPush) return null
return (
我迟早会显示
)
}
`#### 参数
`ts
export function getSetModeWithQueueFn(logger: (msg: string) => void): SetModeWithQueueFn;type SetModeWithQueueFn = (params: {
mode: string,
onShow?: Callback,
onNoticeHide?: Callback,
showTime?: number, // 期望展示的时间
liveroomPopupKey?: string,
waitAfterResetNormal?: number, // 重置为NORMAL后, 等待多久才调用setMode设置其他值, 默认800ms
skipResetNormal?: boolean, // 是否跳过重置为NORMAL, 默认false
} & { [key: string]: any }) // 其他需要透传给hyExt.popup.setMode的参数
=> Promise
`
$3
- createPromisfyFnWithCatch(options: PromisfyFnWithCatchOptions) - hack一个promisfy函数的Pending、Resolve、Reject过程,返回一个新promisfy函数。#### 参数解析
`js
type PromisfyFn = (...args: Array) => Promisetype PromisfyFnWithCatchOptions = {
executePromiseFn: PromisfyFn,
onPending?: (callArgs: Array) => void
onResolve?: (response: any) => void
onReject?: (error: Error) => void
}
` #### Demo
`js
// __tests__/func.test.ts
const mockFn = createMockFn(() => Promise.resolve({b: 'bar'}))
const mockCall = { a: 'foo' }const fn = createPromisfyFnWithCatch({
executePromiseFn: mockFn,
onPending(args: any) {
expect(args[0]).toBe(mockCall)
},
onResolve(res: any) {
expect(res).toMatchObject({b: 'bar'})
}
})
fn(mockCall).then((res) => {
expect(res).toBeTruthy()
expect(mockFn.mock.calls.length).toBe(1)
done()
})
`$3
- once(fn) - 返回一个cache函数,缓存fn首次调用的结果,fn只会执行一次。
`js
// __tests__/func.test.ts
const mockFn = createMockFn(() => true)
const mockFnOnce = once(mockFn)const result1 = mockFnOnce()
const result2 = mockFnOnce()
expect(mockFn.mock.calls.length).toBe(1)
expect(result1 === result2).toBe(true)
`$3
- createPolling(options: PollingOptions) - 返回一个轮询函数
#### 参数解析
`js
type PollingOptions = {
intervalTime: number // 轮询周期。
fn: (...args: Array) => boolean // 执行函数,返回一个是否继续轮询的标记,true代表继续,false代表结束。
immediately?: boolean // 默认是true, 轮询函数调用就马上执行fn;false,要等到intervalTime时间到达时执行fn。
onEnd?: NormalFn // 轮询结束时调用。
}
`#### Demo
`js
// __tests__/func.test.ts
let callCount = 0
const mockFn = createMockFn((res: any) => {
expect(res).toBe(1)
callCount += 1
return callCount > 1 ? false : true
})const pollingFn = createPolling({
intervalTime: 500,
fn: mockFn,
onEnd() {
expect(callCount).toBe(2)
done()
}
})
pollingFn(1)
`$3
- throttle(delay: number, fn: NormalFn) - 返回一个防抖函数, delay代表延时触发的时间,fn代表执行函数#### Demo
`js
// __tests__/func.test.ts
const mockFn = createMockFn((res:any) => {
expect(res).toBe(1)
expect(mockFn.mock.calls.length).toBe(1)
done()
})
const throttleFn = throttle(1000, mockFn)throttleFn(1)
throttleFn(1)
`$3
- memory(fn) - 返回一个缓存每次调用的结果并输出结果的函数,fn代表纯函数,入参成员必须是 number | string, 输出可以是any。
#### Demo
`js
// __tests__/func.test.ts
const mockFn = createMockFn((str: string, num: number) => {
return str + num
})
const cacheFn = memory<[string, number], string>(mockFn)
const result1 = cacheFn('alex', 1)
const result2 = cacheFn('alex', 1)
expect(result1 === result2).toBe(true)
expect(mockFn.mock.calls.length).toBe(1)
})
`Promise风格函数
$3
- promiseTimeout(ms: number, promise: Promise) - 返回一个promise,超时会reject一个超时字符串。
#### Demo
`js
it('promiseTimeout pass', (done) => {
const passPromise = createDelayPromise(200);
promiseTimeout(500, passPromise).then((res) => {
expect(res).toBe(true)
done()
}).catch(done)
}) it('promiseTimeout timeout', (done) => {
const failPromise = createDelayPromise(200);
const timeout = 100
promiseTimeout(timeout, failPromise).catch((err) => {
expect(err).toMatch(
Timed out in ${timeout}ms.)
done()
})
})
`$3
- promiseTimeout(maxExeCount: number, interval: number,
cb: RetryHandleCallback) - 返回一个promise,超过调用数会reject一个错误。
#### 参数解析
`js
type RetryResult = {
isDone: boolean
payload: any
}type RetryHandleCallback = (currExeCount: number) => Promise
`$3
`js
it('retry resolve', (done) => {
const fn = jest.fn(async (count) => {
return {
isDone: count === 3 ? true : false,
payload: { foo: 'bar' }
}
}) const promise = promiseRetry(3, 100, fn)
promise.then((payload) => {
expect(fn).toBeCalledTimes(3)
expect(payload).toMatchObject({ foo: 'bar' })
done()
}).catch(done)
})
``