import size from 'lodash-es/size.js'
import isfun from './isfun.mjs'
import ispint from './ispint.mjs'
import cint from './cint.mjs'
import pm2resolve from './pm2resolve.mjs'
import getTrace from './getTrace.mjs'
/**
* 函數去除抖動
*
* 呼叫無回傳值,任務不被等待;任務同步拋錯或其Promise被reject時以console.log記錄而不外拋(不產生未處理之rejection),不影響後續佇列;輸入非函數時不排入佇列(不影響已排入之任務),以console.log記錄'func is not a function'
*
* Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/debounce.test.mjs Github}
* @memberOf wsemi
* @param {Integer} [ms=300] 輸入未有調用的時間區間,為正整數,預設300ms
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Function} [opt.onTrace=null] 輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'push'時為任務排入佇列(含size為排入後之佇列長度),'start'時為開始偵測(每10ms檢查一次是否已超過ms未有調用),'run'時為已超過ms未有調用而即將執行最後之任務(含dropped為被捨棄之較早任務數),'stop'時為佇列已空而停止偵測;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響去除抖動,預設null
* @example
*
* async function topAsync() {
*
* function test1() {
* return new Promise((resolve, reject) => {
* let ms = []
*
* let dbc = debounce(300)
*
* let i = 0
* function core(name) {
* i++
* ms.push({ name, i })
* console.log({ name, i })
* }
*
* setTimeout(function() {
* dbc(() => {
* core('A')
* })
* }, 100)
* setTimeout(function() {
* dbc(() => {
* core('B')
* })
* }, 200)
* setTimeout(function() {
* dbc(() => {
* core('C')
* })
* }, 250)
* setTimeout(function() {
* dbc(() => {
* core('D')
* })
* }, 350)
* setTimeout(function() {
* dbc(() => {
* core('E')
* })
* }, 400)
* setTimeout(function() {
* resolve(ms)
* }, 800)
* })
* }
* console.log('test1')
* let r1 = await test1()
* console.log(JSON.stringify(r1))
* // test1
* // { name: 'E', i: 1 }
* // [{"name":"E","i":1}]
*
* function test2() {
* return new Promise((resolve, reject) => {
* let ms = []
*
* let dbc = debounce(300)
*
* let i = 0
* function core(name) {
* i++
* ms.push({ name, i })
* console.log({ name, i })
* }
*
* setTimeout(function() {
* dbc(() => {
* core('A')
* })
* }, 50)
* setTimeout(function() {
* dbc(() => {
* core('B')
* })
* }, 100)
* setTimeout(function() {
* dbc(() => {
* core('C')
* })
* }, 150)
* setTimeout(function() {
* dbc(() => {
* core('D')
* })
* }, 500)
* setTimeout(function() {
* dbc(() => {
* core('E')
* })
* }, 550)
* setTimeout(function() {
* resolve(ms)
* }, 1400)
* })
* }
* console.log('test2')
* let r2 = await test2()
* console.log(JSON.stringify(r2))
* // test2
* // { name: 'C', i: 1 }
* // { name: 'E', i: 2 }
* // [{"name":"C","i":1},{"name":"E","i":2}]
*
* }
* topAsync().catch(() => {})
*
*/
function debounce(ms = 300, opt = {}) {
function ClsDebounce(ms) {
let q = [] //queue
let t = null //timer
let tLast = Date.now() //先初始化, 之後由每次run覆蓋
//ms
if (!ispint(ms)) {
ms = 300
}
ms = cint(ms)
//trace, 觀察回調(opt.onTrace), 未給時為null
let trace = getTrace(opt)
function detect() {
if (t !== null) {
return
}
t = setInterval(() => {
//console.log('q', q)
//check
if (size(q) === 0) {
clearInterval(t)
t = null
if (trace) {
trace('stop', {})
}
return
}
//tDiff
let tDiff = Date.now() - tLast
//check
if (tDiff > ms) { //超過指定延時則呼叫指定func
//取最後的任務與清空佇列
let m = q.pop()
if (trace) {
trace('run', { dropped: q.length })
}
q = []
//執行最後任務, 不論成功失敗都須持續執行至無佇列
//有可能為sync或async, async時不await, 須持續執行至無佇列; 結果經pm2resolve正規化, 同步拋錯與非同步reject一律記錄, 不使計時器回呼拋錯亦不產生未處理之rejection
pm2resolve(m.func)(...m.input)
.then((res) => {
if (res.state === 'error') {
//記錄本身亦可能拋錯(如值之util.inspect.custom會拋), 須包try, 否則成為未處理之rejection(同evEmit之通報)
try {
console.log('m.func catch', res.msg)
}
catch (err) {}
}
})
}
}, 10) //10ms偵測, 啟動後跑timer, 無佇列則會停止減耗
if (trace) {
trace('start', {})
}
}
function run(func, ...input) {
//check
if (!isfun(func)) {
console.log('func is not a function')
return
}
//save
tLast = Date.now()
q.push({ func, input })
if (trace) {
trace('push', { size: q.length })
}
//detect
detect()
}
return run
}
return new ClsDebounce(ms)
}
export default debounce