import getGlobal from './getGlobal.mjs'
import isWindow from './isWindow.mjs'
import isnum from './isnum.mjs'
import getTimeHp from './getTimeHp.mjs'
import pm2resolve from './pm2resolve.mjs'
import getTrace from './getTrace.mjs'
//logErr, 記錄本身亦可能拋錯(如值之util.inspect.custom會拋), 須包try
function logErr(err) {
try {
console.log(err)
}
catch (e) {}
}
/**
* 高精度setInterval
*
* Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/setIntervalHp.test.mjs Github}
* @memberOf wsemi
* @param {Function} fun 輸入調用函數
* @param {Number} [interval=1] 輸入時間間隔,須為正有限數(通常為正整數,數字字串亦可),單位為毫秒ms,預設1;0、負數、NaN、Infinity、非數字或空白字串等無效值時不啟動,見回傳說明
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Function} [opt.onTrace=null] 輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'start'時為開始計時(無其他欄位),'fire'時為已距上次調用(首次為建立)達interval而即將調用fun(含n為第幾次調用,由1起算,elapsed為距上次調用(首次為建立)實際經過之毫秒數,必不小於interval),'result'時為該次調用已有結果而即將記錄(含n為對應之fire次數,state為'success'、'error'(同步拋錯或Promise被reject,即將console.log記錄)、'cancelled'或'invalid'(fun非函數,即將console.log記錄)),'stop'時為呼叫停止函數(僅首次通知);停止後不再通知(含停止前已調用而尚未有結果者);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響計時與調用,預設null
* @returns {Function} 回傳停止函數,呼叫後不再調用fun。interval為無效值時不啟動計時、不調用fun,以console.log記錄Error('interval is not a positive finite number')一次,此時仍回傳停止函數(呼叫無作用)。fun同步拋錯或其Promise被reject時以console.log記錄而不外拋,不影響後續調用;fun非函數時每次到期皆以console.log記錄Error('fn is not a function'),直至呼叫停止函數。到期即調用fun,不等待前一次調用之Promise完成(與原生setInterval相同),故fun為非同步函數且執行時間長於interval時,各次調用會重疊執行,須避免重疊者請於fun內自行處理(如以旗標略過)
* @example
*
* let i = 0
* let t = setIntervalHp(() => {
* i++
* //content
* if (i > 10) {
* t()
* }
* }, 50)
*
*/
function setIntervalHp(fun, interval = 1, opt = {}) {
//interval, 須為正有限數(數字字串亦可), 無效時不啟動並以console.log記錄(同fun非函數之記錄方式), 仍回傳停止函數(呼叫無作用)
let ms = isnum(interval) ? Number(interval) : NaN
if (!(ms > 0 && Number.isFinite(ms))) {
logErr(new Error('interval is not a positive finite number'))
return () => {}
}
interval = ms
//trace, 觀察回調(opt.onTrace), 未給時為null
let trace = getTrace(opt)
//g
let g = getGlobal()
//iswin
let iswin = isWindow()
//exec
let execNode = (fun) => {
g.setTimeout(fun, 1)
}
let execBrowser = (fun) => {
g.requestAnimationFrame(fun)
}
let exec = null
if (iswin) {
exec = execBrowser
}
else {
exec = execNode
}
//gtm
let gtm = getTimeHp()
//running
let running = true
//startTime
let startTime = gtm()
// console.log('startTime', startTime)
//nFire, 已通知之調用次數, 僅trace時使用
let nFire = 0
//loop
let loop = () => {
//check
if (!running) {
return
}
//currentTime
let currentTime = gtm()
// console.log('currentTime', currentTime)
//elapsed
let elapsed = currentTime - startTime
//check
if (elapsed >= interval) {
//n, 本次調用之序號, 僅trace時使用
let n = 0
if (trace) {
nFire++
n = nFire
trace('fire', { n, elapsed })
}
//fun, 同步拋錯與其Promise被reject皆以console.log記錄而不外拋(不產生未處理之rejection), 同debounce
try {
pm2resolve(fun)()
.then((res) => {
if (trace && running) {
trace('result', { n, state: res.state })
}
if (res.state === 'error') {
logErr(res.msg)
}
})
}
catch (err) {
if (trace && running) {
trace('result', { n, state: 'invalid' })
}
logErr(err) //fun非函數時pm2resolve同步拋錯
}
//update
startTime = gtm()
}
//continue
exec(loop)
}
//start
if (trace) {
trace('start', {})
}
exec(loop)
//stop
let stop = () => {
if (trace && running) {
trace('stop', {})
}
running = false
}
return stop
}
export default setIntervalHp