import cst from './_const.mjs'
import get from 'lodash-es/get.js'
import genPm from './genPm.mjs'
import isfun from './isfun.mjs'
import ispint from './ispint.mjs'
import ispm from './ispm.mjs'
import getTrace from './getTrace.mjs'
/**
* 等待f函數回傳true
*
* Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/waitFun.test.mjs Github}
* @memberOf wsemi
* @param {Function} fun 輸入判斷用函數
* @param {Object} opt 輸入設定物件,預設{}
* @param {Integer} [opt.attemptNum=200] 輸入最大嘗試次數,為正整數,預設200,以輪詢週期計(立即呼叫不計),計數超過attemptNum後於該次呼叫完成時仍未成立即reject,故fun最多被呼叫attemptNum+2次(立即1次與輪詢)
* @param {Integer} [opt.timeInterval=1000] 輸入嘗試時間週期,為正整數,單位為ms,預設1000;每個週期計1次,前一次呼叫未完成時該週期不呼叫fun(輪詢不重疊)但仍計入嘗試次數
* @param {Function} [opt.onTrace=null] 輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, n, ... },n為當下已計之週期數(立即呼叫為0),供測試以事件(而非等待時間)同步與診斷;type為'call'時為即將呼叫fun,'result'時為fun已完成(含ok為其回傳是否為true),'tick'時為每個週期(含busy為前一次呼叫是否仍未完成而本週期不呼叫),'stop'時為計數超過attemptNum而前一次呼叫仍未完成時停止計時器,'resolve'時為即將resolve,'reject'時為即將reject(含reason為'error'fun拋錯或reject、'exceeded'超過最大嘗試次數、'invalid'fun非函數);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響輪詢與結果,預設null
* @returns {Promise} 回傳Promise,resolve回傳為空代表f函數回傳true;fun拋錯或其Promise被reject時(立即呼叫與輪詢皆同)停止輪詢並reject該錯誤,超過最大嘗試次數時reject字串'exceeded attemptNum[最大嘗試次數]';fun非函數時reject字串'waitfunction需輸入函數f';fun回傳永不settle之Promise時亦不settle(不再累積呼叫),計數超過attemptNum後停止計時器(本函數仍不settle,但不使行程無法結束),需逾時者以pmTimeout包裝
* @example
*
* async function topAsync() {
*
* function test1() {
* return new Promise((resolve, reject) => {
* let ms = []
*
* let i = 0
* waitFun(function() {
* i++
* console.log('waiting: ' + i)
* ms.push('waiting: ' + i)
* return i >= 2
* })
* .then(function() {
* console.log('test1 then')
* ms.push('test1 then')
* })
*
* setTimeout(function() {
* resolve(ms)
* }, 1100)
*
* })
* }
* console.log('test1')
* let r1 = await test1()
* console.log(JSON.stringify(r1))
* // test1
* // waiting: 1
* // waiting: 2
* // test1 then
* // ["waiting: 1","waiting: 2","test1 then"]
*
* function test2() {
* let ms = []
* let i = 0
*
* let f = () => {
* return new Promise((resolve, reject) => {
* setTimeout(function() {
* i++
* console.log('waiting: ' + i)
* ms.push('waiting: ' + i)
* resolve(i >= 2)
* }, 1100)
* })
* }
*
* return waitFun(f)
* .then(function() {
* console.log('test2 then')
* ms.push('test2 then')
* return ms
* })
*
* }
* console.log('test2')
* let r2 = await test2()
* console.log(JSON.stringify(r2))
* // test2
* // waiting: 1
* // waiting: 2
* // test2 then
* // ["waiting: 1","waiting: 2","test2 then"]
*
* }
* topAsync().catch(() => {})
*
*/
async function waitFun(fun, opt = {}) {
let r = null
//pm
let pm = genPm()
//trace, 觀察回調(opt.onTrace), 未給時為null
let trace = getTrace(opt)
//check
if (!isfun(fun)) {
if (trace) {
trace('reject', { n: 0, reason: 'invalid' })
}
pm.reject('waitfunction需輸入函數f')
return pm
}
//func
let func = async () => {
let r = fun()
if (ispm(r)) {
r = await r
}
return r
}
//immediate call
if (trace) {
trace('call', { n: 0 })
}
try {
r = await func()
}
catch (err) {
if (trace) {
trace('reject', { n: 0, reason: 'error' })
}
throw err
}
if (trace) {
trace('result', { n: 0, ok: r === true })
}
if (r === true) {
if (trace) {
trace('resolve', { n: 0 })
}
pm.resolve()
return pm
}
//attemptNum
let attemptNum = get(opt, 'attemptNum', null)
if (!ispint(attemptNum)) {
attemptNum = 200
}
//timeInterval
let timeInterval = get(opt, 'timeInterval', null)
if (!ispint(timeInterval)) {
timeInterval = 1000
}
timeInterval = Math.min(timeInterval, cst.TIMER_TIME_MAX) //夾至計時器上限, 見_const.mjs
//setInterval, 每個週期計1次; 前一次呼叫未完成時該週期只計次不呼叫fun(輪詢不重疊), 逾次仍於呼叫完成時判定
let n = 0
let running = false
let t = setInterval(async() => {
n += 1
//console.log('waitFun: ', n)
//check
if (running) {
if (trace) {
trace('tick', { n, busy: true })
}
if (n > attemptNum) {
clearInterval(t) //已逾次則停止計時器, 逾次判定仍由執行中之該次呼叫完成時為之; 使fun永不settle時計時器不續跑而行程可自然結束
if (trace) {
trace('stop', { n })
}
}
return
}
running = true
if (trace) {
trace('tick', { n, busy: false })
trace('call', { n })
}
//輪詢中fun拋錯或其Promise被reject, 同立即呼叫之處理: 停止輪詢並reject該錯誤, 不得使計時器之async回呼reject(未處理之rejection且輪詢永不停止)
try {
r = await func()
}
catch (err) {
clearInterval(t)
if (trace) {
trace('reject', { n, reason: 'error' })
}
pm.reject(err)
return
}
finally {
running = false
}
if (trace) {
trace('result', { n, ok: r === true })
}
if (r === true) {
//console.log('resolve', n)
clearInterval(t)
if (trace) {
trace('resolve', { n })
}
pm.resolve()
return
}
if (n > attemptNum) {
//console.log('reject', n, attemptNum)
clearInterval(t)
if (trace) {
trace('reject', { n, reason: 'exceeded' })
}
pm.reject(`exceeded attemptNum[${attemptNum}]`) //已超過最大次數
}
}, timeInterval)
return pm
}
export default waitFun