wsemi

wsemi

new wsemi()

Source:

Members

(static) execCliCore

Description:
  • execCli系列函數之共用核心, 提供可獨立使用之函數: buildSpawnArgs(將命令轉為Windows安全之spawn參數)與buildSpawnEnv(建立子進程環境變數)

    Unit Test: Github

Source:

execCli系列函數之共用核心, 提供可獨立使用之函數: buildSpawnArgs(將命令轉為Windows安全之spawn參數)與buildSpawnEnv(建立子進程環境變數)

Unit Test: Github

Example
//need test in nodejs

let { buildSpawnArgs, buildSpawnEnv } = execCliCore

//buildSpawnArgs, Windows下npm全域命令(.cmd)會被解析為以node執行其JS入口
let sp = buildSpawnArgs('node', ['-e', 'console.log(1)'])
console.log(sp)
// => { file: 'C:\\...\\node.exe', args: [ '-e', 'console.log(1)' ], opt: {} } (Windows)
// => { file: 'node', args: [ '-e', 'console.log(1)' ], opt: {} } (非Windows)

//spawn
import { spawn } from 'child_process'
let proc = spawn(sp.file, sp.args, { stdio: 'pipe', windowsHide: true, ...sp.opt })

//buildSpawnEnv, 以process.env為底併入額外變數, 不動本進程
let env = buildSpawnEnv({ API_KEY: 'abc' })
console.log(env.API_KEY, env.PYTHONIOENCODING, process.env.API_KEY)
// => abc utf-8 undefined

Methods

(static) ab2blob(ab, optopt) → {Blob|Object}

Description:
  • ArrayBuffer資料轉Blob資料

    Unit Test: Github

Source:
Example
need test in browser

let ab = (new Uint8Array([66, 97, 115])).buffer
let bb = ab2blob(ab)
console.log(bb)
// => Blob {size: 3, type: ""}

console.log(ab2blob('abc', { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid ab' }
Parameters:
Name Type Attributes Default Description
ab ArrayBuffer

輸入ArrayBuffer資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Blob資料,輸入非ArrayBuffer時回傳空Blob;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Blob | Object

(static) ab2u8arr(ab, optopt) → {Unit8Array|Object}

Description:
  • ArrayBuffer轉Unit8Array

    Unit Test: Github

Source:
Example
let ab = (new Uint8Array([66, 97, 115])).buffer
console.log(ab2u8arr(ab))
// => new Uint8Array([66, 97, 115])
Parameters:
Name Type Attributes Default Description
ab ArrayBuffer

輸入ArrayBuffer

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Unit8Array,輸入非ArrayBuffer時回傳空Unit8Array;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Unit8Array | Object

(static) addExcelWorksheetFromData(wb, data, sheetNameopt) → {Object}

Description:
  • 由數據陣列轉成為Excel的Worksheet物件並添加至Workbook物件

    Workbook物件格式為{ sheets: [{ name, rows }] }(見createExcelWorkbook),數據陣列可為二維陣列(mat)或物件陣列(ltdt),見getExcelWorksheetFromData

    Unit Test: Github

Source:
Example
let data = [
    ['a', '123', 456],
    [null, 'abc123', '', 111.222333],
]

let wb = createExcelWorkbook()
console.log(wb)
// => { sheets: [] }

wb = addExcelWorksheetFromData(wb, data, 'tester')
console.log(JSON.stringify(wb))
// => {"sheets":[{"rows":[["a","123",456],[null,"abc123","",111.222333]],"name":"tester"}]}
Parameters:
Name Type Attributes Default Description
wb Object

輸入Excel的Workbook物件

data Array

輸入數據陣列,可為二維陣列(mat)或物件陣列(ltdt)

sheetName String <optional>
'data'

輸入輸出為Excel時所在分頁(sheet)名稱字串,預設為'data'

Returns:

回傳添加分頁後的Workbook物件,輸入無效時回傳{ error }物件

Type
Object

(static) addMonth(g, n) → {String}

Description:
  • 輸入年月並增加/減少月份數量

    Unit Test: Github

Source:
Example
console.log(addMonth('2019-01', 2))
// => '2019-03'

console.log(addMonth('2018-12', -1))
// => '2018-11'
Parameters:
Name Type Description
g String

輸入月時間字串

n Integer

輸入要增加的月份數量整數,可為正負值

Returns:

回傳新的月時間字串

Type
String

(static) aes2str(str, key, base64opt, optopt) → {String|Object}

Description:
  • AES字串轉一般字串 為str2aes之解密函數,使用AES-128-CBC解密,字串採用PKCS#7填充 str與base64須與加密時str2aes所用之設定相同,否則解不出原文而回傳空字串

    Unit Test: Github

Source:
Example
let str = '53616c7465645f5f47214797ac01bc03cceb69ebced4948501ab94ca9644a6dfd277456aead4432cb9c9d74c38c42c79'
let key = '1234567890abcdefghijk'
console.log(aes2str(str, key))
// => 'test中文abcdefghijklmn'
Parameters:
Name Type Attributes Default Description
str String

輸入AES加密字串,非有效字串時回傳空字串

key String

輸入加密key,非有效字串時回傳空字串

base64 Boolean <optional>
false

輸入來源字串是否為base64格式,須與加密時str2aes所用之設定相同,非布林值時回退為false,預設為false

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳經AES解密後字串,str或key非有效字串、或key錯誤而解不出明文時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件,可據此分辨解密失敗與明文為空

Type
String | Object

(static) alive(timeAliveopt, timeDetectopt) → {Object}

Description:
  • 偵測單元是否在線

    Unit Test: Github

Source:
Example
async function topAsync() {

    function test1() {
        return new Promise((resolve, reject) => {
            let ms = []

            let oAL = alive({ timeAlive: 1500 })
            let t = Date.now()

            let a = { data: 123 }
            let b = { data: '34.56' }

            setTimeout(() => {
                console.log(parseInt((Date.now() - t)) + 'ms', 'trigger a1')
                oAL.trigger('a', a)
            }, 500)

            setTimeout(() => {
                console.log(parseInt((Date.now() - t)) + 'ms', 'trigger a2')
                oAL.trigger('a', a)
            }, 1900)

            setTimeout(() => {
                console.log(parseInt((Date.now() - t)) + 'ms', 'trigger b1')
                oAL.trigger('b', b)
            }, 1000)

            setTimeout(() => {
                console.log(parseInt((Date.now() - t)) + 'ms', 'trigger b2')
                oAL.trigger('b', b)
            }, 3000)

            oAL.on('message', function({ eventName, key, data, now }) {
                console.log(parseInt((Date.now() - t)) + 'ms', { eventName, key, data, now })
                ms.push(eventName + '|' + key)
            })

            setTimeout(() => {
                resolve(ms)
            }, 5000)

        })
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // 503ms trigger a1
    // 508ms { eventName: 'enter', key: 'a', data: { data: 123 }, now: 1 }
    // 1001ms trigger b1
    // 1003ms { eventName: 'enter', key: 'b', data: { data: '34.56' }, now: 2 }
    // 1901ms trigger a2
    // 2523ms { eventName: 'leave', key: 'b', data: { data: '34.56' }, now: 1 }
    // 3002ms trigger b2
    // 3004ms { eventName: 'enter', key: 'b', data: { data: '34.56' }, now: 2 }
    // 3430ms { eventName: 'leave', key: 'a', data: { data: 123 }, now: 1 }
    // 4544ms { eventName: 'leave', key: 'b', data: { data: '34.56' }, now: 0 }
    // ["enter|a","enter|b","leave|b","enter|b","leave|a","leave|b"]

    function test2() {
        return new Promise((resolve, reject) => {
            let ms = []

            let oAL = alive({ timeAlive: 1500 })

            oAL.on('message', function({ eventName, key }) {
                ms.push(eventName + '|' + key)
            })

            oAL.trigger('a', { data: 123 })

            setTimeout(() => {
                oAL.trigger('b', { data: '34.56' })
                let r = oAL.clear() //b剛trigger尚未發出enter, 故只回傳a, 且b之enter不再發出
                ms.push('clear|' + r.map((v) => v.key).join(','))
            }, 500)

            setTimeout(() => {
                resolve(ms)
            }, 2500)

        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // ["enter|a","clear|a"]

}
topAsync().catch(() => {})
Parameters:
Name Type Attributes Default Description
timeAlive Integer <optional>
10000

輸入判斷單元是否斷線之延時整數,單位為毫秒ms,預設為10000

timeDetect Integer <optional>
50

輸入偵測佇列間隔時間整數,單位為毫秒ms,預設為50

opt.onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'enter'或'leave'時為即將發出該message事件(含key、now同事件內容),'tick'時為每次偵測佇列(含size為偵測前之單元數),'stop'時為佇列已空而停止偵測;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響偵測,clear後不再呼叫,預設null

Returns:

回傳事件物件,可呼叫函數on、trigger、get、clear。trigger給予單元的唯一key字串與攜帶數據data物件,on為監聽事件,需自行監聽message事件取得單元進出事件。get事件可取得alive內視為存活的單元清單(含剛trigger而尚未發出enter者)。clear為釋放:立即清除偵測計時器、取消尚未發出之enter、清空佇列,本身不發事件,其後不再發出任何事件、trigger無效、get回傳[],可重複呼叫(釋放為終態,需重新偵測者另建alive);clear回傳已發出enter且尚未leave之單元陣列[{ key, data }],供使用端補發離線通知,再次呼叫回傳[];監聽器於派發中呼叫clear亦安全,同一輪尚未處理之單元不再派發。事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯,故其不會使行程崩潰,會改以error事件回報{ fun: 'listener', name, msg, args },無error監聽者則console.error;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection

Type
Object

(static) arr2dt(keys, valuesopt) → {Object}

Description:
  • 結合keys與values成為物件

    Unit Test: Github

Source:
Example
console.log(arr2dt(['a', 'b']))
// => { 'a': '', 'b': '' }

console.log(arr2dt(['a', 'b'], 'xyz'))
// => { a: 'xyz', b: 'xyz' }

console.log(arr2dt(['a', 'b'], null))
// => { a: null, b: null }

console.log(arr2dt(['a', 'b'], [12.3, '456a']))
// => { 'a': 12.3, 'b': '456a' }

console.log(arr2dt(['a', 'b'], [null, '456a']))
// => { 'a': null, 'b': '456a' }

console.log(arr2dt(['a', 'b'], [12.3]))
// => {}

console.log(arr2dt(['a', 'b'], {}))
// => { a: {}, b: {} }
Parameters:
Name Type Attributes Default Description
keys Array

輸入keys字串陣列

values Array | * <optional>
''

輸入values物件陣列或需自動展開values成為的陣列,預設''

Returns:

回傳組合後物件

Type
Object

(static) arrAccum(vec) → {Array}

Description:
  • 回傳累加後陣列

    Unit Test: Github

Source:
Example
console.log(arrAccum([1, 2, 3, 4]))
// => [1, 3, 6, 10]
Parameters:
Name Type Description
vec Array

輸入要被累加計算的陣列

Returns:

回傳各元素累加後之陣列

Type
Array

(static) arrAdd(arguments) → {Array}

Description:
  • 各陣列內元素相加,可輸入n個同長度陣列,若需輸入之陣列長度不同則回傳空陣列

    Unit Test: Github

Source:
Example
let v1 = [1, 2, 3, 4]
let v2 = [0.1, 0.1, 0.1, 0.1]
let v3 = [11, 22, 33, 44]

console.log(arrAdd(v1, v2))
// => [ 1.1, 2.1, 3.1, 4.1 ]

console.log(arrAdd(v1, v2, v3))
// => [ 12.1, 24.1, 36.1, 48.1 ]
Parameters:
Name Type Description
arguments Array

輸入n個陣列,需同長度,其內元素皆會轉為浮點數,各陣列針對各元素進行相加總

Returns:

回傳各元素相加後陣列

Type
Array

(static) arrAt(vall, istart, iendopt) → {Array}

Description:
  • 由vall陣列提取指定欄位(或欄位範圍)之資料 若istart與iend有效, 提取istart~iend欄位 若僅istart有效, 提取istart欄位

    Unit Test: Github

Source:
Example
console.log(arrAt([1, 2, 3, '4', 5, 'abc'], 1))
// => [2]

console.log(arrAt([1, 2, 3, '4', 5, 'abc'], 1, 4))
// => [2, 3, '4', 5]
Parameters:
Name Type Attributes Default Description
vall Array

輸入要被提取的任意資料陣列

istart Integer

輸入起始的欄位指標整數

iend Integer <optional>
null

輸入結束的欄位指標整數,若不使用則等同於istart,預設null

Returns:

回傳提取的任意資料陣列

Type
Array

(static) arrDiff(strOld, strNew, optopt) → {Object}

Description:
  • 比對新舊陣列差異處

    Unit Test: Github

Source:
Example
let r
let arrOld
let arrNew

if (true) {
    arrOld = [-0.529408622, 0.839882385, 0.663059856, 0.49047221, 123, 0.395763265, 0.866151835]
    arrNew = [-0.529408622, 0.1839882385, 0.663059856, 0.49047221, 0.395763265, 45.6, 0.866151835]
    r = arrDiff(arrOld, arrNew) //因第[5](0.395763265)元素會往前到[4]去, diff會偵測到並視為前面刪除(123)而後面加入(45.6), 故解析後長度會由7變成8
    console.log(r)
    // => {
    //   diff: [
    //     { count: 1, added: false, removed: false, value: '-0.529408622\n' },
    //     { count: 1, added: false, removed: true, value: '0.839882385\n' },
    //     { count: 1, added: true, removed: false, value: '0.1839882385\n' },
    //     {
    //       count: 2,
    //       added: false,
    //       removed: false,
    //       value: '0.663059856\n0.49047221\n'
    //     },
    //     { count: 1, added: false, removed: true, value: '123\n' },
    //     { count: 1, added: false, removed: false, value: '0.395763265\n' },
    //     { count: 1, added: true, removed: false, value: '45.6\n' },
    //     { count: 1, added: false, removed: false, value: '0.866151835\n' }
    //   ],
    //   dfs: [
    //     { p: '', k: 0, vo: '-0.529408622', vn: '' },
    //     { p: 'modify', k: 1, vo: '0.839882385', vn: '0.1839882385' },
    //     { p: '', k: 2, vo: '0.663059856', vn: '' },
    //     { p: '', k: 3, vo: '0.49047221', vn: '' },
    //     { p: 'remove', k: 4, vo: '123', vn: '' },
    //     { p: '', k: 5, vo: '0.395763265', vn: '' },
    //     { p: 'add', k: 6, vo: '45.6', vn: '' },
    //     { p: '', k: 7, vo: '0.866151835', vn: '' }
    //   ]
    // }
}

if (true) {
    arrOld = [
        [1, 0.974848293, 0.791303871, 0.716898185, 0.506002098, 0.137888903, 0.626724085],
        [2, -0.529408622, 0.839882385, 0.663059856, 0.49047221, 123, 0.395763265, 0.866151835],
        [3, -0.10320217, 0.475514539, 0.969205779, 0.711250309, 0.153847069, 0.410092395],
        [4, -0.121479865, 0.486179086, 0.481023842, 0.467410582, 0.42602231, 0.849701641],
        [5, 0.757346003, 0.697242433, 0.67532802, 0.174644416, 0.045652267, 0.397104668],
        [6, 0.663032731, 0.259252779, 0.566177431, 0.679637706, 0.377814487, 0.400248119],
        [7, 0.72721374, 0.263793391, 0.167895215, 0.794808602, 0.107070584, 0.011822872],
        [8, 0.247416664, 0.360426795, 0.014346373, 0.000469616, 0.4082693, 0.913806611],
        [9, 0.345880037, 0.167996664, 0.711054429, 0.363177921, 0.206849994, 0.636855344],
        [10, 0.576739457, 0.324665077, 0.973218005, 0.883927423, 0.176906125, 0.20087887],
        [11, 1.2],
        [12, 23.5],
        [13, 0.504421248, 0.984003751, 0.32549507, 0.987090751, 0.192745589, 0.735133561],
        [14, 0.273214614, 0.083431884, 0.565146092, 0.935388666, 0.637675154, 0.523815661],
    ]
    arrNew = [
        [1, 0.974848293, 0.791303871, 0.716898185, 0.506002098, 0.137888903, 0.626724085],
        [2, -0.529408622, 0.1839882385, 0.663059856, 0.49047221, 0.395763265, 45.6, 0.866151835],
        [3, -0.10320217, 0.475514539, 0.969205779, 0.711250309, 0.153847069, 0.410092395],
        [4, -0.121479865, 0.486179086, 0.481023842, 0.467410582, 0.42602231, 0.849701641],
        [5, 0.757346003, 0.697242433, 0.67532802, 0.174644416, 0.045652267, 0.397104668],
        [7, 0.72721374, 0.263793391, 0.167895215, 0.794808602, 0.107070584, 0.011822872],
        [8, 0.247416664, 0.360426795, 0.014346373, 0.000469616, 0.4082693, 0.913806611],
        [9, 0.345880037, 0.167996664, 0.711054429, 0.363173478, 0.636855344],
        [10, 0.576739457, 0.324665077, 0.973218005, 0.883927423, 0.176906125, 0.20087887],
        [13, 0.504421248, 0.984003751, 0.32549507, 0.987090751, 0.192745589, 0.735133561],
        [14, 0.273214614, 0.083431884, 0.565146092, 0.935388666, 0.637675154, 0.523815661],
        ['n1', 0.944492151, 0.89950443, 0.182709318, 0.892820757, 0.709746901, 0.097385354],
        ['n2', 0.769805921, 0.061355308, 0.314826137, 0.855857651, 0.653550539, 0.772500773],
        ['n3', 0.158739038, 0.085078711, 0.844664253, 0.21630142, 0.912931341, 0.735138313],
    ]
    r = arrDiff(arrOld, arrNew, { mode: 'matrix' })
    console.log('diff', r.diff)
    // => diff [
    //   {
    //     count: 1,
    //     added: false,
    //     removed: false,
    //     value: '0∶1⟋1∶0.974848293⟋2∶0.791303871⟋3∶0.716898185⟋4∶0.506002098⟋5∶0.137888903⟋6∶0.626724085⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: true,
    //     value: '0∶2⟋1∶-0.529408622⟋2∶0.839882385⟋3∶0.663059856⟋4∶0.49047221⟋5∶123⟋6∶0.395763265⟋7∶0.866151835⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶2⟋1∶-0.529408622⟋2∶0.1839882385⟋3∶0.663059856⟋4∶0.49047221⟋5∶0.395763265⟋6∶45.6⟋7∶0.866151835⟋\n'
    //   },
    //   {
    //     count: 3,
    //     added: false,
    //     removed: false,
    //     value: '0∶3⟋1∶-0.10320217⟋2∶0.475514539⟋3∶0.969205779⟋4∶0.711250309⟋5∶0.153847069⟋6∶0.410092395⟋\n' +
    //       '0∶4⟋1∶-0.121479865⟋2∶0.486179086⟋3∶0.481023842⟋4∶0.467410582⟋5∶0.42602231⟋6∶0.849701641⟋\n' +
    //       '0∶5⟋1∶0.757346003⟋2∶0.697242433⟋3∶0.67532802⟋4∶0.174644416⟋5∶0.045652267⟋6∶0.397104668⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: true,
    //     value: '0∶6⟋1∶0.663032731⟋2∶0.259252779⟋3∶0.566177431⟋4∶0.679637706⟋5∶0.377814487⟋6∶0.400248119⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: false,
    //     value: '0∶7⟋1∶0.72721374⟋2∶0.263793391⟋3∶0.167895215⟋4∶0.794808602⟋5∶0.107070584⟋6∶0.011822872⟋\n' +
    //       '0∶8⟋1∶0.247416664⟋2∶0.360426795⟋3∶0.014346373⟋4∶0.000469616⟋5∶0.4082693⟋6∶0.913806611⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: true,
    //     value: '0∶9⟋1∶0.345880037⟋2∶0.167996664⟋3∶0.711054429⟋4∶0.363177921⟋5∶0.206849994⟋6∶0.636855344⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶9⟋1∶0.345880037⟋2∶0.167996664⟋3∶0.711054429⟋4∶0.363173478⟋5∶0.636855344⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: false,
    //     value: '0∶10⟋1∶0.576739457⟋2∶0.324665077⟋3∶0.973218005⟋4∶0.883927423⟋5∶0.176906125⟋6∶0.20087887⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: true,
    //     value: '0∶11⟋1∶1.2⟋\n0∶12⟋1∶23.5⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: false,
    //     value: '0∶13⟋1∶0.504421248⟋2∶0.984003751⟋3∶0.32549507⟋4∶0.987090751⟋5∶0.192745589⟋6∶0.735133561⟋\n' +
    //       '0∶14⟋1∶0.273214614⟋2∶0.083431884⟋3∶0.565146092⟋4∶0.935388666⟋5∶0.637675154⟋6∶0.523815661⟋\n'
    //   },
    //   {
    //     count: 3,
    //     added: true,
    //     removed: false,
    //     value: '0∶n1⟋1∶0.944492151⟋2∶0.89950443⟋3∶0.182709318⟋4∶0.892820757⟋5∶0.709746901⟋6∶0.097385354⟋\n' +
    //       '0∶n2⟋1∶0.769805921⟋2∶0.061355308⟋3∶0.314826137⟋4∶0.855857651⟋5∶0.653550539⟋6∶0.772500773⟋\n' +
    //       '0∶n3⟋1∶0.158739038⟋2∶0.085078711⟋3∶0.844664253⟋4∶0.21630142⟋5∶0.912931341⟋6∶0.735138313⟋\n'
    //   }
    // ]
    for (let i = 0; i < r.dfs.length; i++) {
        let df = r.dfs[i]
        console.log(i, 'df', df)
    }
    // => 0 df [
    //   { p: '', k: 0, vo: '1', vn: '' },
    //   { p: '', k: 1, vo: '0.974848293', vn: '' },
    //   { p: '', k: 2, vo: '0.791303871', vn: '' },
    //   { p: '', k: 3, vo: '0.716898185', vn: '' },
    //   { p: '', k: 4, vo: '0.506002098', vn: '' },
    //   { p: '', k: 5, vo: '0.137888903', vn: '' },
    //   { p: '', k: 6, vo: '0.626724085', vn: '' }
    // ]
    // 1 df [
    //   { p: '', k: 0, vo: '2', vn: '' },
    //   { p: '', k: 1, vo: '-0.529408622', vn: '' },
    //   { p: 'modify', k: 2, vo: '0.839882385', vn: '0.1839882385' },
    //   { p: '', k: 3, vo: '0.663059856', vn: '' },
    //   { p: '', k: 4, vo: '0.49047221', vn: '' },
    //   { p: 'modify', k: 5, vo: '123', vn: '0.395763265' },
    //   { p: 'modify', k: 6, vo: '0.395763265', vn: '45.6' },
    //   { p: '', k: 7, vo: '0.866151835', vn: '' }
    // ]
    // 2 df [
    //   { p: '', k: 0, vo: '3', vn: '' },
    //   { p: '', k: 1, vo: '-0.10320217', vn: '' },
    //   { p: '', k: 2, vo: '0.475514539', vn: '' },
    //   { p: '', k: 3, vo: '0.969205779', vn: '' },
    //   { p: '', k: 4, vo: '0.711250309', vn: '' },
    //   { p: '', k: 5, vo: '0.153847069', vn: '' },
    //   { p: '', k: 6, vo: '0.410092395', vn: '' }
    // ]
    // 3 df [
    //   { p: '', k: 0, vo: '4', vn: '' },
    //   { p: '', k: 1, vo: '-0.121479865', vn: '' },
    //   { p: '', k: 2, vo: '0.486179086', vn: '' },
    //   { p: '', k: 3, vo: '0.481023842', vn: '' },
    //   { p: '', k: 4, vo: '0.467410582', vn: '' },
    //   { p: '', k: 5, vo: '0.42602231', vn: '' },
    //   { p: '', k: 6, vo: '0.849701641', vn: '' }
    // ]
    // 4 df [
    //   { p: '', k: 0, vo: '5', vn: '' },
    //   { p: '', k: 1, vo: '0.757346003', vn: '' },
    //   { p: '', k: 2, vo: '0.697242433', vn: '' },
    //   { p: '', k: 3, vo: '0.67532802', vn: '' },
    //   { p: '', k: 4, vo: '0.174644416', vn: '' },
    //   { p: '', k: 5, vo: '0.045652267', vn: '' },
    //   { p: '', k: 6, vo: '0.397104668', vn: '' }
    // ]
    // 5 df [
    //   { p: 'remove', k: 0, vo: '6', vn: '' },
    //   { p: 'remove', k: 1, vo: '0.663032731', vn: '' },
    //   { p: 'remove', k: 2, vo: '0.259252779', vn: '' },
    //   { p: 'remove', k: 3, vo: '0.566177431', vn: '' },
    //   { p: 'remove', k: 4, vo: '0.679637706', vn: '' },
    //   { p: 'remove', k: 5, vo: '0.377814487', vn: '' },
    //   { p: 'remove', k: 6, vo: '0.400248119', vn: '' }
    // ]
    // 6 df [
    //   { p: '', k: 0, vo: '7', vn: '' },
    //   { p: '', k: 1, vo: '0.72721374', vn: '' },
    //   { p: '', k: 2, vo: '0.263793391', vn: '' },
    //   { p: '', k: 3, vo: '0.167895215', vn: '' },
    //   { p: '', k: 4, vo: '0.794808602', vn: '' },
    //   { p: '', k: 5, vo: '0.107070584', vn: '' },
    //   { p: '', k: 6, vo: '0.011822872', vn: '' }
    // ]
    // 7 df [
    //   { p: '', k: 0, vo: '8', vn: '' },
    //   { p: '', k: 1, vo: '0.247416664', vn: '' },
    //   { p: '', k: 2, vo: '0.360426795', vn: '' },
    //   { p: '', k: 3, vo: '0.014346373', vn: '' },
    //   { p: '', k: 4, vo: '0.000469616', vn: '' },
    //   { p: '', k: 5, vo: '0.4082693', vn: '' },
    //   { p: '', k: 6, vo: '0.913806611', vn: '' }
    // ]
    // 8 df [
    //   { p: '', k: 0, vo: '9', vn: '' },
    //   { p: '', k: 1, vo: '0.345880037', vn: '' },
    //   { p: '', k: 2, vo: '0.167996664', vn: '' },
    //   { p: '', k: 3, vo: '0.711054429', vn: '' },
    //   { p: 'modify', k: 4, vo: '0.363177921', vn: '0.363173478' },
    //   { p: 'modify', k: 5, vo: '0.206849994', vn: '0.636855344' },
    //   { p: 'remove', k: 6, vo: '0.636855344', vn: '' }
    // ]
    // 9 df [
    //   { p: '', k: 0, vo: '10', vn: '' },
    //   { p: '', k: 1, vo: '0.576739457', vn: '' },
    //   { p: '', k: 2, vo: '0.324665077', vn: '' },
    //   { p: '', k: 3, vo: '0.973218005', vn: '' },
    //   { p: '', k: 4, vo: '0.883927423', vn: '' },
    //   { p: '', k: 5, vo: '0.176906125', vn: '' },
    //   { p: '', k: 6, vo: '0.20087887', vn: '' }
    // ]
    // 10 df [
    //   { p: 'remove', k: 0, vo: '11', vn: '' },
    //   { p: 'remove', k: 1, vo: '1.2', vn: '' }
    // ]
    // 11 df [
    //   { p: 'remove', k: 0, vo: '12', vn: '' },
    //   { p: 'remove', k: 1, vo: '23.5', vn: '' }
    // ]
    // 12 df [
    //   { p: '', k: 0, vo: '13', vn: '' },
    //   { p: '', k: 1, vo: '0.504421248', vn: '' },
    //   { p: '', k: 2, vo: '0.984003751', vn: '' },
    //   { p: '', k: 3, vo: '0.32549507', vn: '' },
    //   { p: '', k: 4, vo: '0.987090751', vn: '' },
    //   { p: '', k: 5, vo: '0.192745589', vn: '' },
    //   { p: '', k: 6, vo: '0.735133561', vn: '' }
    // ]
    // 13 df [
    //   { p: '', k: 0, vo: '14', vn: '' },
    //   { p: '', k: 1, vo: '0.273214614', vn: '' },
    //   { p: '', k: 2, vo: '0.083431884', vn: '' },
    //   { p: '', k: 3, vo: '0.565146092', vn: '' },
    //   { p: '', k: 4, vo: '0.935388666', vn: '' },
    //   { p: '', k: 5, vo: '0.637675154', vn: '' },
    //   { p: '', k: 6, vo: '0.523815661', vn: '' }
    // ]
    // 14 df [
    //   { p: 'add', k: 0, vo: 'n1', vn: '' },
    //   { p: 'add', k: 1, vo: '0.944492151', vn: '' },
    //   { p: 'add', k: 2, vo: '0.89950443', vn: '' },
    //   { p: 'add', k: 3, vo: '0.182709318', vn: '' },
    //   { p: 'add', k: 4, vo: '0.892820757', vn: '' },
    //   { p: 'add', k: 5, vo: '0.709746901', vn: '' },
    //   { p: 'add', k: 6, vo: '0.097385354', vn: '' }
    // ]
    // 15 df [
    //   { p: 'add', k: 0, vo: 'n2', vn: '' },
    //   { p: 'add', k: 1, vo: '0.769805921', vn: '' },
    //   { p: 'add', k: 2, vo: '0.061355308', vn: '' },
    //   { p: 'add', k: 3, vo: '0.314826137', vn: '' },
    //   { p: 'add', k: 4, vo: '0.855857651', vn: '' },
    //   { p: 'add', k: 5, vo: '0.653550539', vn: '' },
    //   { p: 'add', k: 6, vo: '0.772500773', vn: '' }
    // ]
    // 16 df [
    //   { p: 'add', k: 0, vo: 'n3', vn: '' },
    //   { p: 'add', k: 1, vo: '0.158739038', vn: '' },
    //   { p: 'add', k: 2, vo: '0.085078711', vn: '' },
    //   { p: 'add', k: 3, vo: '0.844664253', vn: '' },
    //   { p: 'add', k: 4, vo: '0.21630142', vn: '' },
    //   { p: 'add', k: 5, vo: '0.912931341', vn: '' },
    //   { p: 'add', k: 6, vo: '0.735138313', vn: '' }
    // ]
}
Parameters:
Name Type Attributes Default Description
strOld Array

輸入原始數據陣列

strNew Array

輸入更新數據陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
mode String <optional>
'array'

輸入數據維度字串,可選為'array'與'matrix',預設'array'

dls String <optional>
'⟋'

輸入若數據為矩陣時各儲存格分隔字串,預設'⟋'

dlq String <optional>
'∶'

輸入若數據為矩陣時各儲存格欄位指標分隔字串,預設'∶'

Returns:

回傳比對結果物件,若為一維陣列則回傳鍵值diff與dfs,其中diff為比對原始結果字串,dfs為依照各列比對結果陣列;若為二維陣列則回傳鍵值diff與dfs,其中diff一樣為比對原始結果字串,dfs為各列與各儲存格比對結果陣列

Type
Object

(static) arrFilterByKeywords(arr, keywords) → {Array}

Description:
  • 對arr陣列內各字串進行關鍵字計算,計算是否含有關鍵字與程度,多關鍵字用空白區隔,必要關鍵字可於字首添加「+」,不要關鍵字可於字首添加「-」

    Unit Test: Github

Source:
Example
let arr = [
    'abc def xyz',
    '測試abc中文mnop',
    'Instead of creating yet another opinionated application',
    'Node.js module which can be integrated into a larger application',
]
let kws = null
let r = null

kws = 'abc'
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 }
// ]

kws = 'def'
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 }
// ]

kws = 'def 中文'
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: true, weight: 0.25 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 }
// ]

kws = 'def 中文 mnop'
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: true, weight: 0.5555555555555557 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 }
// ]

kws = 'def +yet'
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: false, weight: 0 }
// ]

kws = 'def of module -yet'
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: true, weight: 1 }
// ]

kws = '+'
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 }
// ]

kws = '-'
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 }
// ]

kws = 'def +'
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 }
// ]

kws = ['def', 'of', 'module', '-yet']
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: true, weight: 1 }
// ]

kws = ['can be', 'def']
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: true, weight: 0.25 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: true, weight: 1 }
// ]

kws = ['+abc']
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 }
// ]

kws = ['-abc']
r = arrFilterByKeywords(arr, kws)
console.log(r)
// => [
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: false, weight: 0 },
//     { hasKeyword: true, weight: 1 },
//     { hasKeyword: true, weight: 1 }
// ]
Parameters:
Name Type Description
arr Array

輸入要被尋找的字串陣列

keywords String | Array

輸入要尋找的關鍵字字串或陣列

Returns:

回傳結果物件陣列,陣列內各物件包含hasKeyword與weight欄位,其中hasKeyword代表有無關鍵字為布林值,weight代表權重為浮點數,其值可大於1

Type
Array

(static) arrFilterByNum(arr) → {Array}

Description:
  • 過濾陣列內元素僅保留數字,並且自動轉數值

    Unit Test: Github

Source:
Example
let arr

arr = ['abc', '-2.2', '-1.1', -1.1, -1, -0.5, 0, '0', 0.5, 1, 1.1, '1.1', '2.2', 'xyz']
console.log(arrFilterByNum(arr))
// => [ -2.2, -1.1, -1.1, -1, -0.5, 0, 0, 0.5, 1, 1.1, 1.1, 2.2 ]

arr = ['-2.2', '-1.1', -1.1, -1, -0.5, 0, '0']
console.log(arrFilterByNum(arr))
// => [ -2.2, -1.1, -1.1, -1, -0.5, 0, 0 ]
Parameters:
Name Type Description
arr Array

輸入原始陣列

Returns:

回傳新陣列

Type
Array

(static) arrFilterByPnum(arr) → {Array}

Description:
  • 過濾陣列內元素僅保留正數(不含0),並且自動轉數值

    Unit Test: Github

Source:
Example
let arr

arr = ['abc', '-2.2', '-1.1', -1.1, -1, -0.5, 0, '0', 0.5, 1, 1.1, '1.1', '2.2', 'xyz']
console.log(arrFilterByPnum(arr))
// => [ 0.5, 1, 1.1, 1.1, 2.2 ]

arr = ['-2.2', '-1.1', -1.1, -1, -0.5, 0, '0']
console.log(arrFilterByPnum(arr))
// => []
Parameters:
Name Type Description
arr Array

輸入原始陣列

Returns:

回傳新陣列

Type
Array

(static) arrFilterByPnumAndToLog(arr) → {Array}

Description:
  • 過濾陣列內元素僅保留正數(不含0),並且自動轉數值,再轉log10(Math.log)

    Unit Test: Github

Source:
Example
let arr

arr = ['abc', '-2.2', '-1.1', -1.1, -1, -0.5, 0, '0', 0.5, 1, 1.1, '1.1', '2.2', 'xyz']
console.log(arrFilterByPnumAndToLog(arr))
// => [
//   -0.6931471805599453,
//   0,
//   0.09531017980432493,
//   0.09531017980432493,
//   0.7884573603642703
// ]

arr = ['-2.2', '-1.1', -1.1, -1, -0.5, 0, '0']
console.log(arrFilterByPnumAndToLog(arr))
// => []
Parameters:
Name Type Description
arr Array

輸入原始陣列

Returns:

回傳新陣列

Type
Array

(static) arrFind(vall, vfind) → {Array}

Description:
  • 由vall陣列找尋vfind內各元素之第1位置

    Unit Test: Github

Source:
Example
console.log(arrFind([1, 2, 3, '4', 5, 'abc'], 2))
// => [ 1 ]

console.log(arrFind([1, 2, 3, '4', 5, 'abc'], 12))
// => [ -1 ]

console.log(arrFind([1, 2, 3, '4', 5, 'abc'], [2]))
// => [ 1 ]

console.log(arrFind([1, 2, 3, '4', 5, 'abc'], ['4', 2]))
// => [ 3, 1 ]

console.log(arrFind([1, true, 2, 3, '4', true, 5, 'abc'], true))
// => [ 1 ]

console.log(arrFind([1, true, 2, 3, '4', true, 5, 'abc'], [true]))
// => [ 1 ]

console.log(arrFind([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], { x: 'xyz' }))
// => [ 2 ]

console.log(arrFind([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], [{ x: 'xyz' }]))
// => [ 2 ]

console.log(arrFind([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], ['4', { x: 'xyz' }]))
// => [ 4, 2 ]

console.log(arrFind([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], [7, { x: 'xyz' }]))
// => [-1, 2]
Parameters:
Name Type Description
vall String | Number | Object | Boolean | Array

輸入要被尋找的任意資料陣列

vfind String | Number | Object | Boolean | Array

輸入要尋找的任意資料陣列

Returns:

回傳所找到各元素第1位置之陣列

Type
Array

(static) arrGroupByMaxmin(arr, maxmins, optopt) → {Array}

Description:
  • 依照指定最大最小值物件陣列切分陣列

    Unit Test: Github

Source:
Example
let arr1 = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6]
let mm1 = [
    {
        min: 0,
        max: 2,
    },
    {
        min: 2,
        max: 5,
    },
]
console.log(arrGroupByMaxmin(arr1, mm1))
// => [
//   { min: 0, max: 2, items: [ 1.1 ] },
//   { min: 2, max: 5, items: [ 2.2, 3.3, 4.4 ] }
// ]

let arr2 = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6]
let mm2 = [
    {
        min: 0,
        max: 2,
    },
    {
        min: 2.5,
        max: 5,
    },
]
console.log(arrGroupByMaxmin(arr2, mm2))
// => [
//   { min: 0, max: 2, items: [ 1.1 ] },
//   { min: 2.5, max: 5, items: [ 3.3, 4.4 ] }
// ]

let arr3 = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6]
let mm3 = [
    {
        min: -1e20,
        max: 2,
    },
    {
        min: 2.5,
        max: 5,
    },
    {
        min: 6,
        max: 1e20,
    },
]
console.log(arrGroupByMaxmin(arr3, mm3))
// => [
//   { min: -100000000000000000000, max: 2, items: [ 1.1 ] },
//   { min: 2.5, max: 5, items: [ 3.3, 4.4 ] },
//   { min: 6, max: 100000000000000000000, items: [ 6.6 ] }
// ]

let arr4 = [
    {
        name: 'a',
        value: 1.1,
    },
    {
        name: 'b',
        value: 2.2,
    },
    {
        name: 'c',
        value: 3.3,
    },
    {
        name: 'd',
        value: 4.4,
    },
    {
        name: 'e',
        value: 5.5,
    },
    {
        name: 'f',
        value: 6.6,
    },
]
let mm4 = [
    {
        min: 0,
        max: 2,
    },
    {
        min: 2,
        max: 5,
    },
]
console.log(JSON.stringify(arrGroupByMaxmin(arr4, mm4), null, 2))
// => [
//   {
//     "min": 0,
//     "max": 2,
//     "items": [
//       {
//         "name": "a",
//         "value": 1.1
//       }
//     ]
//   },
//   {
//     "min": 2,
//     "max": 5,
//     "items": [
//       {
//         "name": "b",
//         "value": 2.2
//       },
//       {
//         "name": "c",
//         "value": 3.3
//       },
//       {
//         "name": "d",
//         "value": 4.4
//       }
//     ]
//   }
// ]

let arr5 = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6]
let mm5 = [
    {
        rmin: 0,
        rmax: 2,
    },
    {
        rmin: 2,
        rmax: 5,
    },
]
console.log(arrGroupByMaxmin(arr5, mm5, { keyMin: 'rmin', keyMax: 'rmax' }))
// => [
//   { rmin: 0, rmax: 2, items: [ 1.1 ] },
//   { rmin: 2, rmax: 5, items: [ 2.2, 3.3, 4.4 ] }
// ]

let arr6 = [
    {
        name: 'a',
        data: 1.1,
    },
    {
        name: 'b',
        data: 2.2,
    },
    {
        name: 'c',
        data: 3.3,
    },
    {
        name: 'd',
        data: 4.4,
    },
    {
        name: 'e',
        data: 5.5,
    },
    {
        name: 'f',
        data: 6.6,
    },
]
let mm6 = [
    {
        min: 0,
        max: 2,
    },
    {
        min: 2,
        max: 5,
    },
]
console.log(JSON.stringify(arrGroupByMaxmin(arr6, mm6, { keyValue: 'data' }), null, 2))
// => [
//   {
//     "min": 0,
//     "max": 2,
//     "items": [
//       {
//         "name": "a",
//         "data": 1.1
//       }
//     ]
//   },
//   {
//     "min": 2,
//     "max": 5,
//     "items": [
//       {
//         "name": "b",
//         "data": 2.2
//       },
//       {
//         "name": "c",
//         "data": 3.3
//       },
//       {
//         "name": "d",
//         "data": 4.4
//       }
//     ]
//   }
// ]

let arr7 = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6]
let mm7 = [
    {
        min: 0,
        max: 2,
    },
    {
        min: 2,
        max: 5,
    },
]
console.log(arrGroupByMaxmin(arr7, mm7, { keyItems: 'result' }))
// => [
//   { min: 0, max: 2, result: [ 1.1 ] },
//   { min: 2, max: 5, result: [ 2.2, 3.3, 4.4 ] }
// ]

let arr8 = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6]
let mm8 = [
    {
        min: 1.1,
        minType: '>',
        max: 2.2,
        maxType: '<=',
    },
    {
        min: 2.2,
        minType: '>=',
        max: 5.5,
        maxType: '<',
    },
]
console.log(arrGroupByMaxmin(arr8, mm8))
// => [
//   {
//     min: 1.1,
//     minType: '>',
//     max: 2.2,
//     maxType: '<=',
//     items: [ 2.2 ]
//   },
//   {
//     min: 2.2,
//     minType: '>=',
//     max: 5.5,
//     maxType: '<',
//     items: [ 2.2, 3.3, 4.4 ]
//   }
// ]


let arr9 = [
    {
        name: 'a',
        value: 1.1,
    },
    {
        name: 'b',
        value: 2.2,
    },
    {
        name: 'c',
        value: 3.3,
    },
    {
        name: 'd',
        value: 4.4,
    },
    {
        name: 'e',
        value: 5.5,
    },
    {
        name: 'f',
        value: 6.6,
    },
]
let mm9 = [
    {
        min: 0,
        minType: '>=',
        max: 5,
        maxType: '<',
    },
    {
        min: 5,
        minType: '>=',
        max: 10,
        maxType: '<',
    },
]
console.log(JSON.stringify(arrGroupByMaxmin(arr9, mm9), null, 2))
// => [
//   {
//     "min": 0,
//     "minType": ">=",
//     "max": 5,
//     "maxType": "<",
//     "items": [
//       {
//         "name": "a",
//         "value": 1.1
//       },
//       {
//         "name": "b",
//         "value": 2.2
//       },
//       {
//         "name": "c",
//         "value": 3.3
//       },
//       {
//         "name": "d",
//         "value": 4.4
//       }
//     ]
//   },
//   {
//     "min": 5,
//     "minType": ">=",
//     "max": 10,
//     "maxType": "<",
//     "items": [
//       {
//         "name": "e",
//         "value": 5.5
//       },
//       {
//         "name": "f",
//         "value": 6.6
//       }
//     ]
//   }
// ]


let arr10 = [
    {
        name: 'a',
        data: 1.1,
    },
    {
        name: 'b',
        data: 2.2,
    },
    {
        name: 'c',
        data: 3.3,
    },
    {
        name: 'd',
        data: 4.4,
    },
    {
        name: 'e',
        data: 5.5,
    },
    {
        name: 'f',
        data: 6.6,
    },
]
let mm10 = [
    {
        min: 0,
        minType: '>=',
        max: 5,
        maxType: '<',
    },
    {
        min: 5,
        minType: '>=',
        max: 10,
        maxType: '<',
    },
]
console.log(JSON.stringify(arrGroupByMaxmin(arr10, mm10, { keyValue: 'data' }), null, 2))
// => [
//   {
//     "min": 0,
//     "minType": ">=",
//     "max": 5,
//     "maxType": "<",
//     "items": [
//       {
//         "name": "a",
//         "data": 1.1
//       },
//       {
//         "name": "b",
//         "data": 2.2
//       },
//       {
//         "name": "c",
//         "data": 3.3
//       },
//       {
//         "name": "d",
//         "data": 4.4
//       }
//     ]
//   },
//   {
//     "min": 5,
//     "minType": ">=",
//     "max": 10,
//     "maxType": "<",
//     "items": [
//       {
//         "name": "e",
//         "data": 5.5
//       },
//       {
//         "name": "f",
//         "data": 6.6
//       }
//     ]
//   }
// ]
Parameters:
Name Type Attributes Default Description
arr Array

輸入要被切割的陣列

maxmins Array

輸入要切分的最大最小值物件陣列,每個元素需為物件,至少得要包含keyMax與keyMin欄位

opt Object <optional>
{}

輸入設定物件

Properties
Name Type Attributes Default Description
keyMax String <optional>
'max'

輸入maxmins內各元素中,儲存最大值欄位字串,預設'max'

keyMin String <optional>
'min'

輸入maxmins內各元素中,儲存最小值欄位字串,預設'min'

keyValue String <optional>
'value'

輸入arr內各元素中,若為物件,則儲存欲判斷數值之欄位字串,預設'value'

keyItems String <optional>
'items'

輸入回傳maxmins物件陣列中,儲存所屬arr元素之欄位字串,預設'items'

Returns:

回傳切割後的陣列

Type
Array

(static) arrHas(vtar, vhas) → {Boolean}

Description:
  • 判斷任一字串陣列vtar內元素,是否「等於」任一字串陣列vhas內元素

    Unit Test: Github

Source:
Example
console.log(arrHas([1, 2, 3, '4', 5, 'abc'], 2))
// => true

console.log(arrHas([1, 2, 3, '4', 5, 'abc'], 6))
// => false

console.log(arrHas([1, 2, 3, '4', 5, 'abc'], [2]))
// => true

console.log(arrHas([1, 2, 3, '4', 5, 'abc'], [6]))
// => false

console.log(arrHas([1, 2, 3, '4', 5, 'abc'], ['4', 2]))
// => true

console.log(arrHas([1, 2, 3, '4', 5, 'abc'], ['7', 6]))
// => false

console.log(arrHas([1, true, 2, 3, '4', true, 5, 'abc'], true))
// => true

console.log(arrHas([1, true, 2, 3, '4', true, 5, 'abc'], false))
// => false

console.log(arrHas([1, true, 2, 3, '4', true, 5, 'abc'], [true]))
// => true

console.log(arrHas([1, true, 2, 3, '4', true, 5, 'abc'], [false]))
// => false

console.log(arrHas([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], { x: 'xyz' }))
// => true

console.log(arrHas([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], { x: 'opqr' }))
// => false

console.log(arrHas([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], [{ x: 'xyz' }]))
// => true

console.log(arrHas([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], [{ x: 'opqr' }]))
// => false

console.log(arrHas([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], ['4', { x: 'xyz' }]))
// => true

console.log(arrHas([1, 2, { x: 'xyz' }, 3, '4', 5, 'abc'], ['7', { x: 'opqr' }]))
// => false
Parameters:
Name Type Description
vtar String | Number | Object | Boolean | Array

輸入被查找的字串陣列或字串

vhas String | Number | Object | Boolean | Array

輸入查找字串陣列或字串

Returns:

回傳判斷布林值

Type
Boolean

(static) arrInsert(arr, ind, items) → {Array}

Description:
  • 於陣列arr內指定位置ind插入新陣列items

    Unit Test: Github

Source:
Example
let arr = [1, 2.5, '123']
let r

r = arrInsert([], 0, 'abc')
console.log(r)
// => [ 'abc' ]

r = arrInsert(arr, 0, 'abc')
console.log(r)
// => [ 'abc', 1, 2.5, '123' ]

r = arrInsert(arr, 1, 'abc')
console.log(r)
// => [ 1, 'abc', 2.5, '123' ]

r = arrInsert(arr, 3, 'abc')
console.log(r)
// => [ 1, 2.5, '123', 'abc' ]

r = arrInsert(arr, 4, 'abc')
console.log(r)
// => [ 1, 2.5, '123' ]

r = arrInsert(arr, 1, null)
console.log(r)
// => [ 1, null, 2.5, '123' ]

r = arrInsert(arr, 1, ['abc', null, 'xyz'])
console.log(r)
// => [ 1, 'abc', null, 'xyz', 2.5, '123' ]
Parameters:
Name Type Description
arr Array

輸入原始陣列

ind Integer

輸入插入指標

items String | Number | Object | Boolean | Array

輸入欲插入的資料或陣列,非陣列資料都會自動轉陣列再插入

Returns:

回傳新陣列

Type
Array

(static) arrLookupByMaxmin(arr, maxmins, optopt) → {Array}

Description:
  • 依照指定最大最小值物件陣列,查找陣列內各元素之所屬值

    Unit Test: Github

Source:
Example
let arr1 = [
    {
        name: 'a',
        value: 1.1,
    },
    {
        name: 'b',
        value: 2.2,
    },
    {
        name: 'c',
        value: 3.3,
    },
    {
        name: 'd',
        value: 4.4,
    },
    {
        name: 'e',
        value: 5.5,
    },
    {
        name: 'f',
        value: 6.6,
    },
]
let mm1 = [
    {
        min: 0,
        max: 2,
    },
    {
        min: 2,
        max: 5,
    },
]
console.log(JSON.stringify(arrLookupByMaxmin(arr1, mm1), null, 2))
// => [
//   {
//     "name": "a",
//     "value": 1.1,
//     "items": [
//       {
//         "min": 0,
//         "max": 2
//       }
//     ]
//   },
//   {
//     "name": "b",
//     "value": 2.2,
//     "items": [
//       {
//         "min": 2,
//         "max": 5
//       }
//     ]
//   },
//   {
//     "name": "c",
//     "value": 3.3,
//     "items": [
//       {
//         "min": 2,
//         "max": 5
//       }
//     ]
//   },
//   {
//     "name": "d",
//     "value": 4.4,
//     "items": [
//       {
//         "min": 2,
//         "max": 5
//       }
//     ]
//   },
//   {
//     "name": "e",
//     "value": 5.5
//   },
//   {
//     "name": "f",
//     "value": 6.6
//   }
// ]

let arr2 = [
    {
        name: 'a',
        value: 1.1,
    },
    {
        name: 'b',
        value: 2.2,
    },
    {
        name: 'c',
        value: 3.3,
    },
    {
        name: 'd',
        value: 4.4,
    },
    {
        name: 'e',
        value: 5.5,
    },
    {
        name: 'f',
        value: 6.6,
    },
]
let mm2 = [
    {
        rmin: 0,
        rmax: 2,
    },
    {
        rmin: 2,
        rmax: 5,
    },
]
console.log(JSON.stringify(arrLookupByMaxmin(arr2, mm2, { keyMin: 'rmin', keyMax: 'rmax' }), null, 2))
// => [
//   {
//     "name": "a",
//     "value": 1.1,
//     "items": [
//       {
//         "rmin": 0,
//         "rmax": 2
//       }
//     ]
//   },
//   {
//     "name": "b",
//     "value": 2.2,
//     "items": [
//       {
//         "rmin": 2,
//         "rmax": 5
//       }
//     ]
//   },
//   {
//     "name": "c",
//     "value": 3.3,
//     "items": [
//       {
//         "rmin": 2,
//         "rmax": 5
//       }
//     ]
//   },
//   {
//     "name": "d",
//     "value": 4.4,
//     "items": [
//       {
//         "rmin": 2,
//         "rmax": 5
//       }
//     ]
//   },
//   {
//     "name": "e",
//     "value": 5.5
//   },
//   {
//     "name": "f",
//     "value": 6.6
//   }
// ]

let arr3 = [
    {
        name: 'a',
        data: 1.1,
    },
    {
        name: 'b',
        data: 2.2,
    },
    {
        name: 'c',
        data: 3.3,
    },
    {
        name: 'd',
        data: 4.4,
    },
    {
        name: 'e',
        data: 5.5,
    },
    {
        name: 'f',
        data: 6.6,
    },
]
let mm3 = [
    {
        min: 0,
        max: 2,
    },
    {
        min: 2,
        max: 5,
    },
]
console.log(JSON.stringify(arrLookupByMaxmin(arr3, mm3, { keyValue: 'data' }), null, 2))
// => [
//   {
//     "name": "a",
//     "data": 1.1,
//     "items": [
//       {
//         "min": 0,
//         "max": 2
//       }
//     ]
//   },
//   {
//     "name": "b",
//     "data": 2.2,
//     "items": [
//       {
//         "min": 2,
//         "max": 5
//       }
//     ]
//   },
//   {
//     "name": "c",
//     "data": 3.3,
//     "items": [
//       {
//         "min": 2,
//         "max": 5
//       }
//     ]
//   },
//   {
//     "name": "d",
//     "data": 4.4,
//     "items": [
//       {
//         "min": 2,
//         "max": 5
//       }
//     ]
//   },
//   {
//     "name": "e",
//     "data": 5.5
//   },
//   {
//     "name": "f",
//     "data": 6.6
//   }
// ]

let arr4 = [
    {
        name: 'a',
        value: 1.1,
    },
    {
        name: 'b',
        value: 2.2,
    },
    {
        name: 'c',
        value: 3.3,
    },
    {
        name: 'd',
        value: 4.4,
    },
    {
        name: 'e',
        value: 5.5,
    },
    {
        name: 'f',
        value: 6.6,
    },
]
let mm4 = [
    {
        min: 0,
        max: 2,
    },
    {
        min: 2,
        max: 5,
    },
]
console.log(JSON.stringify(arrLookupByMaxmin(arr4, mm4, { keyItems: 'res' }), null, 2))
// => [
//   {
//     "name": "a",
//     "value": 1.1,
//     "res": [
//       {
//         "min": 0,
//         "max": 2
//       }
//     ]
//   },
//   {
//     "name": "b",
//     "value": 2.2,
//     "res": [
//       {
//         "min": 2,
//         "max": 5
//       }
//     ]
//   },
//   {
//     "name": "c",
//     "value": 3.3,
//     "res": [
//       {
//         "min": 2,
//         "max": 5
//       }
//     ]
//   },
//   {
//     "name": "d",
//     "value": 4.4,
//     "res": [
//       {
//         "min": 2,
//         "max": 5
//       }
//     ]
//   },
//   {
//     "name": "e",
//     "value": 5.5
//   },
//   {
//     "name": "f",
//     "value": 6.6
//   }
// ]

let arr5 = [
    {
        name: 'a',
        value: 1.1,
    },
    {
        name: 'b',
        value: 2.2,
    },
    {
        name: 'c',
        value: 3.3,
    },
    {
        name: 'd',
        value: 4.4,
    },
    {
        name: 'e',
        value: 5.5,
    },
    {
        name: 'f',
        value: 6.6,
    },
]
let mm5 = [
    {
        name: 'x1',
        min: 0,
        max: 2,
    },
    {
        name: 'x2',
        min: 2,
        max: 5,
    },
]
console.log(JSON.stringify(arrLookupByMaxmin(arr5, mm5, {
    keyItems: 'res',
    beforeAddResult: (m) => {
        return m.name
    }
}), null, 2))
// => [
//   {
//     "name": "a",
//     "value": 1.1,
//     "res": [
//       "x1"
//     ]
//   },
//   {
//     "name": "b",
//     "value": 2.2,
//     "res": [
//       "x2"
//     ]
//   },
//   {
//     "name": "c",
//     "value": 3.3,
//     "res": [
//       "x2"
//     ]
//   },
//   {
//     "name": "d",
//     "value": 4.4,
//     "res": [
//       "x2"
//     ]
//   },
//   {
//     "name": "e",
//     "value": 5.5
//   },
//   {
//     "name": "f",
//     "value": 6.6
//   }
// ]

let arr6 = [
    {
        name: 'a',
        value: 1.1,
    },
    {
        name: 'b',
        value: 2.2,
    },
    {
        name: 'c',
        value: 3.3,
    },
    {
        name: 'd',
        value: 4.4,
    },
    {
        name: 'e',
        value: 5.5,
    },
    {
        name: 'f',
        value: 6.6,
    },
]
let mm6 = [
    {
        min: 1.1,
        minType: '>',
        max: 2.2,
        maxType: '<=',
    },
    {
        min: 2.2,
        minType: '>=',
        max: 5.5,
        maxType: '<',
    },
]
console.log(JSON.stringify(arrLookupByMaxmin(arr6, mm6), null, 2))
// => [
//   {
//     "name": "a",
//     "value": 1.1
//   },
//   {
//     "name": "b",
//     "value": 2.2,
//     "items": [
//       {
//         "min": 1.1,
//         "minType": ">",
//         "max": 2.2,
//         "maxType": "<="
//       },
//       {
//         "min": 2.2,
//         "minType": ">=",
//         "max": 5.5,
//         "maxType": "<"
//       }
//     ]
//   },
//   {
//     "name": "c",
//     "value": 3.3,
//     "items": [
//       {
//         "min": 2.2,
//         "minType": ">=",
//         "max": 5.5,
//         "maxType": "<"
//       }
//     ]
//   },
//   {
//     "name": "d",
//     "value": 4.4,
//     "items": [
//       {
//         "min": 2.2,
//         "minType": ">=",
//         "max": 5.5,
//         "maxType": "<"
//       }
//     ]
//   },
//   {
//     "name": "e",
//     "value": 5.5
//   },
//   {
//     "name": "f",
//     "value": 6.6
//   }
// ]
Parameters:
Name Type Attributes Default Description
arr Array

輸入要被切割的陣列,需為物件陣列,否則無法添加查找結果

maxmins Array

輸入要切分的最大最小值物件陣列,每個元素需為物件,至少得要包含keyMax與keyMin欄位

opt Object <optional>
{}

輸入設定物件

Properties
Name Type Attributes Default Description
keyMax String <optional>
'max'

輸入maxmins內各元素中,儲存最大值欄位字串,預設'max'

keyMin String <optional>
'min'

輸入maxmins內各元素中,儲存最小值欄位字串,預設'min'

keyValue String <optional>
'value'

輸入arr內各元素中,若為物件,則儲存欲判斷數值之欄位字串,預設'value'

keyItems String <optional>
'items'

輸入回傳arr物件陣列中,儲存所屬maxmins元素之欄位字串,預設'items'

beforeAddResult function <optional>
null

輸入儲存至arr物件陣列前的處理函數,預設null

Returns:

回傳切割後的陣列

Type
Array

(static) arrMax(vall, optopt) → {Number|Object}

Description:
  • 找尋陣列內元素最大值

    Unit Test: Github

Source:
Example
let r

r = arrMax([100000, 1, 30, 4, 21])
console.log(r)
// => 100000

r = arrMax([1, 30, 4, 21, 100000])
console.log(r)
// => 100000

r = arrMax([1, 30, 4, 100000, 21])
console.log(r)
// => 100000

r = arrMax([1, 30, 4, 100000, 21], { returnIndex: true })
console.log(r)
// => 3

r = arrMax(['March', 'Jan', 'Feb', 'Dec'])
console.log(r)
// => null

r = arrMax(['1', '30', '  4  ', '100000', '21'])
console.log(r)
// => 100000

r = arrMax(['1', '30', '  4  ', '100000', 21])
console.log(r)
// => 100000

r = arrMax(['a1', 'b30', '  4  ', '100000', 21])
console.log(r)
// => 100000

r = arrMax(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }, { s: 'Nov', i: 'a25' }],
    { compareKey: 'i' }
)
console.log(r)
// => { s: 'Feb', i: 100000 }

r = arrMax(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }, { s: 'Nov', i: 'a25' }],
    { compareKey: 's' }
)
console.log(r)
// => null

r = arrMax(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }, { s: 'Nov', i: 'a25' }],
    { compareKey: 'i', returnIndex: true }
)
console.log(r)
// => 2
Parameters:
Name Type Attributes Default Description
vall Array

輸入原始陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnIndex Boolean <optional>
false

輸入是否回傳排序指標陣列布林值,預設false

compareKey String <optional>
null

輸入當vall為物件陣列時,指定取compareKey欄位出來排序,compareKey需為有效字串,預設null

Returns:

回傳最大值數字或判斷具有最大值之物件

Type
Number | Object

(static) arrMin(vall, optopt) → {Number|Object}

Description:
  • 找尋陣列內元素最小值

    Unit Test: Github

Source:
Example
let r

r = arrMin([100000, 1, 30, 4, 21])
console.log(r)
// => 1

r = arrMin([1, 30, 4, 21, 100000])
console.log(r)
// => 1

r = arrMin([1, 30, 4, 100000, 21])
console.log(r)
// => 1

r = arrMin([1, 30, 4, 100000, 21], { returnIndex: true })
console.log(r)
// => 0

r = arrMin(['March', 'Jan', 'Feb', 'Dec'])
console.log(r)
// => null

r = arrMin(['1', '30', '  4  ', '100000', '21'])
console.log(r)
// => 1

r = arrMin(['1', '30', '  4  ', '100000', 21])
console.log(r)
// => 1

r = arrMin(['a1', 'b30', '  4  ', '100000', 21])
console.log(r)
// => 4

r = arrMin(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }, { s: 'Nov', i: 'a25' }],
    { compareKey: 'i' }
)
console.log(r)
// => { s: 'March', i: 1 }

r = arrMin(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }, { s: 'Nov', i: 'a25' }],
    { compareKey: 's' }
)
console.log(r)
// => null

r = arrMin(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }, { s: 'Nov', i: 'a25' }],
    { compareKey: 'i', returnIndex: true }
)
console.log(r)
// => 0
Parameters:
Name Type Attributes Default Description
vall Array

輸入原始陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnIndex Boolean <optional>
false

輸入是否回傳排序指標陣列布林值,預設false

compareKey String <optional>
null

輸入當vall為物件陣列時,指定取compareKey欄位出來排序,compareKey需為有效字串,預設null

Returns:

回傳最小值數字或判斷具有最小值之物件

Type
Number | Object

(static) arrMulti(arguments) → {Array}

Description:
  • 各陣列同位置元素相乘

    Unit Test: Github

Source:
Example
let v1 = [1, 2, 3, 4]
let v2 = [0.1, 0.1, 0.1, 0.1]
let v3 = [11, 22, 33, 44]

console.log(arrMulti(v1, v2))
// => [ 0.1, 0.2, 0.30000000000000004, 0.4 ]

console.log(arrMulti(v1, v2, v3))
// => [ 1.1, 4.4, 9.900000000000002, 17.6 ]
Parameters:
Name Type Description
arguments Array

輸入n個陣列,需同長度,其內元素皆會轉為浮點數,各陣列針對各元素進行相加總

Returns:

回傳各元素相加後陣列

Type
Array

(static) arrNorm(vec) → {Array}

Description:
  • 陣列Norm值,代表由各元素平方值相加開根號

    Unit Test: Github

Source:
Example
console.log(arrNorm([1, 2, 3, 4]))
// => 5.477225575051661

console.log(arrNorm([0.1, 0.1, 0.1, 0.1]))
// => 0.2

console.log(arrNorm([11, 22, 33, 44]))
// => 60.249481325568276
Parameters:
Name Type Description
vec Array

輸入要被累加計算的陣列

Returns:

回傳各元素累加後之陣列

Type
Array

(static) arrProduct(arguments) → {Array}

Description:
  • 各陣列同位置元素相乘並加總

    Unit Test: Github

Source:
Example
let v1 = [1, 2, 3, 4]
let v2 = [0.1, 0.1, 0.1, 0.1]
let v3 = [11, 22, 33, 44]

console.log(arrProduct(v1, v2))
// => 1

console.log(arrProduct(v1, v2, v3))
// => 33
Parameters:
Name Type Description
arguments Array

輸入n個陣列,需同長度,其內元素皆會轉為浮點數,各陣列針對各元素進行相加總

Returns:

回傳各元素相加後陣列

Type
Array

(static) arrPull(vsrc, vpull) → {Array}

Description:
  • 針對陣列移除元素或陣列內全部元素

    Unit Test: Github

Source:
Example
console.log(arrPull([1, 2, 3, 4, 5, 'abc'], [1, 4]))
// => [ 2, 3, 5, 'abc' ]

console.log(arrPull([1, 2, 3, '4', 5, 'abc'], [1, 4]))
// => [ 2, 3, '4', 5, 'abc' ]

console.log(arrPull([1, 2, 3, '4', 5, 'abc'], [6, 7]))
// => [ 1, 2, 3, '4', 5, 'abc' ]
Parameters:
Name Type Description
vsrc Array

輸入原始陣列

vpull Array | Object | Number | String | Boolean

輸入要被移除的元素或陣列

Returns:

回傳移除後陣列

Type
Array

(static) arrPullAt(vsrc, vpull) → {Array}

Description:
  • 針對陣列移除指定位置或陣列的指定位置

    Unit Test: Github

Source:
Example
console.log(arrPullAt([1, 2, 3, 4, 5, 'abc'], [0, 2]))
// => [ 2, 4, 5, 'abc' ]

console.log(arrPullAt([1, 2, 3, '4', 5, 'abc'], [1, 3]))
// => [ 1, 3, 5, 'abc' ]

console.log(arrPullAt([1, 2, 3, '4', 5, 'abc'], [4, 7]))
// => [ 1, 2, 3, '4', 'abc' ]
Parameters:
Name Type Description
vsrc Array

輸入原始陣列

vpull Array | Integer

輸入要被移除的指定位置或陣列的指定位置

Returns:

回傳移除後陣列

Type
Array

(static) arrReduce(vall, fun, optopt) → {Number|Object}

Description:
  • 找尋陣列內元素指定值

    Unit Test: Github

Source:
Example
let r

r = arrReduce([100000, 1, 30, 4, 21], (vTemp, vNow) => {
    if (vTemp === null) {
        return vNow
    }
    return Math.min(vTemp, vNow)
})
console.log(r)
// => 1

r = arrReduce([1, 30, 4, 21, 100000], (vTemp, vNow) => {
    if (vTemp === null) {
        return vNow
    }
    return Math.max(vTemp, vNow)
})
console.log(r)
// => 100000

r = arrReduce([1, 30, 4, 100000, 21], (vTemp, vNow) => {
    if (vTemp === null) {
        return vNow
    }
    return Math.max(vTemp, vNow)
}, { returnIndex: true })
console.log(r)
// => 3

r = arrReduce(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }, { s: 'Nov', i: 25, }],
    (vTemp, vNow) => {
        if (vTemp === null) {
            return vNow.i
        }
        return Math.min(vTemp, vNow.i)
    },
)
console.log(r)
// => 1

r = arrReduce(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }, { s: 'Nov', i: 25, }],
    (vTemp, vNow) => {
        if (vTemp === null) {
            return vNow
        }
        return vTemp.i < vNow.i ? vNow : vTemp //max
    },
)
console.log(r)
// => { s: 'Feb', i: 100000 }

r = arrReduce(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }, { s: 'Nov', i: 25, }],
    (vTemp, vNow) => {
        if (vTemp === null) {
            return vNow
        }
        return vTemp.i < vNow.i ? vNow : vTemp //max
    },
    { returnIndex: true }
)
console.log(r)
// => 2
Parameters:
Name Type Attributes Default Description
vall Array

輸入原始陣列

fun function

輸入處理與比對函數,輸入參數為全域暫存值與現在提取值,函數比對完須回傳新的全域暫存值

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnIndex Boolean <optional>
false

輸入是否回傳排序指標陣列布林值,預設false

Returns:

回傳指定值數字或判斷具有指定值之物件

Type
Number | Object

(static) arrReverse(vsrc) → {Array}

Description:
  • 反序排列陣列

    Unit Test: Github

Source:
Example
console.log(arrReverse([1, 2, 3, 4, 5, 'abc']))
// => [ 'abc', 5, 4, 3, 2, 1 ]

console.log(arrReverse([1, 2, 3, '4', 5, 'abc']))
// => [ 'abc', 5, '4', 3, 2, 1 ]
Parameters:
Name Type Description
vsrc Array

輸入原始陣列

Returns:

回傳移除後陣列

Type
Array

(static) arrSep(arr, num) → {Array}

Description:
  • 依照指定數量切分陣列

    Unit Test: Github

Source:
Example
let arr = ['a', 123, 'xyz', 5.678, null, 'd', [], { x: 'x1', y: 'y1' }]

console.log(arrSep(arr, 2))
// => [
//   [ 'a', 123 ],
//   [ 'xyz', 5.678 ],
//   [ null, 'd' ],
//   [ [], { x: 'x1', y: 'y1' } ]
// ]

console.log(arrSep(arr, 3))
// => [
//   [ 'a', 123, 'xyz' ],
//   [ 5.678, null, 'd' ],
//   [ [], { x: 'x1', y: 'y1' } ]
// ]
Parameters:
Name Type Description
arr Array

輸入要被切割的陣列

num Integer | Object

輸入要切分數量的正整數或設定物件,可給予鍵'numCount'或'numGroup'而對應值皆為正整數,numCount代表原本num表示每個切割後群組內所擁有的元素數量上限,numGroup代表欲切割成幾個群組數量

Returns:

回傳切割後的陣列

Type
Array

(static) arrSort(vall, optopt) → {Array}

Description:
  • 排序vall陣列,可針對純數字、純字串、含固定開頭字元的數字字串、物件陣列進行排列

    排序規則(同Excel之升冪):數字與數字字串依數值排序排在最前;其他字串其次,若皆可剔除開頭結尾非數字字元而得數字(例如'abc1')則依其數值排序,否則依字串排序;無法轉數字或無法解析者(空字串、純空白字串、null、undefined、NaN、布林、物件、陣列等)無法排序,一律放最末並維持原順序

    localeCompare為true時數字與字串一併以localeCompare(numeric)排序,無法解析者仍放最末;vall全為物件時取compareKey欄位之值套用相同規則,無此欄位者放最末

    Unit Test: Github

Source:
Example
let r

r = arrSort([1, 30, 4, 21, 100000])
console.log(r)
// => [ 1, 4, 21, 30, 100000 ]

r = arrSort([1, 30, 4, 21, 100000], { returnIndex: true })
console.log(r)
// => [ 0, 2, 3, 1, 4 ]

r = arrSort(['March', 'Jan', 'Feb', 'Dec'])
console.log(r)
// => [ 'Dec', 'Feb', 'Jan', 'March' ]

r = arrSort(['1', '30', '  4  ', '21', '100000'])
console.log(r)
// => [ '1', '  4  ', '21', '30', '100000' ]

r = arrSort(['1', '30', '  4  ', 21, '100000'])
console.log(r)
// => [ '1', '  4  ', 21, '30', '100000' ]

r = arrSort([1, 2, 'abc', 5, 3, '4'])
console.log(r)
// => [ 1, 2, 3, '4', 5, 'abc' ]

r = arrSort([10, '', 9, 'N/A', ' ', null, 100])
console.log(r)
// => [ 9, 10, 100, 'N/A', '', ' ', null ]

r = arrSort(['abc1', 'abc30', 'abc4', 'abc21', 'abc100000'])
console.log(r)
// => [ 'abc1', 'abc4', 'abc21', 'abc30', 'abc100000' ]

r = arrSort(['1a', '30c', '  4 abc ', 'xyz', '21d', '100000xy'])
console.log(r)
// => [ '  4 abc ', '100000xy', '1a', '21d', '30c', 'xyz' ]

r = arrSort(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }],
    { compareKey: 'i' }
)
console.log(r)
// => [
//   { s: 'March', i: 1 },
//   { s: 'Jan', i: 4 },
//   { s: 'Dec', i: 30 },
//   { s: 'Feb', i: 100000 }
// ]

r = arrSort(
    [{ s: 'March', i: 1 }, { s: 'Jan', i: 4 }, { s: 'Feb', i: 100000 }, { s: 'Dec', i: 30 }],
    { compareKey: 's' }
)
console.log(r)
// => [
//   { s: 'Dec', i: 30 },
//   { s: 'Feb', i: 100000 },
//   { s: 'Jan', i: 4 },
//   { s: 'March', i: 1 }
// ]

r = arrSort(
    [{ s: 'abc1', i: 1, }, { s: 'abc', i: -1, }, { s: 'abc30', i: 4, }, { s: 'abc4', i: 100000, }, { s: 'abc100000', i: 30, }],
    { compareKey: 's' }
)
console.log(r)
// => [
//   { s: 'abc', i: -1 },
//   { s: 'abc1', i: 1 },
//   { s: 'abc100000', i: 30 },
//   { s: 'abc30', i: 4 },
//   { s: 'abc4', i: 100000 }
// ]

r = arrSort(
    [{ s: 'abc1', i: 1, }, { s: 'abc', i: -1, }, { s: 'abc30', i: 4, }, { s: 'abc4', i: 100000, }, { s: 'abc100000', i: 30, }],
    { compareKey: 's', localeCompare: true }
)
console.log(r)
// => [
//   { s: 'abc', i: -1 },
//   { s: 'abc1', i: 1 },
//   { s: 'abc4', i: 100000 },
//   { s: 'abc30', i: 4 },
//   { s: 'abc100000', i: 30 }
// ]

r = arrSort(
    [{ s: '中文1', i: 1, }, { s: '中文', i: -1, }, { s: '中文30', i: 4, }, { s: '中文4', i: 100000, }, { s: '中文100000', i: 30, }],
    { compareKey: 's', localeCompare: true }
)
console.log(r)
// => [
//   { s: '中文', i: -1 },
//   { s: '中文1', i: 1 },
//   { s: '中文4', i: 100000 },
//   { s: '中文30', i: 4 },
//   { s: '中文100000', i: 30 }
// ]

r = arrSort(
    [{ s: 'xyz.txt', i: 100, }, { s: 'abc1.txt', i: 1, }, { s: 'abc.txt', i: -1, }, { s: 'abc', i: -2, }, { s: 'abc30.txt', i: 4, }, { s: 'abc4.txt', i: 100000, }, { s: 'abc100000.txt', i: 30, }],
    { compareKey: 's', localeCompare: true }
)
console.log(r)
// => [
//   { s: 'abc', i: -2 },
//   { s: 'abc.txt', i: -1 },
//   { s: 'abc1.txt', i: 1 },
//   { s: 'abc4.txt', i: 100000 },
//   { s: 'abc30.txt', i: 4 },
//   { s: 'abc100000.txt', i: 30 },
//   { s: 'xyz.txt', i: 100 }
// ]
Parameters:
Name Type Attributes Default Description
vall Array

輸入要被提取的任意資料陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
localeCompare Boolean <optional>
false

輸入是否使用localeCompare排序布林值,預設false

returnIndex Boolean <optional>
false

輸入是否回傳排序指標陣列布林值,預設false

compareKey String <optional>
null

輸入當vall為物件陣列時,指定取compareKey欄位出來排序,compareKey需為有效字串,預設null

Returns:

回傳排序後陣列或指標陣列

Type
Array

(static) asind(v) → {Number}

Description:
  • 輸入浮點數計算asin角度值

    Unit Test: Github

Source:
Example
console.log(asind(0))
// => 0

console.log(asind(0.5))
// => 30.000000000000004

console.log(asind(0.7071067811865475))
// => 44.99999999999999

console.log(asind(0.8660254037844386))
// => 59.99999999999999

console.log(asind(1))
// => 90

console.log(asind(-0.5))
// => -30.000000000000004

console.log(asind(-0.7071067811865475))
// => -44.99999999999999

console.log(asind(-0.8660254037844386))
// => -59.99999999999999

console.log(asind(-1))
// => -90
Parameters:
Name Type Description
v Number | String

輸入數字或字串

Returns:

回傳asin角度值

Type
Number

(static) atan2d(x, y) → {Number}

Description:
  • 輸入浮點數x,y計算atan2角度值

    Unit Test: Github

Source:
Example
console.log(atan2d(1, 0))
// => 0

console.log(atan2d(1, 0.5))
// => 26.56505117707799

console.log(atan2d(1, 0.7071067811865475))
// => 35.264389682754654

console.log(atan2d(1, 0.8660254037844386))
// => 40.89339464913091

console.log(atan2d(1, 1))
// => 45

console.log(atan2d(1, 1.7320508075688767))
// => 59.99999999999999

console.log(atan2d(1, Infinity))
// => 90

console.log(atan2d(1, -0.5))
// => -26.56505117707799

console.log(atan2d(1, -0.7071067811865475))
// => -35.264389682754654

console.log(atan2d(1, -0.8660254037844386))
// => -40.89339464913091

console.log(atan2d(1, -1))
// => -45

console.log(atan2d(1, -1.7320508075688767))
// => -59.99999999999999

console.log(atan2d(1, -Infinity))
// => -90
Parameters:
Name Type Description
x Number | String

輸入點x座標之數字或字串

y Number | String

輸入點y座標之數字或字串

Returns:

回傳atan2角度值

Type
Number

(static) atand(v) → {Number}

Description:
  • 輸入浮點數計算atan角度值

    Unit Test: Github

Source:
Example
console.log(atand(0))
// => 0

console.log(atand(0.5))
// => 26.56505117707799

console.log(atand(0.7071067811865475))
// => 35.264389682754654

console.log(atand(0.8660254037844386))
// => 40.89339464913091

console.log(atand(1))
// => 45

console.log(atand(1.7320508075688767))
// => 59.99999999999999

console.log(atand(Infinity))
// => 90

console.log(atand(-0.5))
// => -26.56505117707799

console.log(atand(-0.7071067811865475))
// => -35.264389682754654

console.log(atand(-0.8660254037844386))
// => -40.89339464913091

console.log(atand(-1))
// => -45

console.log(atand(-1.7320508075688767))
// => -59.99999999999999

console.log(atand(-Infinity))
// => -90
Parameters:
Name Type Description
v Number | String

輸入數字或字串

Returns:

回傳atan角度值

Type
Number

(static) attstr() → {Object}

Description:
  • 屬性字串處理

    Unit Test: Github

Source:
Example
let c
let c1
let c2
let r

let at = attstr({ uniqItems: true })

//parse
console.log('parse')

c = 'abc123'
r = at.parse(c)
console.log(r)
// => [ 'abc123' ]

c = 'abc123;abc123'
r = at.parse(c)
console.log(r)
// => [ 'abc123' ]

c = 'abc123;def456'
r = at.parse(c)
console.log(r)
// => [ 'abc123', 'def456' ]

c = 'abc@123'
r = at.parse(c)
console.log(r)
// => [ { item: 'abc@123', table: 'abc', id: '123' } ]

c = 'abc@123;abc@123'
r = at.parse(c)
console.log(r)
// => [ { item: 'abc@123', table: 'abc', id: '123' } ]

c = 'abc@123;def@456'
r = at.parse(c)
console.log(r)
// => [
//   { item: 'abc@123', table: 'abc', id: '123' },
//   { item: 'def@456', table: 'def', id: '456' }
// ]

c = ''
r = at.parse(c)
console.log(r)
// => []

//join
console.log('join')

c = ['abc123']
r = at.join(c)
console.log(r)
// => 'abc123'

c = ['abc123', 'def456']
r = at.join(c)
console.log(r)
// => 'abc123;def456'

c = ['abc@123']
r = at.join(c)
console.log(r)
// => 'abc@123'

c = ['abc@123', 'def@456']
r = at.join(c)
console.log(r)
// => 'abc@123;def@456'

c = [{ table: 'abc', id: '123' }, { table: 'def', id: '456' }]
r = at.join(c)
console.log(r)
// => 'abc@123;def@456'

c = []
r = at.join(c)
console.log(r)
// => ''

//add
console.log('add')

c1 = 'abc123'
c2 = 'def456'
r = at.add(c1, c2)
console.log(r)
// => abc123;def456

c1 = 'abc123'
c2 = 'def456;ghi789'
r = at.add(c1, c2)
console.log(r)
// => abc123;def456;ghi789

c1 = 'abc123'
c2 = 'abc123'
r = at.add(c1, c2)
console.log(r)
// => abc123

c1 = 'abc123'
c2 = 'abc123;def456'
r = at.add(c1, c2)
console.log(r)
// => abc123;def456

c1 = 'abc123;ghi789'
c2 = 'abc123;def456'
r = at.add(c1, c2)
console.log(r)
// => abc123;ghi789;def456

c1 = ''
c2 = 'abc123'
r = at.add(c1, c2)
console.log(r)
// => abc123

c1 = ''
c2 = 'abc123;def456'
r = at.add(c1, c2)
console.log(r)
// => abc123;def456

c1 = 'abc@123'
c2 = 'def@456'
r = at.add(c1, c2)
console.log(r)
// => abc@123;def@456

c1 = 'abc@123'
c2 = 'def@456;ghi@789'
r = at.add(c1, c2)
console.log(r)
// => abc@123;def@456;ghi@789

c1 = 'abc@123;ghi@789'
c2 = 'abc@123;def@456'
r = at.add(c1, c2)
console.log(r)
// => abc@123;ghi@789;def@456

c1 = ''
c2 = 'abc@123'
r = at.add(c1, c2)
console.log(r)
// => abc@123

c1 = ''
c2 = 'abc@123;def@456'
r = at.add(c1, c2)
console.log(r)
// => abc@123;def@456

//remove
console.log('remove')

c1 = 'abc123'
c2 = 'abc123'
r = at.remove(c1, c2)
console.log(r)
// => ''

c1 = 'abc123;def456'
c2 = 'abc123'
r = at.remove(c1, c2)
console.log(r)
// => def456

c1 = 'abc123'
c2 = 'def456'
r = at.remove(c1, c2)
console.log(r)
// => abc123

c1 = 'abc123'
c2 = 'ghi789;jkl012'
r = at.remove(c1, c2)
console.log(r)
// => abc123

c1 = 'abc123'
c2 = 'abc123;jkl012'
r = at.remove(c1, c2)
console.log(r)
// => ''

c1 = 'abc123;def456'
c2 = 'ghi789;jkl012'
r = at.remove(c1, c2)
console.log(r)
// => abc123;def456

c1 = 'abc123;def456'
c2 = 'def456;jkl012'
r = at.remove(c1, c2)
console.log(r)
// => abc123

c1 = ''
c2 = 'ghi789'
r = at.remove(c1, c2)
console.log(r)
// => ''

c1 = ''
c2 = 'ghi789;jkl012'
r = at.remove(c1, c2)
console.log(r)
// => ''

c1 = 'abc@123'
c2 = 'abc@123'
r = at.remove(c1, c2)
console.log(r)
// => ''

c1 = 'abc@123;def@456'
c2 = 'abc@123'
r = at.remove(c1, c2)
console.log(r)
// => def@456

c1 = 'abc@123'
c2 = 'def@456'
r = at.remove(c1, c2)
console.log(r)
// => abc@123

c1 = 'abc@123'
c2 = 'ghi@789;jkl@012'
r = at.remove(c1, c2)
console.log(r)
// => abc@123

c1 = 'abc@123'
c2 = 'abc@123;jkl@012'
r = at.remove(c1, c2)
console.log(r)
// => ''

c1 = 'abc@123;def@456'
c2 = 'ghi@789;jkl@012'
r = at.remove(c1, c2)
console.log(r)
// => abc@123;def@456

c1 = 'abc@123;def@456'
c2 = 'def@456;jkl@012'
r = at.remove(c1, c2)
console.log(r)
// => abc@123

c1 = ''
c2 = 'ghi@789'
r = at.remove(c1, c2)
console.log(r)
// => ''

c1 = ''
c2 = 'ghi@789;jkl@012'
r = at.remove(c1, c2)
console.log(r)
// => ''

let at2 = attstr({ uniqItems: true, dlmItem: ',', dlmSep: '|' })

c = 'x1|abc@123|def@456,x2|ghi@789'
r = at2.parse(c)
console.log(r)
// => [
//   { item: 'x1|abc@123|def@456', table: 'x1', id: ['abc@123','def@456'] },
//   { item: 'x2|ghi@789', table: 'x2', id: 'ghi@789' }
// ]

let at3 = attstr({ uniqItems: true, dlmItem: ',', dlmSep: '|', keyTable: 'name', keyId: 'emails' })

c = 'x1|abc@123|def@456,x2|ghi@789'
r = at3.parse(c)
console.log(r)
// => [
//   { item: 'x1|abc@123|def@456', name: 'x1', emails: ['abc@123','def@456'] },
//   { item: 'x2|ghi@789', name: 'x2', emails: 'ghi@789' }
// ]

let at4 = attstr({ uniqItems: false })

c = ['abc@123', 'abc@123', 'def@456']
r = at4.join(c)
console.log(r)
// => 'abc@123;abc@123;def@456'

c = [{ table: 'abc', id: '123' }, { table: 'def', id: '456' }, { table: 'def', id: '456' }]
r = at4.join(c)
console.log(r)
// => 'abc@123;def@456;def@456'


c1 = 'abc123;ghi789'
c2 = 'abc123;def456'
r = at4.add(c1, c2)
console.log(r)
// => abc123;ghi789;abc123;def456

c1 = 'abc@123;ghi@789'
c2 = 'abc@123;def@456'
r = at4.add(c1, c2)
console.log(r)
// => abc@123;ghi@789;abc@123;def@456
Returns:

回傳attstr物件,提供parse、join、remove、add共4種處理函數

Type
Object

(static) b642obj(b64, optopt) → {*}

Description:
  • base64字串轉任意資料

    Unit Test: Github

Source:
Example
console.log(b642obj('eyJhIjoxfQ=='))
// => { a: 1 }

console.log(b642obj(NaN, { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid b64' }
Parameters:
Name Type Attributes Default Description
b64 String

輸入base64字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳任意物件,輸入非字串或解析失敗時回傳空物件;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
*

(static) b642str(b64, optopt) → {String|Object}

Description:
  • base64字串轉一般字串

    Unit Test: Github

Source:
Example
console.log(b642str('dGVzdOS4reaWhw=='))
// => 'test中文'
Parameters:
Name Type Attributes Default Description
b64 String

輸入base64字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳一般字串,輸入非有效字串或解碼失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) b642u16arr(b64, optopt) → {Uint16Array|Object}

Description:
  • base64字串轉Uint16Array

    以逐位元組(little-endian)方式解碼,為u16arr2b64之反向;不可經u8arr2u16arr轉換,該函數為逐元素轉換無法還原大於255之元素

    Unit Test: Github

Source:
Example
console.log(b642u16arr('CwBPAAYA'))
// => new Uint16Array([11, 79, 6])
Parameters:
Name Type Attributes Default Description
b64 String

輸入base64字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Uint16Array,輸入非字串或解出之位元組長度非偶數(無法組成Uint16Array)時回傳空Uint16Array;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Uint16Array | Object

(static) b642u8arr(b64, optopt) → {Uint8Array|Object}

Description:
  • base64字串轉Uint8Array

    Unit Test: Github

Source:
Example
console.log(b642u8arr('AQItAA=='))
// => new Uint8Array([1, 2.3, '45', 'abc'])
Parameters:
Name Type Attributes Default Description
b64 String

輸入base64字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Uint8Array,輸入非字串或非合法base64時回傳空Uint8Array;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Uint8Array | Object

(static) binstr(s, keys, mode) → {Boolean}

Description:
  • 判斷字串s是否「任一出現於、全部包含於」字串陣列keys內元素

    Unit Test: Github

Source:
Example
console.log(binstr('1.25abc', 'ab'))
// => true
Parameters:
Name Type Default Description
s String

輸入要判斷的字串

keys Array | String

輸入字串陣列或字串

mode String anyone

輸入模式,可選'anyone'與'all',預設為'anyone'

Returns:

回傳判斷布林值

Type
Boolean

(static) blob2ab(bb) → {Promise}

Description:
  • 前端Blob或input檔案物件轉ArrayBuffer資料陣列

    Unit Test: Github

Source:
Example
need test in browser

let bb = new Blob([new Uint8Array([66, 97, 115])])
blob2ab(b)
    .then(function(ab){
        console.log(ab)
        // => ArrayBuffer(3) {
        //     [[Int8Array]]: Int8Array(3) [66, 97, 115]
        //     [[Uint8Array]]: Uint8Array(3) [66, 97, 115]
        //     byteLength: 3
        // }
    })
Parameters:
Name Type Description
bb Blob | File

輸入Blob或File

Returns:

回傳Promise,resolve回傳Blob或File的ArrayBuffer資料陣列,reject回傳錯誤訊息

Type
Promise

(static) blob2b64(bb) → {Promise}

Description:
  • 前端Blob或input檔案物件轉Base64字串

    Unit Test: Github

Source:
Example
need test in browser

let b64 = 'YWJj5ris6Kmm'
let u8a = b642u8arr(b64)
let bb = new Blob([u8a])
blob2b64(bb)
    .then(function(b64Out){
        console.log(b64Out)
        // => data:application/octet-stream;base64,YWJj5ris6Kmm
    })
Parameters:
Name Type Description
bb Blob | File

輸入Blob或File

Returns:

回傳Promise,resolve回傳Blob或File的Base64字串,reject回傳錯誤訊息

Type
Promise

(static) blob2str(bb) → {Promise}

Description:
  • 前端Blob或input檔案物件轉字串

    Unit Test: Github

Source:
Example
need test in browser

let strIn = 'abc測試'
let b64 = str2b64(strIn)
let u8a = b642u8arr(b64)
let bb = new Blob([u8a])
blob2str(bb)
    .then(function(strOut){
        console.log(strOut)
        // => abc測試
    })
Parameters:
Name Type Description
bb Blob | File

輸入Blob或File

Returns:

回傳Promise,resolve回傳Blob或File的字串,reject回傳錯誤訊息

Type
Promise

(static) blob2u8arr(bb) → {Promise}

Description:
  • 前端Blob或input檔案物件轉Uint8Array資料陣列

    Unit Test: Github

Source:
Example
need test in browser

let u8aIn = new Uint8Array([97, 98, 99, 230, 184, 172, 232, 169, 166])
let bb = new Blob([u8aIn])
blob2u8arr(bb)
    .then(function(u8aOut){
        console.log(u8aOut)
        // => Uint8Array(9) [97, 98, 99, 230, 184, 172, 232, 169, 166]
    })
Parameters:
Name Type Description
bb Blob | File

輸入Blob或File

Returns:

回傳Promise,resolve回傳Blob或File的Uint8Array資料陣列,reject回傳錯誤訊息

Type
Promise

(static) blobs2b64s(bbs) → {Promise}

Description:
  • 前端Blob或input檔案物件陣列,逐一轉為各檔案之Base64資料

    Unit Test: Github

Source:
Example
need test in browser

let b64 = 'YWJj5ris6Kmm'
let u8a = b642u8arr(b64)
let bb = new Blob([u8a])
let bbs = [bb]
blobs2b64s(bbs)
    .then(function(b64){
        console.log(b64)
        // => ["data:application/octet-stream;base64,YWJj5ris6Kmm"]
    })
Parameters:
Name Type Description
bbs Blob | File

輸入Blob或File陣列

Returns:

回傳Promise,resolve回傳各Blob或File的Base64資料,reject回傳錯誤訊息

Type
Promise

(static) blobs2u8arrs(bbs) → {Promise}

Description:
  • 前端Blob或input檔案物件陣列,逐一轉為各檔案之Uint8Array資料陣列

    Unit Test: Github

Source:
Example
need test in browser

let u8a = new Uint8Array([97, 98, 99, 230, 184, 172, 232, 169, 166])
let bb = new Blob([u8a])
let bbs = [bb]
blobs2u8arrs(bbs)
    .then(function(u8as){
        console.log(u8as)
        // => [Uint8Array(9)]
        //      0: Uint8Array(9) [97, 98, 99, 230, 184, 172, 232, 169, 166]
        //      length: 1
    })
Parameters:
Name Type Description
bbs Blob | File

輸入Blob或File陣列

Returns:

回傳Promise,resolve回傳各Blob或File的Uint8Array資料陣列,reject回傳錯誤訊息

Type
Promise

(static) bodyLog(c)

Description:
  • 前端顯示log訊息,預設插入至body

    Unit Test: Github

Source:
Example
bodyLog('show訊息')
// => 由html顯示'show訊息'
Parameters:
Name Type Description
c String

輸入字串

(static) browserView(data, type)

Description:
  • 前端瀏覽器以分頁開啟指定數據如文字、圖片、影片、pdf等

    Unit Test: Github

Source:
Example
need test in browser

let text = 'abc'
browserView(u8a, 'text/plain')

let u8a = new Uint8Array([66, 97, 115])
browserView(u8a, 'application/pdf')
Parameters:
Name Type Description
data *

輸入數據

type String

輸入數據MIME Type字串,例如'application/pdf'或'text/plain'等,詳見[https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types]

(static) browserViewFromBlob(bb, type)

Description:
  • 前端瀏覽器以分頁開啟blob數據如文字、圖片、影片、pdf等

    Unit Test: Github

Source:
Example
need test in browser

let u8a = new Uint8Array([66, 97, 115])
let bb = new Blob([u8a])
browserView(bb, 'application/pdf')
Parameters:
Name Type Description
bb Blob

輸入blob數據

type String

輸入數據MIME Type字串,例如'application/pdf'或'text/plain'等,詳見[https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types]

(static) browserViewFromText(text)

Description:
  • 前端瀏覽器以分頁開啟文字資料

    Unit Test: Github

Source:
Example
need test in browser

let text = 'abc'
browserViewFromText(u8a)
Parameters:
Name Type Description
text String

輸入文字字串

(static) browserViewFromU8arr(u8a, type)

Description:
  • 前端瀏覽器以分頁開啟Uint8Array數據如文字、圖片、影片、pdf等

    Unit Test: Github

Source:
Example
need test in browser

let u8a = new Uint8Array([66, 97, 115])
browserView(u8a, 'application/pdf')
Parameters:
Name Type Description
u8a Uint8Array

輸入Uint8Array數據

type String

輸入數據MIME Type字串,例如'application/pdf'或'text/plain'等,詳見[https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types]

(static) bs2u8arr(bs, optopt) → {Unit8Array|Object}

Description:
  • BinaryString轉Unit8Array,BinaryString為UTF-16編碼

    Unit Test: Github

Source:
Example
console.log(bs2u8arr('abc'))
// => new Uint8Array([97, 98, 99])
Parameters:
Name Type Attributes Default Description
bs String

輸入BinaryString字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Unit8Array資料,輸入非有效字串時回傳空Unit8Array;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Unit8Array | Object

(static) bufRead(buffer, offset, isBE, mLen, nBytes) → {Number}

Description:
Source:
Example
let offset = 0
let isBE = true
let mLen = 52
let nBytes = 8
let b = new Uint8Array([66, 117, 16, 240, 246, 48, 64, 0]) //1447656645380
let j = bufRead(b, offset, isBE, mLen, nBytes)
console.log(j)
// => 1447656645380
Parameters:
Name Type Description
buffer Uint8Array | Buffer

輸入Uint8Array(Nodejs,Browser)或Buffer(Nodejs)資料

offset Integer

輸入平移整數

isBE Boolean

輸入是否為大端序Big-Endian

mLen Integer

輸入有效位數整數

nBytes Integer

輸入使用位元組整數

Returns:

回傳數字

Type
Number

(static) bufReadDbl(buffer, offsetopt, isBEopt, mLenopt, nBytesopt) → {Number}

Description:
  • 讀取Uint8Array(Nodejs,Browser)或Buffer(Nodejs)內的浮點數資料

    Unit Test: Github

Source:
Example
let b = new Uint8Array([66, 117, 16, 240, 246, 48, 64, 0]) //1447656645380
let j = bufReadDbl(b)
console.log(j)
// => 1447656645380
Parameters:
Name Type Attributes Default Description
buffer Uint8Array | Buffer

輸入Uint8Array(Nodejs,Browser)或Buffer(Nodejs)資料

offset Integer <optional>
0

輸入平移整數,預設0

isBE Boolean <optional>
true

輸入是否為大端序Big-Endian,預設true

mLen Integer <optional>
52

輸入有效位數整數,預設52

nBytes Integer <optional>
8

輸入使用位元組整數,預設8

Returns:

回傳數字

Type
Number

(static) bufWrite(value, buffer, offset, isBE, mLen, nBytes)

Description:
Source:
Example
let offset = 0
let isBE = true
let mLen = 52
let nBytes = 8

let i = 1447656645380 //new Uint8Array([66, 117, 16, 240, 246, 48, 64, 0])
let b = Buffer.alloc(8)
bufWrite(i, b, offset, isBE, mLen, nBytes)
console.log(b)
// >= <Buffer 42 75 10 f0 f6 30 40 00>
console.log(new Uint8Array(b))
// => Uint8Array [66, 117, 16, 240, 246, 48, 64, 0]
Parameters:
Name Type Description
value Number

輸入數字

buffer Uint8Array | Buffer

輸入被寫入的Uint8Array(Nodejs,Browser)或Buffer(Nodejs)資料

offset Integer

輸入平移整數

isBE Boolean

輸入是否為大端序Big-Endian

mLen Integer

輸入有效位數整數

nBytes Integer

輸入使用位元組整數

(static) bufWriteDbl(value, buffer, offsetopt, isBEopt, mLenopt, nBytesopt) → {Number}

Description:
  • 寫入Uint8Array(Nodejs,Browser)或Buffer(Nodejs)內的浮點數資料,需先依照nBytes宣告Uint8Array或Buffer空間

    Unit Test: Github

Source:
Example
let i = 1447656645380 //new Uint8Array([66, 117, 16, 240, 246, 48, 64, 0])
let b = Buffer.alloc(8)
bufWriteDbl(i, b)
console.log(b)
// >= <Buffer 42 75 10 f0 f6 30 40 00>
console.log(new Uint8Array(b))
// => Uint8Array [66, 117, 16, 240, 246, 48, 64, 0]
Parameters:
Name Type Attributes Default Description
value Number

輸入數字

buffer Uint8Array | Buffer

輸入Uint8Array(Nodejs,Browser)或Buffer(Nodejs)資料

offset Integer <optional>
0

輸入平移整數,預設0

isBE Boolean <optional>
true

輸入是否為大端序Big-Endian,預設true

mLen Integer <optional>
52

輸入有效位數整數,預設52

nBytes Integer <optional>
8

輸入使用位元組整數,預設8

Returns:

回傳數字

Type
Number

(static) cache(optopt) → {Object}

Description:
  • 非同步函數快取,通過指定key代表函數與其輸入、代為執行與快取。快取可設定失效時間,超過失效時間則重新執行與再更新快取

    Unit Test: Github

Source:
Example
async function topAsync() {

    function test1() {
        return new Promise((resolve, reject) => {
            let ms = []

            let oc = cache()

            // oc.on('message', function(msg) {
            //     console.log('message', msg)
            // })
            // oc.on('error', function(msg) {
            //     console.log('error', msg)
            // })

            let i = 0
            let j = 0
            function fun(v1, v2) {
                i++
                console.log('call fun, count=' + i)
                ms.push('call fun, count=' + i)
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        j++
                        ms.push(v1 + '|' + v2 + ', count=' + j)
                        resolve(v1 + '|' + v2 + ', count=' + j)
                    }, 300)
                })
            }

            oc.set('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1200 }) //快取1200ms, 但第1次執行就需要300ms, 故執行完畢後只會再保留800ms
            setTimeout(function() {
                //第1次呼叫(1ms), 此時沒有快取只能執行取值, 執行300ms後回應, 會取得第1次結果(count=1)
                oc.get('fun')
                    .then(function(msg) {
                        console.log('fun 1st', msg)
                        ms.push('fun 1st', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 1st error', err)
                        ms.push('fun 1st error', String(err))
                    })
            }, 1)
            setTimeout(function() {
                //第2次呼叫(50ms), 此時第1次呼叫還沒完成(要到300ms), 故get會共用執行中的promise等待, 於第1次執行完畢(300ms)時一併取得第1次結果(count=1)
                oc.get('fun')
                    .then(function(msg) {
                        console.log('fun 2nd', msg)
                        ms.push('fun 2nd', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 2nd error', err)
                        ms.push('fun 2nd error', String(err))
                    })
            }, 50)
            setTimeout(function() {
                //第3次呼叫(250ms), 此時第1次呼叫還沒完成(要到300ms), 同第2次呼叫共用執行中的promise等待, 於300ms時一併取得第1次結果(count=1)
                oc.get('fun')
                    .then(function(msg) {
                        console.log('fun 3rd', msg)
                        ms.push('fun 3rd', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 3rd error', err)
                        ms.push('fun 3rd error', String(err))
                    })
            }, 250)
            setTimeout(function() {
                //第4次呼叫(500ms), 此時第1次呼叫已結束(300ms), 且第1次快取(count=1)未過期(要到1200ms), 故get可拿到第1次計算的快取(count=1)
                oc.get('fun')
                    .then(function(msg) {
                        console.log('fun 4th', msg)
                        ms.push('fun 4th', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 4th error', err)
                        ms.push('fun 4th error', String(err))
                    })
            }, 500)
            setTimeout(function() {
                //第5次呼叫(1300ms), 此時第1次快取(count=1)已過期(1200ms), 故重新執行取值(1300~1600ms執行, 2500ms過期), 於1600ms取得第2次結果(count=2)
                oc.get('fun')
                    .then(function(msg) {
                        console.log('fun 5th', msg)
                        ms.push('fun 5th', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 5th error', err)
                        ms.push('fun 5th error', String(err))
                    })
            }, 1300)
            setTimeout(function() {
                //第6次呼叫(1600ms), 此時第2次執行剛好結束(1600ms), 若尚未結束則共用執行中的promise等待, 若已結束則直接拿快取, 皆會取得第2次結果(count=2)
                oc.get('fun')
                    .then(function(msg) {
                        console.log('fun 6th', msg)
                        ms.push('fun 6th', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 6th error', err)
                        ms.push('fun 6th error', String(err))
                    })
            }, 1600)

            setTimeout(function() {
                resolve(ms)
            }, 2400)

        })
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // call fun, count=1
    // fun 1st inp1|inp2, count=1
    // fun 2nd inp1|inp2, count=1
    // fun 3rd inp1|inp2, count=1
    // fun 4th inp1|inp2, count=1
    // call fun, count=2
    // fun 5th inp1|inp2, count=2
    // fun 6th inp1|inp2, count=2
    // ["call fun, count=1","inp1|inp2, count=1","fun 1st","inp1|inp2, count=1","fun 2nd","inp1|inp2, count=1","fun 3rd","inp1|inp2, count=1","fun 4th","inp1|inp2, count=1","call fun, count=2","inp1|inp2, count=2","fun 5th","inp1|inp2, count=2","fun 6th","inp1|inp2, count=2"]

    function test2() {
        return new Promise((resolve, reject) => {
            let ms = []

            let oc = cache()

            // oc.on('message', function(msg) {
            //     console.log('message', msg)
            // })
            // oc.on('error', function(msg) {
            //     console.log('error', msg)
            // })

            let i = 0
            let j = 0
            function fun(v1, v2) {
                i++
                console.log('call fun, count=' + i)
                ms.push('call fun, count=' + i)
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        j++
                        ms.push(v1 + '|' + v2 + ', count=' + j)
                        resolve(v1 + '|' + v2 + ', count=' + j)
                    }, 300)
                })
            }

            //首次getProxy即註冊並執行取值(0~300ms), 快取1200ms, 但第1次執行就需要300ms, 故執行完畢後只會再保留800ms
            //因useCacheWhenError預設false, 函數執行失敗時get與getProxy會reject, 故此種不取值之呼叫仍須接catch, 否則會產生unhandled rejection
            oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1200 })
                .catch(function(err) {
                    console.log('getProxy error', err)
                })
            setTimeout(function() {
                //第1次呼叫(1ms), 此時首次getProxy執行中, 會共用執行中的promise等待, 於300ms取得第1次結果(count=1)
                oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1200 })
                    .then(function(msg) {
                        console.log('fun 1st', msg)
                        ms.push('fun 1st', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 1st error', err)
                        ms.push('fun 1st error', String(err))
                    })
            }, 1)
            setTimeout(function() {
                //第2次呼叫(100ms), 此時執行中, 同第1次呼叫共用執行中的promise等待, 於300ms取得第1次結果(count=1)
                oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1200 })
                    .then(function(msg) {
                        console.log('fun 2nd', msg)
                        ms.push('fun 2nd', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 2nd error', err)
                        ms.push('fun 2nd error', String(err))
                    })
            }, 100)
            setTimeout(function() {
                //第3次呼叫(500ms), 此時已有快取, 直接取得第1次結果(count=1)
                oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1200 })
                    .then(function(msg) {
                        console.log('fun 3rd', msg)
                        ms.push('fun 3rd', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 3rd error', err)
                        ms.push('fun 3rd error', String(err))
                    })
            }, 500)
            setTimeout(function() {
                //第4次呼叫(1300ms), 此時第1次快取(count=1)已失效(1200ms), 會重新呼叫函數取值(1300~1600ms), 取得第2次結果(count=2)
                oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1200 })
                    .then(function(msg) {
                        console.log('fun 4th', msg)
                        ms.push('fun 4th', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 4th error', err)
                        ms.push('fun 4th error', String(err))
                    })
            }, 1300)

            setTimeout(function() {
                resolve(ms)
            }, 1700)

        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // call fun, count=1
    // fun 1st inp1|inp2, count=1
    // fun 2nd inp1|inp2, count=1
    // fun 3rd inp1|inp2, count=1
    // call fun, count=2
    // fun 4th inp1|inp2, count=2
    // ["call fun, count=1","inp1|inp2, count=1","fun 1st","inp1|inp2, count=1","fun 2nd","inp1|inp2, count=1","fun 3rd","inp1|inp2, count=1","call fun, count=2","inp1|inp2, count=2","fun 4th","inp1|inp2, count=2"]

    function test3() {
        return new Promise((resolve, reject) => {
            let ms = []

            let oc = cache()

            // oc.on('message', function(msg) {
            //     console.log('message', msg)
            // })
            // oc.on('error', function(msg) {
            //     console.log('error', msg)
            // })

            let i = 0
            let j = 0
            function fun(v1, v2) {
                i++
                console.log('call fun, count=' + i)
                ms.push('call fun, count=' + i)
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        j++
                        ms.push(v1 + '|' + v2 + ', count=' + j)
                        resolve(v1 + '|' + v2 + ', count=' + j)
                    }, 300)
                })
            }

            //首次getProxy即註冊並執行取值(0~300ms), 快取1500ms, 但第1次執行就需要300ms, 故執行完畢後只會再保留1200ms
            //因useCacheWhenError預設false, 函數執行失敗時get與getProxy會reject, 故此種不取值之呼叫仍須接catch, 否則會產生unhandled rejection
            oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1500 })
                .catch(function(err) {
                    console.log('getProxy error', err)
                })
            setTimeout(function() {
                //第1次呼叫(延遲1ms), 此時首次getProxy執行中, 會共用執行中的promise等待, 於300ms取得第1次結果(count=1)
                oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1500 })
                    .then(function(msg) {
                        console.log('fun 1st', msg)
                        ms.push('fun 1st', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 1st error', err)
                        ms.push('fun 1st error', String(err))
                    })
            }, 1)
            setTimeout(function() {
                //第2次呼叫(延遲200ms), 此時執行中, 同第1次呼叫共用執行中的promise等待, 於300ms取得第1次結果(count=1), 早於被強制更新(1100ms)
                oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1500 })
                    .then(function(msg) {
                        console.log('fun 2nd', msg)
                        ms.push('fun 2nd', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 2nd error', err)
                        ms.push('fun 2nd error', String(err))
                    })
            }, 200)
            setTimeout(function() {
                //第3次呼叫(500ms), 此時已有快取, 直接取得第1次結果(count=1)
                oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1500 })
                    .then(function(msg) {
                        console.log('fun 3rd', msg)
                        ms.push('fun 3rd', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 3rd error', err)
                        ms.push('fun 3rd error', String(err))
                    })
            }, 500)
            setTimeout(function() {
                //更新快取值(延遲1100ms), 快取值為abc, 快取時間也被更新至此時, 故會重新計算1500ms才會失效
                oc.update('fun', 'abc')
                console.log('fun update', 'abc')
                ms.push('fun update', 'abc')
            }, 1100)
            setTimeout(function() {
                //第4次呼叫(延遲1300ms), 此時會取得被強制更新之快取值(abc), 快取還剩1300ms才失效(也就是在2600ms失效)
                oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1500 })
                    .then(function(msg) {
                        console.log('fun 4th', msg)
                        ms.push('fun 4th', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 4th error', err)
                        ms.push('fun 4th error', String(err))
                    })
            }, 1300)
            setTimeout(function() {
                //第5次呼叫(延遲2700ms), 此時被強制更新之快取值(abc)已失效, 會重新呼叫函數取值, 取得第2次結果(count=2)
                oc.getProxy('fun', { fun, inputs: ['inp1', 'inp2'], timeExpired: 1500 })
                    .then(function(msg) {
                        console.log('fun 5th', msg)
                        ms.push('fun 5th', msg)
                    })
                    .catch(function(err) {
                        console.log('fun 5th error', err)
                        ms.push('fun 5th error', String(err))
                    })
            }, 2700)

            setTimeout(function() {
                resolve(ms)
            }, 3100)

        })
    }
    console.log('test3')
    let r3 = await test3()
    console.log(JSON.stringify(r3))
    // test3
    // call fun, count=1
    // fun 1st inp1|inp2, count=1
    // fun 2nd inp1|inp2, count=1
    // fun 3rd inp1|inp2, count=1
    // fun update abc
    // fun 4th abc
    // call fun, count=2
    // fun 5th inp1|inp2, count=2
    // ["call fun, count=1","inp1|inp2, count=1","fun 1st","inp1|inp2, count=1","fun 2nd","inp1|inp2, count=1","fun 3rd","inp1|inp2, count=1","fun update","abc","fun 4th","abc","call fun, count=2","inp1|inp2, count=2","fun 5th","inp1|inp2, count=2"]

    async function test4() {
        //執行中clear與update: clear於執行中呼叫仍有效(執行中之呼叫取得本次結果, 下次get重新執行); update於執行中呼叫以update之值為準, 不被執行結果覆寫
        let oc = cache()

        let n = 0
        let fun = () => {
            n++
            return new Promise(function(resolve, reject) {
                setTimeout(function() {
                    resolve('count=' + n)
                }, 100)
            })
        }

        //clear於執行中
        oc.set('clear', { fun, inputs: [], timeExpired: 30000 })
        let pm1 = oc.get('clear')
        await new Promise((resolve) => setTimeout(resolve, 10))
        oc.clear('clear')
        let v1 = await pm1 //執行中之呼叫取得本次結果(count=1)
        let v2 = await oc.get('clear') //clear有效, 重新執行(count=2)
        let v3 = await oc.get('clear') //快取(count=2)

        //update於執行中
        n = 0
        oc.set('update', { fun, inputs: [], timeExpired: 30000 })
        let pm2 = oc.get('update')
        await new Promise((resolve) => setTimeout(resolve, 10))
        oc.update('update', 'abc')
        let v4 = await pm2 //以update之值為準(abc)
        let v5 = await oc.get('update') //快取(abc), 不重新執行

        return { v1, v2, v3, v4, v5, n }
    }
    console.log('test4')
    let r4 = await test4()
    console.log(JSON.stringify(r4))
    // test4
    // {"v1":"count=1","v2":"count=2","v3":"count=2","v4":"abc","v5":"abc","n":1}

    async function test5() {
        //timeFrom: 預設'start'自fun開始起算, 'end'自fun結束起算; fun耗時200ms, timeExpired 220ms, 結束後再隔120ms取值: 'start'時已過期(320>220)重新執行, 'end'時未過期(120<220)取快取
        let oc = cache()

        let n = 0
        let fun = () => {
            n++
            return new Promise(function(resolve, reject) {
                setTimeout(function() {
                    resolve('count=' + n)
                }, 200)
            })
        }

        oc.set('start', { fun, inputs: [], timeExpired: 220 }) //預設timeFrom='start'
        let v1 = await oc.get('start')
        await new Promise((resolve) => setTimeout(resolve, 120))
        let v2 = await oc.get('start')

        n = 0
        oc.set('end', { fun, inputs: [], timeExpired: 220, timeFrom: 'end' })
        let v3 = await oc.get('end')
        await new Promise((resolve) => setTimeout(resolve, 120))
        let v4 = await oc.get('end')

        return { v1, v2, v3, v4 }
    }
    console.log('test5')
    let r5 = await test5()
    console.log(JSON.stringify(r5))
    // test5
    // {"v1":"count=1","v2":"count=2","v3":"count=1","v4":"count=1"}

    async function test6() {
        //useCacheWhenError: 預設false則失敗不寫入快取, 錯誤拋給執行者與等待中之併發呼叫, 下次get重新執行; 設true則失敗仍寫入快取(值為undefined)並保留整個timeExpired, 呼叫端取得undefined而非收到錯誤; 兩者error事件皆發出
        let oc = cache()

        let errs = []
        oc.on('error', function(msg) {
            errs.push(msg.key + ':' + msg.msg.message)
        })

        let n = 0
        let fun = () => {
            n++
            return new Promise(function(resolve, reject) {
                setTimeout(function() {
                    if (n <= 2) {
                        reject(new Error('down' + n))
                    }
                    else {
                        resolve('up' + n)
                    }
                }, 50)
            })
        }

        oc.set('fun', { fun, inputs: [], timeExpired: 30000 }) //預設useCacheWhenError=false

        //第1次執行失敗: 執行者與等待中之併發呼叫皆收到reject
        let pm1 = oc.get('fun').then((v) => 'ok:' + v).catch((e) => 'err:' + e.message)
        await new Promise((resolve) => setTimeout(resolve, 10))
        let pm2 = oc.get('fun').then((v) => 'ok:' + v).catch((e) => 'err:' + e.message)
        let r1 = await pm1
        let r2 = await pm2

        //第2次執行仍失敗(失敗不寫入快取故重新執行)
        let r3 = await oc.get('fun').then((v) => 'ok:' + v).catch((e) => 'err:' + e.message)

        //第3次執行成功並快取
        let r4 = await oc.get('fun').then((v) => 'ok:' + v).catch((e) => 'err:' + e.message)
        let r5 = await oc.get('fun').then((v) => 'ok:' + v).catch((e) => 'err:' + e.message)

        //useCacheWhenError=true時, 失敗不拋錯而是取得undefined, 且快取整個timeExpired故不重新執行
        let m = 0
        let funErr = async () => {
            m++
            throw new Error('always down')
        }
        oc.set('funErr', { fun: funErr, inputs: [], timeExpired: 30000, useCacheWhenError: true })
        let c1 = await oc.get('funErr').then((v) => 'ok:' + v).catch((e) => 'err:' + e.message)
        let c2 = await oc.get('funErr').then((v) => 'ok:' + v).catch((e) => 'err:' + e.message)

        await new Promise((resolve) => setTimeout(resolve, 50)) //error事件以timer脫勾發送, 稍待

        return { rs: [r1, r2, r3, r4, r5], n, cs: [c1, c2], m, errs }
    }
    console.log('test6')
    let r6 = await test6()
    console.log(JSON.stringify(r6))
    // test6
    // {"rs":["err:down1","err:down1","err:down2","ok:up3","ok:up3"],"n":3,"cs":["ok:undefined","ok:undefined"],"m":1,"errs":["fun:down1","fun:down2","funErr:always down"]}

    async function test7() {
        //useCloneDeep: 預設true回傳複製值, 呼叫端修改不影響快取; 設false則直接回傳快取內之值(為同一參照), 可省去大量數據之複製開銷, 但呼叫端須自行確保不修改
        let oc = cache()

        let fun = async () => {
            return { a: 1 }
        }

        //預設true
        oc.set('clone', { fun, inputs: [], timeExpired: 30000 })
        let v1 = await oc.get('clone')
        v1.a = 2 //修改所取得之值
        let v2 = await oc.get('clone') //快取未受影響

        //get傳入useCloneDeep=false
        oc.set('noclone', { fun, inputs: [], timeExpired: 30000 })
        let v3 = await oc.get('noclone', { useCloneDeep: false })
        let v4 = await oc.get('noclone', { useCloneDeep: false })

        //set時設定useCloneDeep=false, get未指定則沿用set之設定
        oc.set('setnoclone', { fun, inputs: [], timeExpired: 30000, useCloneDeep: false })
        let v5 = await oc.get('setnoclone')
        let v6 = await oc.get('setnoclone')

        //get之設定可覆寫set之設定
        let v7 = await oc.get('setnoclone', { useCloneDeep: true })

        return { v2, sameNoclone: v3 === v4, sameSetnoclone: v5 === v6, sameOverride: v5 === v7 }
    }
    console.log('test7')
    let r7 = await test7()
    console.log(JSON.stringify(r7))
    // test7
    // {"v2":{"a":1},"sameNoclone":true,"sameSetnoclone":true,"sameOverride":false}

}
topAsync().catch(() => {})
Parameters:
Name Type Attributes Default Description
opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部邏輯事件發生當下同步呼叫(非計時器週期),傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷,事件欄位只含呼叫端傳入之key(通常為字串)、字串、布林與數字,不含fun之輸入與回傳值;type為'set'時為set呼叫(含key、added為是否新增,key已存在而被忽略時為false),'get'時為get呼叫之判定(含key、n為本實例第幾次get呼叫、mode為'invalid'無效key,'wait'共用執行中之promise,'first'首次或clear後執行,'expired'因超過timeExpired而重新執行,'cache'使用快取,timeDiff為mode是'expired'或'cache'時距快取時間之毫秒數,其餘mode為null),'start'時為即將呼叫fun(含key、run為本實例第幾次執行),'end'時為該次fun執行結束(含key、run、ok為fun是否未失敗、stored為本次結果是否寫入快取,失敗而不寫入快取或執行中被update而不覆寫時為false)且早於該次執行之get settle,'update'、'clear'時為對應呼叫(含key、running為該key是否執行中),'remove'時為對應呼叫(含key);key無效時update、clear、remove不通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響快取,預設null

Returns:

回傳事件物件,可呼叫函數on、set、get、getProxy、clear、remove。on為監聽事件,需自行監聽message與error事件。set為加入待執行函數,函數結束回傳欲快取的值,set傳入參數依序為key與快取物件,key為唯一識別字串,可使用函數加上輸入參數作為key,因考慮輸入參數可能為大量數據會有效能問題,由開發者自行決定key,而快取物件需設定欄位fun為待執行的非同步函數(fun非函數時視為回傳undefined之空函數)、inputs為待執行函數fun的傳入參數組、timeExpired為過期時間整數,單位為毫秒ms,預設5000、timeFrom為快取時間之起算點,可選'start'或'end','start'為自函數開始執行時起算,'end'為自函數執行完畢時起算,預設'start'、useCacheWhenError為函數執行失敗時是否仍寫入快取,預設false,失敗不寫入快取,錯誤會拋給呼叫端(含等待中之併發呼叫)且下次get會重新執行(fun同步拋錯與其Promise被reject皆同),故呼叫端需自行catch,若設定true則失敗仍寫入快取,此時快取值為undefined並保留整個timeExpired,呼叫端不會收到錯誤而是取得undefined、useCloneDeep為取值時是否複製快取值後才回傳,預設true,因快取值若為物件則可能受外部調用而被修改到快取區的記憶體,若快取值為大量數據可設定false以節省複製開銷,但呼叫端須自行確保不修改所取得之值。get為依照key取得目前快取值,若該key之函數執行中則共用執行中之promise,待其執行完畢即一併取得結果,不另行輪詢;fun內(含同步階段)呼叫同key之get亦共用本次執行而不重複執行,惟fun若等待該get之結果則互相等待而永不settle(需逾時者以pmTimeout包裝),get傳入參數依序為key與設定物件,設定物件之欄位useCloneDeep可覆寫set時之設定,未指定時沿用set之設定。getProxy為合併set與get功能,直接set註冊待執行函數與取值,傳入參數同set,且會一併傳給get,回傳同get。update為強制更新key所屬快取值,同時也會更新該快取之時間至當前,視同函數已執行完成並寫入新值,故對已set但尚未執行過(或已clear)之key,其後get取得update之值而不執行函數(直至過期),於函數執行中呼叫時以update之值為準,不被執行結果覆寫;update之後才呼叫clear者,下次get仍重新執行。clear為清除key所屬快取的是否執行標記,使該快取視為需重新執行函數取值,於函數執行中呼叫亦有效,執行中之呼叫仍取得本次結果,下次get才重新執行。remove為直接清除key所屬快取,清除後用set重設。事件以timer脫勾派發且事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯,故其不會使行程崩潰,會改以error事件回報{ fun: 'listener', name, msg, args },無error監聽者則console.error;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection;故error事件同時承載本模組自身之錯誤(fun為'get'等且帶key)與監聽器錯誤(fun為'listener'且無key),監聽error時請先以fun欄位分流

Type
Object

(static) cacheBd(fun, optopt) → {Object}

Description:
  • 非同步函數快取,封裝指定非同步函數與再提供取用快取版

    get須定期或高執行率,getFromCache雖能自己自動調用,但僅第1次取得後數據就無法更新,此會導致一直取得舊數據

    Unit Test: Github

Source:
Example
let test1 = async () => {
    let ms = []

    let fun = async(p1, p2) => {
        await delay(300)
        return `${p1}:${p2}`
    }

    let bc = cacheBd(fun)
    let execFun = bc.get
    let execFunCache = bc.getFromCache

    let pm1 = execFunCache(123, 'abc') //pm1執行取得快取, 因無快取將持續等待
    pm1
        .then((res) => {
            // console.log('res', res)
            ms.push({ result: `pm1-${res}` })
        })

    let pm2 = execFun(4.56, 'def') //pm2執行原本函數與使用輸入, 會最先回傳, 且更新快取給等待或之後取得快取使用
    pm2
        .then((res) => {
            // console.log('res', res)
            ms.push({ result: `pm2-${res}` })
        })

    let pm3 = execFunCache(78.9, 'xyz') //pm3執行取得快取, 因已有快取將直接取得快取
    pm3
        .then((res) => {
            // console.log('res', res)
            ms.push({ result: `pm3-${res}` })
        })

    await Promise.all([pm1, pm2, pm3])
    // console.log('rs', rs)

    console.log('ms', ms)
    return ms
}
await test1()
// ms [
//   { result: 'pm2-4.56:def' },
//   { result: 'pm1-4.56:def' },
//   { result: 'pm3-4.56:def' }
// ]

let test2 = async () => {
    let ms = []

    let fun = async(p1, p2) => {
        await delay(300)
        return `${p1}:${p2}`
    }

    let bc = cacheBd(fun)
    let execFun = bc.get
    let execFunCache = bc.getFromCache

    setTimeout(() => {
        console.log('pm1 exec..')
        let pm1 = execFunCache(123, 'abc') //pm1執行取得快取, 因無快取將持續等待
        pm1
            .then((res) => {
                console.log('pm1 then', res)
                ms.push({ result: `pm1-${res}` })
            })
    }, 1)

    setTimeout(() => {
        console.log('pm2 exec..')
        let pm2 = execFun(4.56, 'def') //1000ms後, pm2執行原本函數與使用輸入, 會最先回傳, 且更新快取給等待或之後取得快取使用
        pm2
            .then((res) => {
                console.log('pm2 then', res)
                ms.push({ result: `pm2-${res}` })
            })
    }, 1000)

    setTimeout(() => {
        console.log('pm3 exec..')
        let pm3 = execFunCache(78.9, 'xyz') //pm3執行取得快取, 因已有快取將直接取得快取
        pm3
            .then((res) => {
                console.log('pm3 then', res)
                ms.push({ result: `pm3-${res}` })
            })
    }, 3000)

    await delay(5000)

    console.log('ms', ms)
    return ms
}
await test2()
// ms [
//   { result: 'pm2-4.56:def' },
//   { result: 'pm1-4.56:def' },
//   { result: 'pm3-4.56:def' }
// ]

let test3 = async () => {
    let ms = []

    let fun = async(p1, p2) => {
        await delay(300)
        return `${p1}:${p2}`
    }

    let bc = cacheBd(fun)
    let execFun = bc.get
    let execFunCache = bc.getFromCache

    setTimeout(() => {
        console.log('pm1 exec..')
        let pm1 = execFunCache(123, 'abc') //pm1執行取得快取, 因無快取將持續等待, 3000ms將執行get與自己的輸入, 並最先回傳
        pm1
            .then((res) => {
                console.log('pm1 then', res)
                ms.push({ result: `pm1-${res}` })
            })
    }, 1)

    setTimeout(() => {
        console.log('pm2 exec..')
        let pm2 = execFun(4.56, 'def') //3500ms後, pm2執行原本函數與使用輸入, 計算完畢則會覆蓋快取, 此快取將再給等待或之後取得快取使用
        pm2
            .then((res) => {
                console.log('pm2 then', res)
                ms.push({ result: `pm2-${res}` })
            })
    }, 3500)

    setTimeout(() => {
        console.log('pm3 exec..')
        let pm3 = execFunCache(78.9, 'xyz') //pm3執行取得快取, 因已有快取將直接取得快取
        pm3
            .then((res) => {
                console.log('pm3 then', res)
                ms.push({ result: `pm3-${res}` })
            })
    }, 4500)

    await delay(6500)

    console.log('ms', ms)
    return ms
}
await test3()
// ms [
//   { result: 'pm1-123:abc' },
//   { result: 'pm2-4.56:def' },
//   { result: 'pm3-4.56:def' }
// ]
Parameters:
Name Type Attributes Default Description
fun function

輸入非同步函數

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
nMax Integer <optional>
200

輸入使用快取版之總嘗試次數,為正整數,超過則報錯,預設200

nTrigger Integer <optional>
3

輸入嘗試指定次數,為正整數,若嘗試指定次數之後皆無快取則須自動執行get用以取得快取,預設3

onTrace function <optional>
null

輸入觀察回調函數,於內部邏輯事件發生當下同步呼叫(輪詢之等待本身不通知,其間隔由delay提供),傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷,事件欄位只含數字與布林,不含fun之輸入與回傳值;type為'poll'時為getFromCache每次嘗試之判定(含call為本實例第幾次getFromCache呼叫、i為第幾次嘗試、cached為是否已有快取、running為執行中之get數、trigger為是否因此觸發get,須i大於nTrigger且無快取且無執行中之get),觸發時緊接著通知該次get,'get'時為get開始(含seq為本實例第幾次get,不論直接呼叫或由getFromCache觸發),'done'時為該次get結束(含seq、ok為fun是否未失敗、cached為其結果是否寫入快取,先啟動而後完成者不覆蓋後啟動者已寫入之快取時為false)且早於該次get之settle,'settle'時為getFromCache結束(含call、ok為是否取得快取,超過nMax而reject時為false)且早於其settle;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響快取,預設null

Returns:

回傳事件物件,可呼叫事件get、getFromCache,get為封裝後封裝原本非同步函數,getFromCache為提供取用快取版之非同步函數,若有快取則優先使用,若超過指定次數則自動執行get使能取得快取。getFromCache於嘗試nTrigger次之後,若尚無快取且無任何執行中之get(含直接呼叫者)才觸發get,觸發之get失敗時不向外報錯而續等,超過nMax次(每次間隔1000ms)仍無快取時reject Error('exceeded nMax[nMax]')。get為直接執行fun並回傳其自身之結果(失敗則reject其錯誤),不與執行中者共用;寫入快取依啟動先後,先啟動而後完成者之結果不覆蓋後啟動者已寫入之快取;fun成功回傳之任何值(含null與undefined)皆視為已有快取,是否已有快取不以回傳值判定。fun非函數時get以TypeError被reject,getFromCache吞下其錯誤而持續輪詢至nMax後reject

Type
Object

(static) cacheSt(optopt) → {Object}

Description:
  • 非同步狀態旗標快取,含TTL與互斥/同步原語,可用於在單一process內協調非同步動作之執行順序與資源獨占

    Unit Test: Github

Source:
Example
let test1 = async () => {
    let ms = []

    let cs = cacheSt({ timeExpire: 10000 })

    //案例: mutex取得與釋放
    let b1 = cs.checkWithSet('myLock') //第一次取得lock, 回true
    ms.push({ '1st checkWithSet': b1 })
    let b2 = cs.checkWithSet('myLock') //第二次嘗試, 已被占用回false
    ms.push({ '2nd checkWithSet': b2 })
    await cs.del('myLock')             //釋放lock
    let b3 = cs.checkWithSet('myLock') //釋放後可再取
    ms.push({ 'after del checkWithSet': b3 })

    cs.clear() //結束時停止TTL偵測timer
    console.log('ms', ms)
    return ms
}
await test1()
// ms [
//   { '1st checkWithSet': true },
//   { '2nd checkWithSet': false },
//   { 'after del checkWithSet': true }
// ]

let test2 = async () => {
    let ms = []

    let cs = cacheSt({ timeExpire: 10000 })

    //案例: 多key原子占用並執行函數, 結束自動釋放
    let r = await cs.setWithFree(['lockA', 'lockB'], async () => {
        ms.push({ inside: 'fn running' })
        return 'done'
    })
    ms.push({ result: r })
    ms.push({ 'lockA after': await cs.check('lockA') }) //已自動釋放
    ms.push({ 'lockB after': await cs.check('lockB') }) //已自動釋放

    cs.clear()
    console.log('ms', ms)
    return ms
}
await test2()
// ms [
//   { inside: 'fn running' },
//   { result: 'done' },
//   { 'lockA after': false },
//   { 'lockB after': false }
// ]

let test3 = async () => {
    let ms = []

    let cs = cacheSt({ timeExpire: 10000 })

    //案例: 等待key出現 (條件變數模式)
    setTimeout(() => {
        cs.set('ready', 'data-x')
    }, 200)

    await cs.waitExist('ready', { attemptNum: 30, timeInterval: 100 }) //3秒內等候ready出現
    let v = await cs.get('ready')
    ms.push({ 'after waitExist': v })

    cs.clear()
    console.log('ms', ms)
    return ms
}
await test3()
// ms [
//   { 'after waitExist': 'data-x' }
// ]
Parameters:
Name Type Attributes Default Description
opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
timeExpire Integer <optional>
1800000

輸入TTL過期時間整數,單位為毫秒ms,key寫入超過此時間後將被內部偵測週期自動刪除,預設30分鐘(1800000ms)

timeDetect Integer <optional>
2000

輸入TTL偵測週期整數,單位為毫秒ms,預設2000

onTrace function <optional>
null

輸入觀察回調函數,於TTL偵測週期內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;僅涵蓋TTL偵測週期,不含set、get、check、checkWithSet、setWithFree、del之呼叫(其本身皆為同步完成,並已有同名事件可由on監聽),亦不含waitExist與waitNotExist之輪詢(其輪詢事件由waitFun之opt.onTrace提供,本函數不轉發,其開始與結束已有waitExist、waitNotExist事件可由on監聽);type為'expire'時為偵測到key已超過timeExpire而剛被刪除(含key、age為其寫入至本次偵測之毫秒數),'detect'時為每次偵測週期結束(含n為第幾次偵測、size為偵測前之key數、expired為本次刪除之key數);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響快取,clear後不再呼叫(含clear時正在進行之偵測週期之餘下部分),預設null

Returns:

回傳事件物件,內含上述對外方法可呼叫,亦提供on用於監聽各方法之事件。事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯,故其不會使行程崩潰,會改以error事件回報{ fun: 'listener', name, msg, args },無error監聽者則console.error;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection

Type
Object

(static) cbol(v) → {Boolean}

Description:
  • 布林值或字串轉布林值

    Unit Test: Github

Source:
Example
console.log(cbol(true))
// => true

console.log(cbol(false))
// => false

console.log(cbol(0))
// => false

console.log(cbol(1))
// => true

console.log(cbol('true'))
// => true

console.log(cbol('tRuE'))
// => true

console.log(cbol('TRUE'))
// => true

console.log(cbol('abc'))
// => false

console.log(cbol(''))
// => false

console.log(cbol([]))
// => false

console.log(cbol({}))
// => false

console.log(cbol(null))
// => false

console.log(cbol(undefined))
// => false
Parameters:
Name Type Description
v *

輸入任意值

Returns:

回傳布林值,恆回傳原始布林值(輸入new Boolean時回傳其原始值而非包裝物件)

Type
Boolean

(static) cdbl(v) → {Number}

Description:
  • 數字或字串轉浮點數 若輸入非數字則回傳0

    Unit Test: Github

Source:
Example
console.log(cdbl('25'))
// => 25
Parameters:
Name Type Description
v Number | String

輸入數字或字串

Returns:

回傳數字

Type
Number

(static) ceil(v, idigopt) → {Number}

Description:
  • 數字或字串無條件進位至指定位數(0位為整數)

    Unit Test: Github

Source:
Example
console.log(ceil(1.5))
// => 2

console.log(ceil('1.5'))
// => 2

console.log(ceil(-1.5))
// => -1

console.log(ceil('125abc'))
// => ''

console.log(ceil(9.83501, 2))
// => 9.84

console.log(ceil(9.8350, 2))
// => 9.84

console.log(ceil(9.82501, 2))
// => 9.83

console.log(ceil(9.8250, 2))
// => 9.83

console.log(ceil(9.8249, 2))
// => 9.83

console.log(ceil(12.6449, 2))
// => 12.65

console.log(ceil(12.645, 2))
// => 12.65

console.log(ceil(12.6451, 2))
// => 12.65

console.log(ceil(12.65, 2))
// => 12.65

console.log(ceil(12.64, 2))
// => 12.64

console.log(ceil(12.6, 2))
// => 12.6

console.log(ceil(12, 2))
// => 12
Parameters:
Name Type Attributes Default Description
v Number | String

輸入數字或字串

idig Integer <optional>
0

輸入指定位數整數,預設0

Returns:

回傳無條件進位的數字

Type
Number

(static) cfilesize(b) → {String}

Description:
  • 檔案大小(bytes)自動轉換單位 可調整檔案大小為b,kb,mb,gb,並添加單位 若輸入不是數字或字串時則回傳空字串

    Unit Test: Github

Source:
Example
console.log(cfilesize(0))
// => '0.0 b'

console.log(cfilesize(100))
// => '100.0 b'

console.log(cfilesize(2048))
// => '2.0 kb'

console.log(cfilesize(2000000))
// => '1.9 mb'

console.log(cfilesize(2000000000))
// => '1.9 gb'
Parameters:
Name Type Description
b Number | String

輸入單位需為bytes的檔案大小,可為數字或字串

Returns:

回傳自動轉換單位後的字串

Type
String

(static) cfinancial(v, idigopt) → {String}

Description:
  • 數字或字串轉金融格式(百分位添加逗號)表示 若輸入不是數字或字串時則回傳空字串

    Unit Test: Github

Source:
Example
console.log(cfinancial(0.123456789, 3))
// => '0.123'

console.log(cfinancial(1234567.89))
// => '1,234,568'

console.log(cfinancial(1234567.89, 1))
// => '1,234,567.9'
Parameters:
Name Type Attributes Default Description
v Number | String

輸入數字或字串

idig Integer <optional>
0

輸入指定小數位整數,預設為0

Returns:

回傳金融格式的字串

Type
String

(static) chkOrmData(data, optopt) → {null|Object}

Description:
  • w-orm系列寫入資料(鍵名與字串值,含巢狀物件與陣列元素)之良構檢核,回傳null(合格)或{ reason, path }

    輸入為各後端於複製(如cloneDeep)後、補主鍵與加欄位前之寫入資料(單一文件、文件陣列或任何值),本函數不修改輸入(但會呼叫可列舉存取器,見第7點),並假設後端其後寫入的就是被檢查之值;存取器屬性(自有或繼承之可列舉者)於檢核時呼叫其getter一次取值,有狀態之getter須由後端於複製後轉為資料屬性。

    1. 走訪範圍:一般物件與類別實例之自有可列舉字串鍵(鍵名與值)及繼承自Object.prototype以外原型之可列舉字串鍵(lodash merge、sequelize update、mdb取值皆經原型鏈,自有同名或較近原型同名者含不可列舉者遮蔽之)、陣列元素(依索引)、Map之項目(字串鍵、物件鍵之內容與值,逐項迭代)、Set之元素、String包裝物件之字串值與其額外屬性(字元索引鍵除外)、RegExp之source、Error之name/message/cause;Map、Set、RegExp、Error另走訪其自有與繼承之可列舉屬性;被後端自動轉成文字之函數(原始碼)與Symbol值(描述);字串經chkOrmStr檢核,預設只檢孤立代理(值之NUL不檢查,opt.nul為true時一併檢查NUL)。內建物件依內部槽判型(與標籤無關,標籤被改寫者亦然;原型為Object.prototype或null者直接當一般物件)。

    2. 略過:Date、TypedArray、ArrayBuffer、SharedArrayBuffer、DataView、Buffer(整個物件,連其上自有額外屬性)、數字、布林、null、undefined、BigInt、Symbol鍵、不可列舉鍵、陣列之洞與非索引屬性、Object.prototype上之屬性、Number/Boolean包裝與Promise/WeakMap/WeakSet(無可列舉內容);toJSON或自訂序列化之輸出不檢查。

    3. 走訪順序固定為文件順序(深度優先):同一物件內鍵名先於其值(自有鍵名、由近至遠之繼承鍵名、自有值、繼承值),各值依自有鍵序(陣列依索引、整數樣鍵依引擎規定先於其他鍵);回傳首個不合格處。

    4. 迭代走訪(顯式堆疊,不遞迴),深度、節點數與字串長度皆不設上限;循環參照與共享參照安全,同一物件出現多處只走訪一次(路徑取首次出現處);稀疏陣列依實際元素走訪,成本不隨length;Map、Set逐項迭代不預先具現;同一原型(如同一類別之多個實例)之鍵只掃描一次。

    5. 回傳{ reason, path }:reason為chkOrmStr之固定原因詞彙(不回顯值);path以root起始,物件鍵為.鍵名(限30字元並以?取代控制字元、格式字元、方括號)、陣列元素與Set元素為[索引]、Map項目為序號,不合格之物件鍵名不出現在path,以.標示;Error與RegExp之文字為.message、.name、.cause、.source;路徑超過120字元截為...加末100碼元(切點避開代理對中間);opt.root含控制字元、孤立代理、NUL或過長者改用預設。

    6. 不吞例外:讀取過程中Proxy陷阱、getter等拋出之例外原樣拋出;各後端於複製後呼叫則一般資料不會遇到。

    7. 會呼叫可列舉存取器(含繼承者),後端應於獨立複本上走訪:存取器之getter以被走訪者為this執行,其副作用可能改動被走訪之同一份複本——例如繼承之存取器把已檢查之欄位改成不合格字串,或把主鍵改指向既有他列——使其後寫入或刪除的不是被檢查之值(cloneDeep保留原型而不讀繼承存取器,故複製後存取器仍在)。後端須另做一份獨立複本(如再cloneDeep一次)供本函數走訪,寫入用複本不被走訪;交給事件訂閱者之物件亦須為獨立快照(訂閱者的改動不得影響其後各筆),訂閱函數拋錯不得影響操作。此作法擋得住存取器對寫入複本的直接改動,但擋不住存取器『修好』走訪用複本中的不合格值(寫入用複本仍帶原值而通過檢核);要連此一併杜絕,須在檢核前把存取器固定為資料(每個可列舉屬性含繼承者只讀一次,產生不含存取器之資料),檢核並寫入同一份固定後之資料。契約案例表增補案例next之toctou組(inh-getter-、event-shared、listener-throws)驗證獨立複本與事件快照;『修好』之情形尚無案例,列為已知限制。

    後端依回傳組成訊息:invalid data: reason at [path];w-orm系列契約案例表見wsemi/src/_cvOrmStrCases.mjs。

    Unit Test: Github

Source:
Example
console.log(chkOrmData({ id: 'a', name: '中文\u{1F600}', tags: ['x', 'y'], meta: { k: 'v' } }))
// => null

console.log(chkOrmData({ id: 'a', meta: { tags: ['ok', 'b\uD800'] } }))
// => { reason: 'string contains lone surrogate', path: 'data.meta.tags[1]' }

console.log(chkOrmData({ id: 'a', ['k\uD800']: 1 }, { root: 'data[2]' }))
// => { reason: 'string contains lone surrogate', path: 'data[2].<invalid key>' }

console.log(chkOrmData({ name: 'a\u0000b' }))
// => null

console.log(chkOrmData({ name: 'a\u0000b' }, { nul: true }))
// => { reason: 'string contains NUL', path: 'data.name' }
Parameters:
Name Type Attributes Default Description
data *

輸入已複製之寫入資料,可為物件、陣列或任意值

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
nul Boolean <optional>
false

輸入是否一併檢查鍵名與字串值之NUL布林值,僅嚴格等於true時檢查,預設false

root String <optional>
'data'

輸入path之起始標籤字串,如data[1],預設'data'

Returns:

回傳null(合格),或{ reason, path }

Type
null | Object

(static) chkOrmStr(s, optopt) → {String|null}

Description:
  • w-orm系列字串之良構檢核,回傳null(合格)或固定原因字串

    檢查順序:非字串→NUL→孤立代理,回傳首個命中之原因。

    1. 非字串(含String包裝物件、undefined、null、數字、物件等任何非原始字串值)回傳'not a string',不呼叫該值之任何方法。

    2. NUL:字串含U+0000回傳'string contains NUL';opt.nul嚴格等於false時不檢查(僅自有資料屬性生效,繼承值、存取器與其他假值皆維持檢查)。

    3. 孤立代理:含孤立之代理碼元(高代理之後無低代理,或低代理之前無高代理,即String.prototype.isWellFormed()為false)回傳'string contains lone surrogate';U+FFFD本身與成對代理(emoji、CJK擴充B區以後之罕用字)為良構,回傳null。

    4. 長度不另限;對字串輸入不拋錯;不修改輸入;純JS(不import任何模組、不用Node API),無String.prototype.isWellFormed之環境走等價之正則退路;檢核所用之內建於模組載入時取得,載入後被覆寫或原型被汙染不影響結果。

    回傳之原因字串為固定詞彙,不回顯輸入值,供呼叫端組成'invalid id: 原因 at [路徑]'等訊息;w-orm系列契約案例表見wsemi/src/_cvOrmStrCases.mjs。

    Unit Test: Github

Source:
Example
console.log(chkOrmStr('abc'))
// => null

console.log(chkOrmStr('中文\u{1F600}'))
// => null

console.log(chkOrmStr('a\uD800'))
// => string contains lone surrogate

console.log(chkOrmStr('a\u0000b'))
// => string contains NUL

console.log(chkOrmStr('a\u0000b', { nul: false }))
// => null

console.log(chkOrmStr(123))
// => not a string
Parameters:
Name Type Attributes Default Description
s *

輸入任意值,通常為字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
nul Boolean <optional>
true

輸入是否檢查NUL布林值,僅嚴格等於false時不檢查,預設true

Returns:

回傳null(合格),或原因字串'not a string'、'string contains NUL'、'string contains lone surrogate'

Type
String | null

(static) cint(v, optopt) → {Integer}

Description:
  • 數字或字串四捨五入轉整數 若輸入非數字則回傳0

    Unit Test: Github

Source:
Example
console.log(cint('1.5'))
// => 2

console.log(cint('-1.5'))
// => -1

console.log(cint(Infinity))
// => 1.7976931348623157e+308

console.log(cint(Infinity, { useClampSafe: true }))
// => 9007199254740991

console.log(cint(-Infinity, { useClampSafe: true }))
// => -9007199254740991

try {
    cint(Infinity, { useLimitSafe: true })
}
catch (err) {
    console.log(err.message)
    // => v[1.7976931348623157e+308] is not a safe integer
}

console.log(cint(Infinity, { useLimitSafe: true, returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'v[1.7976931348623157e+308] is not a safe integer' }

console.log(cint('1.5', { returnWithStateAndMsg: true }))
// => { state: 'success', msg: 2 }
Parameters:
Name Type Attributes Default Description
v Number | String

輸入數字或字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useClampSafe Boolean <optional>
false

輸入是否鉗制至安全整數範圍布林值,若為true則四捨五入後之整數超出安全整數範圍時取邊界值,大於Number.MAX_SAFE_INTEGER者取Number.MAX_SAFE_INTEGER、小於Number.MIN_SAFE_INTEGER者取Number.MIN_SAFE_INTEGER,預設false

useLimitSafe Boolean <optional>
false

輸入是否限制須為安全整數布林值,若為true則四捨五入後之整數超出安全整數範圍(即Number.isSafeInteger為false,含Infinity、-Infinity與絕對值大於Number.MAX_SAFE_INTEGER者)時視為錯誤,預設false。與opt.useClampSafe同時為true時因已先鉗制至安全整數範圍故不會觸發

returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false。預設false時錯誤仍以拋出錯誤表達,行為不變

Returns:

回傳四捨五入後整數;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Integer

(static) clearXSS(inp) → {Object|Array|String}

Description:
  • 清除xss攻擊語法

    Depend on: https://jsxss.com/zh/index.html

    白名單外之標籤轉義,白名單內之標籤刪除白名單外之屬性與危險網址(style屬性保留),標籤外之文字只轉義會開始標籤之<(其後為英文字母、斜線、驚嘆號、問號或位於結尾),>與其餘之<維持原樣,故'a < b'等數學式不改寫

    輸出僅供以HTML內文呈現(如v-html、innerHTML),不可放入屬性值或註解內

    走訪採顯式堆疊並以來源物件之參照記憶化,不遞迴:深巢狀與循環參照不拋錯(循環保持),共用子結構只處理一次且輸出中保持共用,成本與節點數成正比,不設深度上限(僅受堆積限制)。處置如下:

    1. 字串清除(含包裝String,清除後為原始字串);空字串與number、boolean、null、undefined等非字串原值回傳

    2. 物件(含類別實例)與真陣列(含子類別)走訪並保留原型(null原型物件變一般物件),length視為一般鍵(不使走訪改為類陣列,不新增鍵),成本與length值無關;物件依自有可列舉之字串鍵與Symbol鍵,陣列依索引(空洞補undefined,非索引屬性不複製,正規表示式比對結果之index與input除外,其input亦清除);Map走訪值、Set走訪元素(Map之鍵不處理,清除後相同之Set元素合併),Map與Set之自有可列舉屬性一併走訪;不可列舉屬性不複製,原型上之資料不處理

    3. 物件之字串鍵與Map之鍵不清除,由使用端自行檢核

    4. arguments物件與以Symbol.toStringTag偽稱Array或Arguments之物件,視為物件並依自有可列舉鍵走訪,轉為一般物件

    5. Date、RegExp、型別陣列、Buffer、ArrayBuffer、包裝Number等回傳複本,其自有字串屬性不清除(物件值屬性依上述走訪);函數、Error、WeakMap、Promise、URL等不可複製者,巢狀時回傳同一參照而不清除其內字串,位於最外層時函數複製為一般物件(清除其可列舉屬性)、Error等回傳{};其餘經lodash cloneDeep複製後仍帶自訂Symbol.toStringTag之物件亦不清除其內字串

    6. 經lodash cloneDeep複製之物件(上述第4、5點以外之非一般物件,如類別實例、null原型物件、陣列子類別、自訂標籤者)沿用其複製行為(含原型上之setter),再依複本之終態逐鍵清除;除此之外輸入不被修改

    7. 清除對字串不保證冪等(如<b='<<>清一次與清兩次結果不同),已清除之值再送入可能再被改寫

    Unit Test: Github

Source:
Example
console.log(clearXSS(`><script>alert('XSS')</script>`))
// => `>&lt;script>alert('XSS')&lt;/script>`

console.log(clearXSS(`<img src="javascript:alert('XSS')">`))
// => `<img src>`

console.log(clearXSS(`孔深 < 500 m 且 > 10 m`))
// => `孔深 < 500 m 且 > 10 m`

console.log(clearXSS({ a: `<script>alert('XSS')</script>`, b: [1, `<b>x</b>`, { c: `<img src=x onerror=alert(1)>` }], d: new Map([['k', `<script>1</script>`]]) }))
// => { a: "&lt;script>alert('XSS')&lt;/script>", b: [ 1, '<b>x</b>', { c: '<img src>' } ], d: Map(1) { 'k' => '&lt;script>1&lt;/script>' } }
Parameters:
Name Type Description
inp String | Object | Array

輸入資料,可為字串,或含字串之物件、陣列、Map、Set

Returns:

輸出資料,字串輸入回傳清除後字串,其餘回傳清除後之新結構(原始值原樣回傳)

Type
Object | Array | String

(async, static) clearXSSDyn(inp, optopt, pathItems) → {Promise}

Description:
Source:
Example
let r

r = await clearXSSDyn(`><script>alert('XSS')</script>`)
console.log(r)
// => `>&lt;script>alert('XSS')&lt;/script>`

r = await clearXSSDyn(`<img src="javascript:alert('XSS')">`)
console.log(r)
// => `<img src>`
Parameters:
Name Type Attributes Default Description
inp String | Object | Array

輸入資料

opt Object <optional>
{}

輸入設定物件,保留簽名相容,目前不使用

pathItems String | Object | Array

輸入資源字串、字串陣列、物件、物件陣列

Returns:

回傳Promise,resolve回傳輸出資料,reject回傳錯誤訊息

Type
Promise

(static) color() → {Object}

Description:
  • 顏色轉換

    Unit Test: Github

Source:
Example
import oc from 'wsemi/src/color.mjs'

let c
let c1
let c2
let r

c = '#cd'
r = oc.toRgbaString(c)
console.log(r)
// => rgba(205, 205, 205, 1)

c = '#6a3'
r = oc.toRgbaString(c)
console.log(r)
// => rgba(102, 170, 51, 1)

c = '#6b8e23'
r = oc.toRgbaString(c)
console.log(r)
// => rgba(107, 142, 35, 1)

c1 = '#cd'
c2 = 'hsl (320, 50%, 40%)'
r = oc.mix(c1, 0.5, c2, 0.5, 'toRgbString')
console.log(r)
// => rgb(179, 128, 162)

c1 = '#cd'
c2 = 'hsl (320, 50%, 40%)' //rgb(153, 51, 119)
r = oc.mix(c1, 0, c2, 1, 'toRgbString')
console.log(r)
// => rgb(153, 51, 119)

c1 = 'rgb(250, 120, 50)'
c2 = 'hsva (320, 100%, 50%, 0.1)'
r = oc.mix(c1, 0.5, c2, 0.5, 'toRgba')
console.log(r)
// => { r: 189, g: 60, b: 68, a: 0.55 }

c = 'rgb(250, 120, 50)'
r = oc.toRgbaString(c)
console.log(r)
// => rgba(250, 120, 50, 1)

c = 'rgb(250, 120, 50)'
r = oc.toHslaString(c)
console.log(r)
// => hsla(21, 0.952, 0.588, 1)

c = 'rgb(250, 120, 50)'
r = oc.toHsvaString(c)
console.log(r)
// => hsva(21, 0.8, 0.98, 1)

c = 'rgb(250, 120, 50)'
r = oc.toHexString(c)
console.log(r)
// => #fa7832

c = 'skyblue'
r = oc.toRgbaString(c)
console.log(r)
// => rgba(135, 206, 235, 1)

c = 'hsl (320, 50%, 40%)'
r = oc.toRgba(c)
console.log(r)
// => { r: 153, g: 51, b: 119, a: 1 }

c = 'hsva (320, 100%, 50%, 0.1)'
r = oc.toRgba(c)
console.log(r)
// => { r: 128, g: 0, b: 85, a: 0.1 }

c = { r: 255, g: 150, b: 50 }
r = oc.toHsla(c)
console.log(r)
// => { h: 29, s: 0.672, l: 0.002, a: 1 }

c = { r: '50%', g: 150, b: 50, a: 0.1 }
r = oc.toHsva(c)
console.log(r)
// => { h: 74, s: 0.667, v: 0.002, a: 0.1 }

c = { h: 320, s: 0.2, l: 0.15 }
r = oc.toRgba(c)
console.log(r)
// => { r: 46, g: 31, b: 31, a: 1 }

c = { h: 320, s: '12%', l: '8%', a: 0.1 }
r = oc.toRgba(c)
console.log(r)
// => { r: 23, g: 18, b: 18, a: 0.1 }

c = { h: 320, s: 0.2, v: 0.15 }
r = oc.toRgba(c)
console.log(r)
// => { r: 38, g: 31, b: 31, a: 1 }

c = { h: '50%', s: 0.2, l: 0.15 }
r = oc.toHexString(c)
console.log(r)
// => #1f2e2e

c = { h: '50%', s: 0.2, l: 0.15 }
r = oc.toRgbaString(c)
console.log(r)
// => rgba(31, 46, 46, 1)

c = { h: '50%', s: 0.2, l: 0.15 }
r = oc.toRgba(c)
console.log(r)
// => { r: 31, g: 46, b: 46, a: 1 }

c = { h: 180, s: 0.2, l: 0.15 }
r = oc.toHexString(c)
console.log(r)
// => #1f2e2e

c = { h: '50%', s: '12%', v: '8%', a: 0.1 }
r = oc.toRgba(c)
console.log(r)
// => { r: 20, g: 18, b: 18, a: 0.1 }

let gradient = {
    0: 'rgb(255, 255, 255)',
    0.2: 'rgb(254, 178, 76)',
    0.4: 'rgb(252, 78, 42)',
    0.6: 'rgb(220, 58, 38)',
    0.8: 'rgb(200, 40, 23)',
    1: 'rgba(180, 30, 60, 0.5)',
}
let fun = oc.interp(gradient)
console.log('fun(0)', fun(0))
//fun(0) rgba(255, 255, 255, 1)

console.log('fun(0.1)', fun(0.1))
//fun(0.1) rgba(255, 217, 166, 1)

console.log('fun(0.2)', fun(0.2))
//fun(0.2) rgba(254, 178, 76, 1)

console.log('fun(0.8)', fun(0.8))
//fun(0.8) rgba(200, 40, 23, 1)

console.log('fun(0.9)', fun(0.9))
//fun(0.9) rgba(190, 35, 42, 0.75)

console.log('fun(1)', fun(1))
//fun(1) rgba(180, 30, 60, 0.5)

console.log('fun(2)', fun(2))
//fun(2) null

console.log('fun(-1)', fun(-1))
//fun(-1) null
Returns:

回傳color物件,提供toRgbaString、toHslaString、toHsvaString、toHexString等共37種處理函數

Type
Object

(static) composeToTree(items, optopt, bindKeyopt, bindParentopt, bindChildrenopt, saveExtPropsopt) → {Array}

Description:
  • 組合關聯陣列成為樹狀陣列

    Unit Test: Github

Source:
Example
let data = [
    {
        id: 1,
        text: '1-a',
    },
    {
        id: 2,
        text: '2-b',
    },
    {
        id: 3,
        text: '3-c',
        parentId: 2,
    },
    {
        id: 4,
        text: '4-d',
        parentId: 2,
    },
    {
        id: 5,
        text: '5-e',
        parentId: 3,
    },
    {
        id: 6,
        text: 'empty',
    },
]
let r = composeToTree(data)
let cr = JSON.stringify(r)
console.log(cr)
// => [
//   {
//     "id": 1,
//     "text": "1-a"
//   },
//   {
//     "id": 2,
//     "text": "2-b",
//     "children": [
//       {
//         "id": 3,
//         "text": "3-c",
//         "parentId": 2,
//         "children": [
//           {
//             "id": 5,
//             "text": "5-e",
//             "parentId": 3
//           }
//         ]
//       },
//       {
//         "id": 4,
//         "text": "4-d",
//         "parentId": 2
//       }
//     ]
//   },
//   {
//     "id": 6,
//     "text": "empty"
//   }
// ]
Parameters:
Name Type Attributes Default Description
items Array

輸入關聯陣列

opt Object <optional>
{}

輸入設定物件,預設{}

bindKey String <optional>
'id'

輸入項目物件識別用欄位字串,預設'id'

bindParent String <optional>
'parentId'

輸入項目物件內存放父節點欄位字串,預設'parentId'

bindChildren String <optional>
'children'

輸入產生樹狀物件時,各節點內存放子節點欄位字串,預設'children'

saveExtProps Boolean <optional>
false

輸入是否儲存項目物件從屬資訊布林值,預設false

Returns:

回傳樹狀陣列

Type
Array

(static) convertToTree(data, optopt, bindKeyopt, bindParentopt, bindTextopt, bindChildrenopt, bindTypeopt, bindNumOfChilrenopt, bindRootopt, saveExtPropsopt) → {Array}

Description:
  • 展開物件或陣列成為樹狀陣列

    Unit Test: Github

Source:
Example
let r

let obj = {
    a: 1,
    b: 12.3,
    c: 'abc',
    d: '45-de',
    x: true,
    y: null,
    z: function() {},
    e: [],
    f: [
        91,
        912.3,
        'abc',
        '945-de',
        true,
        null,
        function() {},
        [
            5,
            54.3,
            'xyz',
        ]
    ],
    g: {},
    h: {
        ga: 81,
        gb: 812.3,
        gc: 'abc',
        gd: '845-de',
        ge: [
            71,
            712.3,
            'abc',
            '745-de',
            true,
            null,
            function() {},
        ],
        gf: {
            gfa: 61,
            gfb: 612.3,
            gfc: 'abc',
            gfd: '645-de',
            gfe: true,
            gff: null,
            gfg: function() {},
        },
        gx: true,
        gy: null,
        gz: function() {},
    },
    i: Symbol('foo'),
    [Symbol('i-sym-key-a')]: 'i-sym-value',
    [Symbol('i-sym-key-b')]: {
        symfa: 61,
        symfb: 612.3,
        symfc: 'abc',
        symfd: '645-de',
        symfe: true,
        symff: null,
        symfg: function() {},
    },
}
r = convertToTree(obj)
console.log('convertToTree obj', JSON.stringify(r, null, 2))
// => convertToTree obj [
//   {
//     "id": "root",
//     "parentId": "",
//     "type": "object",
//     "key": "root",
//     "numOfChilren": 12,
//     "children": [
//       {
//         "id": "root-a",
//         "parentId": "root",
//         "type": "node",
//         "key": "a",
//         "text": 1
//       },
//       {
//         "id": "root-b",
//         "parentId": "root",
//         "type": "node",
//         "key": "b",
//         "text": 12.3
//       },
//       {
//         "id": "root-c",
//         "parentId": "root",
//         "type": "node",
//         "key": "c",
//         "text": "abc"
//       },
//       {
//         "id": "root-d",
//         "parentId": "root",
//         "type": "node",
//         "key": "d",
//         "text": "45-de"
//       },
//       {
//         "id": "root-x",
//         "parentId": "root",
//         "type": "node",
//         "key": "x",
//         "text": true
//       },
//       {
//         "id": "root-y",
//         "parentId": "root",
//         "type": "node",
//         "key": "y",
//         "text": null
//       },
//       {
//         "id": "root-z",
//         "parentId": "root",
//         "type": "node",
//         "key": "z"
//       },
//       {
//         "id": "root-e",
//         "parentId": "root",
//         "type": "array",
//         "key": "e",
//         "numOfChilren": 0
//       },
//       {
//         "id": "root-f",
//         "parentId": "root",
//         "type": "array",
//         "key": "f",
//         "numOfChilren": 8,
//         "children": [
//           {
//             "id": "root-f-0",
//             "parentId": "root-f",
//             "type": "node",
//             "key": 0,
//             "text": 91
//           },
//           {
//             "id": "root-f-1",
//             "parentId": "root-f",
//             "type": "node",
//             "key": 1,
//             "text": 912.3
//           },
//           {
//             "id": "root-f-2",
//             "parentId": "root-f",
//             "type": "node",
//             "key": 2,
//             "text": "abc"
//           },
//           {
//             "id": "root-f-3",
//             "parentId": "root-f",
//             "type": "node",
//             "key": 3,
//             "text": "945-de"
//           },
//           {
//             "id": "root-f-4",
//             "parentId": "root-f",
//             "type": "node",
//             "key": 4,
//             "text": true
//           },
//           {
//             "id": "root-f-5",
//             "parentId": "root-f",
//             "type": "node",
//             "key": 5,
//             "text": null
//           },
//           {
//             "id": "root-f-6",
//             "parentId": "root-f",
//             "type": "node",
//             "key": 6
//           },
//           {
//             "id": "root-f-7",
//             "parentId": "root-f",
//             "type": "array",
//             "key": 7,
//             "numOfChilren": 3,
//             "children": [
//               {
//                 "id": "root-f-7-0",
//                 "parentId": "root-f-7",
//                 "type": "node",
//                 "key": 0,
//                 "text": 5
//               },
//               {
//                 "id": "root-f-7-1",
//                 "parentId": "root-f-7",
//                 "type": "node",
//                 "key": 1,
//                 "text": 54.3
//               },
//               {
//                 "id": "root-f-7-2",
//                 "parentId": "root-f-7",
//                 "type": "node",
//                 "key": 2,
//                 "text": "xyz"
//               }
//             ]
//           }
//         ]
//       },
//       {
//         "id": "root-g",
//         "parentId": "root",
//         "type": "object",
//         "key": "g",
//         "numOfChilren": 0
//       },
//       {
//         "id": "root-h",
//         "parentId": "root",
//         "type": "object",
//         "key": "h",
//         "numOfChilren": 9,
//         "children": [
//           {
//             "id": "root-h-ga",
//             "parentId": "root-h",
//             "type": "node",
//             "key": "ga",
//             "text": 81
//           },
//           {
//             "id": "root-h-gb",
//             "parentId": "root-h",
//             "type": "node",
//             "key": "gb",
//             "text": 812.3
//           },
//           {
//             "id": "root-h-gc",
//             "parentId": "root-h",
//             "type": "node",
//             "key": "gc",
//             "text": "abc"
//           },
//           {
//             "id": "root-h-gd",
//             "parentId": "root-h",
//             "type": "node",
//             "key": "gd",
//             "text": "845-de"
//           },
//           {
//             "id": "root-h-ge",
//             "parentId": "root-h",
//             "type": "array",
//             "key": "ge",
//             "numOfChilren": 7,
//             "children": [
//               {
//                 "id": "root-h-ge-0",
//                 "parentId": "root-h-ge",
//                 "type": "node",
//                 "key": 0,
//                 "text": 71
//               },
//               {
//                 "id": "root-h-ge-1",
//                 "parentId": "root-h-ge",
//                 "type": "node",
//                 "key": 1,
//                 "text": 712.3
//               },
//               {
//                 "id": "root-h-ge-2",
//                 "parentId": "root-h-ge",
//                 "type": "node",
//                 "key": 2,
//                 "text": "abc"
//               },
//               {
//                 "id": "root-h-ge-3",
//                 "parentId": "root-h-ge",
//                 "type": "node",
//                 "key": 3,
//                 "text": "745-de"
//               },
//               {
//                 "id": "root-h-ge-4",
//                 "parentId": "root-h-ge",
//                 "type": "node",
//                 "key": 4,
//                 "text": true
//               },
//               {
//                 "id": "root-h-ge-5",
//                 "parentId": "root-h-ge",
//                 "type": "node",
//                 "key": 5,
//                 "text": null
//               },
//               {
//                 "id": "root-h-ge-6",
//                 "parentId": "root-h-ge",
//                 "type": "node",
//                 "key": 6
//               }
//             ]
//           },
//           {
//             "id": "root-h-gf",
//             "parentId": "root-h",
//             "type": "object",
//             "key": "gf",
//             "numOfChilren": 7,
//             "children": [
//               {
//                 "id": "root-h-gf-gfa",
//                 "parentId": "root-h-gf",
//                 "type": "node",
//                 "key": "gfa",
//                 "text": 61
//               },
//               {
//                 "id": "root-h-gf-gfb",
//                 "parentId": "root-h-gf",
//                 "type": "node",
//                 "key": "gfb",
//                 "text": 612.3
//               },
//               {
//                 "id": "root-h-gf-gfc",
//                 "parentId": "root-h-gf",
//                 "type": "node",
//                 "key": "gfc",
//                 "text": "abc"
//               },
//               {
//                 "id": "root-h-gf-gfd",
//                 "parentId": "root-h-gf",
//                 "type": "node",
//                 "key": "gfd",
//                 "text": "645-de"
//               },
//               {
//                 "id": "root-h-gf-gfe",
//                 "parentId": "root-h-gf",
//                 "type": "node",
//                 "key": "gfe",
//                 "text": true
//               },
//               {
//                 "id": "root-h-gf-gff",
//                 "parentId": "root-h-gf",
//                 "type": "node",
//                 "key": "gff",
//                 "text": null
//               },
//               {
//                 "id": "root-h-gf-gfg",
//                 "parentId": "root-h-gf",
//                 "type": "node",
//                 "key": "gfg"
//               }
//             ]
//           },
//           {
//             "id": "root-h-gx",
//             "parentId": "root-h",
//             "type": "node",
//             "key": "gx",
//             "text": true
//           },
//           {
//             "id": "root-h-gy",
//             "parentId": "root-h",
//             "type": "node",
//             "key": "gy",
//             "text": null
//           },
//           {
//             "id": "root-h-gz",
//             "parentId": "root-h",
//             "type": "node",
//             "key": "gz"
//           }
//         ]
//       },
//       {
//         "id": "root-i",
//         "parentId": "root",
//         "type": "node",
//         "key": "i"
//       },
//       {
//         "id": "root-Symbol(i-sym-key-a)",
//         "parentId": "root",
//         "type": "node",
//         "text": "i-sym-value"
//       },
//       {
//         "id": "root-Symbol(i-sym-key-b)",
//         "parentId": "root",
//         "type": "object",
//         "numOfChilren": 7,
//         "children": [
//           {
//             "id": "root-Symbol(i-sym-key-b)-symfa",
//             "parentId": "root-Symbol(i-sym-key-b)",
//             "type": "node",
//             "key": "symfa",
//             "text": 61
//           },
//           {
//             "id": "root-Symbol(i-sym-key-b)-symfb",
//             "parentId": "root-Symbol(i-sym-key-b)",
//             "type": "node",
//             "key": "symfb",
//             "text": 612.3
//           },
//           {
//             "id": "root-Symbol(i-sym-key-b)-symfc",
//             "parentId": "root-Symbol(i-sym-key-b)",
//             "type": "node",
//             "key": "symfc",
//             "text": "abc"
//           },
//           {
//             "id": "root-Symbol(i-sym-key-b)-symfd",
//             "parentId": "root-Symbol(i-sym-key-b)",
//             "type": "node",
//             "key": "symfd",
//             "text": "645-de"
//           },
//           {
//             "id": "root-Symbol(i-sym-key-b)-symfe",
//             "parentId": "root-Symbol(i-sym-key-b)",
//             "type": "node",
//             "key": "symfe",
//             "text": true
//           },
//           {
//             "id": "root-Symbol(i-sym-key-b)-symff",
//             "parentId": "root-Symbol(i-sym-key-b)",
//             "type": "node",
//             "key": "symff",
//             "text": null
//           },
//           {
//             "id": "root-Symbol(i-sym-key-b)-symfg",
//             "parentId": "root-Symbol(i-sym-key-b)",
//             "type": "node",
//             "key": "symfg"
//           }
//         ]
//       }
//     ]
//   }
// ]

let arr = [
    91,
    912.3,
    'abc',
    '945-de',
    true,
    null,
    function() {},
    [
        5,
        54.3,
        'xyz',
        {
            gf: {
                gfa: 61,
                gfb: 612.3,
                gfc: 'abc',
                gfd: '645-de',
                gfe: true,
                gff: null,
                gfg: function() {},
            },
        },
    ],
    {
        h: {
            ga: 81,
            gb: 812.3,
            gc: 'abc',
            gd: '845-de',
            ge: [
                71,
                712.3,
                'abc',
                '745-de',
                true,
                null,
                function() {},
            ],
            gx: true,
            gy: null,
            gz: function() {},
        },
        i: Symbol('foo'),
        [Symbol('i-sym-key-a')]: 'i-sym-value',
        [Symbol('i-sym-key-b')]: {
            symfa: 61,
            symfb: 612.3,
            symfc: 'abc',
            symfd: '645-de',
            symfe: true,
            symff: null,
            symfg: function() {},
        },
    },
]
r = convertToTree(arr)
console.log('convertToTree arr', JSON.stringify(r, null, 2))
// => convertToTree arr [
//   {
//     "id": "root",
//     "parentId": "",
//     "type": "array",
//     "key": "root",
//     "numOfChilren": 9,
//     "children": [
//       {
//         "id": "root-0",
//         "parentId": "root",
//         "type": "node",
//         "key": 0,
//         "text": 91
//       },
//       {
//         "id": "root-1",
//         "parentId": "root",
//         "type": "node",
//         "key": 1,
//         "text": 912.3
//       },
//       {
//         "id": "root-2",
//         "parentId": "root",
//         "type": "node",
//         "key": 2,
//         "text": "abc"
//       },
//       {
//         "id": "root-3",
//         "parentId": "root",
//         "type": "node",
//         "key": 3,
//         "text": "945-de"
//       },
//       {
//         "id": "root-4",
//         "parentId": "root",
//         "type": "node",
//         "key": 4,
//         "text": true
//       },
//       {
//         "id": "root-5",
//         "parentId": "root",
//         "type": "node",
//         "key": 5,
//         "text": null
//       },
//       {
//         "id": "root-6",
//         "parentId": "root",
//         "type": "node",
//         "key": 6
//       },
//       {
//         "id": "root-7",
//         "parentId": "root",
//         "type": "array",
//         "key": 7,
//         "numOfChilren": 4,
//         "children": [
//           {
//             "id": "root-7-0",
//             "parentId": "root-7",
//             "type": "node",
//             "key": 0,
//             "text": 5
//           },
//           {
//             "id": "root-7-1",
//             "parentId": "root-7",
//             "type": "node",
//             "key": 1,
//             "text": 54.3
//           },
//           {
//             "id": "root-7-2",
//             "parentId": "root-7",
//             "type": "node",
//             "key": 2,
//             "text": "xyz"
//           },
//           {
//             "id": "root-7-3",
//             "parentId": "root-7",
//             "type": "object",
//             "key": 3,
//             "numOfChilren": 1,
//             "children": [
//               {
//                 "id": "root-7-3-gf",
//                 "parentId": "root-7-3",
//                 "type": "object",
//                 "key": "gf",
//                 "numOfChilren": 7,
//                 "children": [
//                   {
//                     "id": "root-7-3-gf-gfa",
//                     "parentId": "root-7-3-gf",
//                     "type": "node",
//                     "key": "gfa",
//                     "text": 61
//                   },
//                   {
//                     "id": "root-7-3-gf-gfb",
//                     "parentId": "root-7-3-gf",
//                     "type": "node",
//                     "key": "gfb",
//                     "text": 612.3
//                   },
//                   {
//                     "id": "root-7-3-gf-gfc",
//                     "parentId": "root-7-3-gf",
//                     "type": "node",
//                     "key": "gfc",
//                     "text": "abc"
//                   },
//                   {
//                     "id": "root-7-3-gf-gfd",
//                     "parentId": "root-7-3-gf",
//                     "type": "node",
//                     "key": "gfd",
//                     "text": "645-de"
//                   },
//                   {
//                     "id": "root-7-3-gf-gfe",
//                     "parentId": "root-7-3-gf",
//                     "type": "node",
//                     "key": "gfe",
//                     "text": true
//                   },
//                   {
//                     "id": "root-7-3-gf-gff",
//                     "parentId": "root-7-3-gf",
//                     "type": "node",
//                     "key": "gff",
//                     "text": null
//                   },
//                   {
//                     "id": "root-7-3-gf-gfg",
//                     "parentId": "root-7-3-gf",
//                     "type": "node",
//                     "key": "gfg"
//                   }
//                 ]
//               }
//             ]
//           }
//         ]
//       },
//       {
//         "id": "root-8",
//         "parentId": "root",
//         "type": "object",
//         "key": 8,
//         "numOfChilren": 2,
//         "children": [
//           {
//             "id": "root-8-h",
//             "parentId": "root-8",
//             "type": "object",
//             "key": "h",
//             "numOfChilren": 8,
//             "children": [
//               {
//                 "id": "root-8-h-ga",
//                 "parentId": "root-8-h",
//                 "type": "node",
//                 "key": "ga",
//                 "text": 81
//               },
//               {
//                 "id": "root-8-h-gb",
//                 "parentId": "root-8-h",
//                 "type": "node",
//                 "key": "gb",
//                 "text": 812.3
//               },
//               {
//                 "id": "root-8-h-gc",
//                 "parentId": "root-8-h",
//                 "type": "node",
//                 "key": "gc",
//                 "text": "abc"
//               },
//               {
//                 "id": "root-8-h-gd",
//                 "parentId": "root-8-h",
//                 "type": "node",
//                 "key": "gd",
//                 "text": "845-de"
//               },
//               {
//                 "id": "root-8-h-ge",
//                 "parentId": "root-8-h",
//                 "type": "array",
//                 "key": "ge",
//                 "numOfChilren": 7,
//                 "children": [
//                   {
//                     "id": "root-8-h-ge-0",
//                     "parentId": "root-8-h-ge",
//                     "type": "node",
//                     "key": 0,
//                     "text": 71
//                   },
//                   {
//                     "id": "root-8-h-ge-1",
//                     "parentId": "root-8-h-ge",
//                     "type": "node",
//                     "key": 1,
//                     "text": 712.3
//                   },
//                   {
//                     "id": "root-8-h-ge-2",
//                     "parentId": "root-8-h-ge",
//                     "type": "node",
//                     "key": 2,
//                     "text": "abc"
//                   },
//                   {
//                     "id": "root-8-h-ge-3",
//                     "parentId": "root-8-h-ge",
//                     "type": "node",
//                     "key": 3,
//                     "text": "745-de"
//                   },
//                   {
//                     "id": "root-8-h-ge-4",
//                     "parentId": "root-8-h-ge",
//                     "type": "node",
//                     "key": 4,
//                     "text": true
//                   },
//                   {
//                     "id": "root-8-h-ge-5",
//                     "parentId": "root-8-h-ge",
//                     "type": "node",
//                     "key": 5,
//                     "text": null
//                   },
//                   {
//                     "id": "root-8-h-ge-6",
//                     "parentId": "root-8-h-ge",
//                     "type": "node",
//                     "key": 6
//                   }
//                 ]
//               },
//               {
//                 "id": "root-8-h-gx",
//                 "parentId": "root-8-h",
//                 "type": "node",
//                 "key": "gx",
//                 "text": true
//               },
//               {
//                 "id": "root-8-h-gy",
//                 "parentId": "root-8-h",
//                 "type": "node",
//                 "key": "gy",
//                 "text": null
//               },
//               {
//                 "id": "root-8-h-gz",
//                 "parentId": "root-8-h",
//                 "type": "node",
//                 "key": "gz"
//               }
//             ]
//           },
//           {
//             "id": "root-8-i",
//             "parentId": "root-8",
//             "type": "node",
//             "key": "i"
//           },
//           {
//             "id": "root-8-Symbol(i-sym-key-a)",
//             "parentId": "root-8",
//             "type": "node",
//             "text": "i-sym-value"
//           },
//           {
//             "id": "root-8-Symbol(i-sym-key-b)",
//             "parentId": "root-8",
//             "type": "object",
//             "numOfChilren": 7,
//             "children": [
//               {
//                 "id": "root-8-Symbol(i-sym-key-b)-symfa",
//                 "parentId": "root-8-Symbol(i-sym-key-b)",
//                 "type": "node",
//                 "key": "symfa",
//                 "text": 61
//               },
//               {
//                 "id": "root-8-Symbol(i-sym-key-b)-symfb",
//                 "parentId": "root-8-Symbol(i-sym-key-b)",
//                 "type": "node",
//                 "key": "symfb",
//                 "text": 612.3
//               },
//               {
//                 "id": "root-8-Symbol(i-sym-key-b)-symfc",
//                 "parentId": "root-8-Symbol(i-sym-key-b)",
//                 "type": "node",
//                 "key": "symfc",
//                 "text": "abc"
//               },
//               {
//                 "id": "root-8-Symbol(i-sym-key-b)-symfd",
//                 "parentId": "root-8-Symbol(i-sym-key-b)",
//                 "type": "node",
//                 "key": "symfd",
//                 "text": "645-de"
//               },
//               {
//                 "id": "root-8-Symbol(i-sym-key-b)-symfe",
//                 "parentId": "root-8-Symbol(i-sym-key-b)",
//                 "type": "node",
//                 "key": "symfe",
//                 "text": true
//               },
//               {
//                 "id": "root-8-Symbol(i-sym-key-b)-symff",
//                 "parentId": "root-8-Symbol(i-sym-key-b)",
//                 "type": "node",
//                 "key": "symff",
//                 "text": null
//               },
//               {
//                 "id": "root-8-Symbol(i-sym-key-b)-symfg",
//                 "parentId": "root-8-Symbol(i-sym-key-b)",
//                 "type": "node",
//                 "key": "symfg"
//               }
//             ]
//           }
//         ]
//       }
//     ]
//   }
// ]
Parameters:
Name Type Attributes Default Description
data Array | Object

輸入項目物件或陣列

opt Object <optional>
{}

輸入設定物件,預設{}

bindKey String <optional>
'id'

輸入識別用欄位字串,預設'id'

bindParent String <optional>
'parentId'

輸入存放父節點欄位字串,預設'parentId'

bindText String <optional>
'value'

輸入存放值欄位字串,預設'value'

bindChildren String <optional>
'children'

輸入產生樹狀物件時,各節點內存放子節點欄位字串,預設'children'

bindType String <optional>
'type'

輸入存放值種類欄位字串,預設'type'

bindNumOfChilren String <optional>
'numOfChilren'

輸入存放值為物件或陣列時所屬子節點數量欄位字串,預設'numOfChilren'

bindRoot String <optional>
'root'

輸入若為物件時,自動給予根節點鍵值字串,預設'root'

saveExtProps Boolean <optional>
false

輸入是否儲存項目物件從屬資訊布林值,預設false

Returns:

回傳樹狀陣列

Type
Array

(static) copyStrToClipboard(str, optopt) → {String}

Description:
  • 複製字串至剪貼簿

    Unit Test: Github

Source:
Example
copyStrToClipboard('The Woodman(樵夫) set to work at once, and so...')

let ele = document.querySelector('#id')
copyStrToClipboard('The Woodman(樵夫) set to work at once, and so...', { eleParent: ele })
Parameters:
Name Type Attributes Default Description
str String

輸入欲複製至剪貼簿的字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
eleParent HTMLElement <optional>
document.body

輸入臨時插入的父層元素,因例如於鎖定焦點(focus)的區域內要複製字串,會因臨時創建的textarea元素無法聚焦導致無法複製至剪貼簿,故得由外部傳入可聚焦的父層元素供創建textarea之用,預設document.body

Returns:

回傳字串,若成功則為空字串,若失敗則回傳錯誤訊息

Type
String

(static) core(t, tNowopt, optopt) → {Object}

Description:
  • 毫秒時間轉過去時間

    Unit Test: Github

Source:
Example
let t
let tNow = '2020-10-18T12:34:56.987+08:00'
let r

t = '2020-10-18T12:34:54.987+08:00'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '2秒前', err: '' }

t = '2020-10-18T04:34:54.987+00:00'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '2秒前', err: '' }

t = '2020-10-18T04:34:54.987Z'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '2秒前', err: '' }

t = '2020-10-18T12:34:55.500+08:00'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '1秒前', err: '' }

t = '2020-10-18T12:31:54.987+08:00'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '3分鐘前', err: '' }

t = '2020-10-18T08:31:54.987+08:00'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '4小時前', err: '' }

t = '2020-10-13T08:31:54.987+08:00'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: false, msg: '5天前', err: '' }

t = '2020-04-13T08:31:54.987+08:00'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: false, msg: '6個月前', err: '' }

t = '2013-04-13T08:31:54.987+08:00'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: false, msg: '7年前', err: '' }

t = '2023-04-13T08:31:54.987+08:00'
r = timemsTZ2past(t, tNow)
console.log(r)
// => { today: null, msg: '', err: '時間未到' }
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串

tNow String <optional>
null

輸入現在毫秒時間字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳物件,含today、msg、err三欄位;t非有效毫秒時間字串時回傳{ today: null, msg: '', err: '時間格式錯誤' },時間未到時回傳{ today: null, msg: '', err: '時間未到' };若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件,其中時間未到屬算得之結果故state仍為'success'

Type
Object

(static) cosd(deg) → {Number}

Description:
  • 輸入角度計算cos值

    Unit Test: Github

Source:
Example
console.log(cosd(0))
// => 1

console.log(cosd(30))
// => 0.8660254037844387

console.log(cosd(45))
// => 0.7071067811865476

console.log(cosd(60))
// => 0.5000000000000001

console.log(cosd(90))
// => 6.123233995736766e-17

console.log(cosd(120))
// => -0.4999999999999998

console.log(cosd(135))
// => -0.7071067811865475

console.log(cosd(150))
// => -0.8660254037844387

console.log(cosd(180))
// => -1

console.log(cosd(210))
// => -0.8660254037844386

console.log(cosd(225))
// => -0.7071067811865477

console.log(cosd(240))
// => -0.5000000000000004

console.log(cosd(270))
// => -1.8369701987210297e-16

console.log(cosd(300))
// => 0.5000000000000001

console.log(cosd(315))
// => 0.7071067811865474

console.log(cosd(330))
// => 0.8660254037844384

console.log(cosd(360))
// => 1

console.log(cosd(450))
// => 3.061616997868383e-16

console.log(cosd(720))
// => 1
Parameters:
Name Type Description
deg Number | String

輸入角度數字或字串

Returns:

回傳cos值

Type
Number

(static) cotd(deg) → {Number}

Description:
  • 輸入角度計算cot值

    Unit Test: Github

Source:
Example
console.log(cotd(0))
// => Infinity

console.log(cotd(30))
// => 1.7320508075688774

console.log(cotd(45))
// => 1.0000000000000002

console.log(cotd(60))
// => 0.577350269189626

console.log(cotd(90))
// => 6.123233995736766e-17

console.log(cotd(120))
// => -0.5773502691896254

console.log(cotd(135))
// => -0.9999999999999998

console.log(cotd(150))
// => -1.7320508075688774

console.log(cotd(180))
// => -8165619676597685

console.log(cotd(210))
// => 1.7320508075688767

console.log(cotd(225))
// => 1.0000000000000004

console.log(cotd(240))
// => 0.5773502691896264

console.log(cotd(270))
// => 1.83697019872103e-16

console.log(cotd(300))
// => -0.5773502691896258

console.log(cotd(315))
// => -0.9999999999999996

console.log(cotd(330))
// => -1.7320508075688754

console.log(cotd(360))
// => -4082809838298842.5

console.log(cotd(450))
// => 3.061616997868383e-16

console.log(cotd(720))
// => -2041404919149421.2
Parameters:
Name Type Description
deg Number | String

輸入角度數字或字串

Returns:

回傳cot值

Type
Number

(static) createExcelWorkbook() → {Object}

Description:
  • 產生Excel的Workbook空物件

    Workbook物件格式為{ sheets: [] },各分頁為{ name, rows }物件,可交由addExcelWorksheetFromData添加分頁、getExcelU8ArrFromWorkbook輸出檔案

    Unit Test: Github

Source:
Example
let wb = createExcelWorkbook()
console.log(wb)
// => { sheets: [] }
Returns:

回傳Excel的Workbook物件

Type
Object

(static) cscd(deg) → {Number}

Description:
  • 輸入角度計算csc值

    Unit Test: Github

Source:
Example
console.log(cscd(0))
// => Infinity

console.log(cscd(30))
// => 2.0000000000000004

console.log(cscd(45))
// => 1.4142135623730951

console.log(cscd(60))
// => 1.1547005383792517

console.log(cscd(90))
// => 1

console.log(cscd(120))
// => 1.1547005383792515

console.log(cscd(135))
// => 1.414213562373095

console.log(cscd(150))
// => 2.0000000000000004

console.log(cscd(180))
// => 8165619676597685

console.log(cscd(210))
// => -1.9999999999999996

console.log(cscd(225))
// => -1.4142135623730951

console.log(cscd(240))
// => -1.1547005383792517

console.log(cscd(270))
// => -1

console.log(cscd(300))
// => -1.1547005383792517

console.log(cscd(315))
// => -1.4142135623730947

console.log(cscd(330))
// => -1.9999999999999982

console.log(cscd(360))
// => -4082809838298842.5

console.log(cscd(450))
// => 1

console.log(cscd(720))
// => -2041404919149421.2
Parameters:
Name Type Description
deg Number | String

輸入角度數字或字串

Returns:

回傳csc值

Type
Number

(static) cstr(v) → {String}

Description:
  • 輸入轉字串 若輸入不是字串、數字、Symbol、BigInt時則回傳空字串

    Unit Test: Github

Source:
Example
console.log(cstr(2.25))
// => '2.25'

console.log(cstr(123n))
// => '123'

console.log(cstr(BigInt('9007199254740993')))
// => '9007199254740993' (超過Number.MAX_SAFE_INTEGER, BigInt精確保留)
Parameters:
Name Type Description
v String | Number | Symbol | BigInt

輸入資料

Returns:

回傳字串

Type
String

(static) cvOrmFind(find, optopt) → {Object}

Description:
  • w-orm系列條件(find)之形狀檢核與正規化,回傳全新之null原型深複本,不合白名單者同步拋錯

    輸入為任意值:undefined與一般物件{}為全部(回傳空的null原型物件),其餘依系列白名單檢核,通過者回傳全新複本(物件皆為null原型、陣列為全新密集陣列、Date為新Date,與輸入無任何共用參照),後續一律只使用複本。

    白名單:

    1. 頂層與每層條件物件須為一般物件(原型為Object.prototype或null),null、陣列、字串、數值、布林、函數、Date、RegExp、Map、Set、類別實例等一律reject,不得視同{};原型被改為null或Object.prototype之Map、Set、WeakMap、WeakSet、Promise、ArrayBuffer、DataView、TypedArray、Symbol與BigInt包裝、WeakRef、FinalizationRegistry等內建品牌物件,於任何位置(頂層、欄位運算子物件、$and/$or元素,空或帶自有鍵)亦reject;頂層空物件(代表全部)須可證明為真實空物件,Proxy與不可複製之宿主物件不視為全部

    2. 欄位等值{欄: 純量},多欄隱含AND;純量為字串、有限數字(含-0)、布林、有效Date,字串不得含NUL與孤立代理字元;null僅可用於欄位等值、$eq、$ne

    3. 欄位運算子:$eq $ne $gt $gte $lt $lte(值為純量)、$in $nin(值為純量陣列,元素不收null;$in:[]允許為恆假,$nin:[]reject),運算子物件須至少一個運算子

    4. 文件層邏輯:$and、$or(值為非空陣列,元素為非空條件物件),可巢狀、可與欄位鍵並列

    5. 欄名:1~128字元(UTF-16碼元),符合^[\p{L}][\p{L}\p{M}\p{N}]*$且非__proto__;點路徑、$開頭、連字號、空白、引號等一律reject

    6. 其餘一律reject(整批性、不得靜默忽略):$not $nor $regex $options $exists $type $where $expr $function $elemMatch $size $all $mod $like $ilike $custom等白名單外運算子、欄位層$and/$or、子查詢、Symbol鍵、函數值、BigInt、Buffer、RegExp、NaN、±Infinity、無效Date、undefined、包裝物件

    7. 異常結構一律reject:存取器屬性、不可列舉屬性、Symbol鍵、陣列之洞與額外屬性、length非非負整數之陣列;每個屬性與元素只讀一次並只使用該次所讀之值,帶length鍵之一般物件(如{length:3})之length為一般欄名

    8. 上限(opt只可下調,不可放寬):邏輯巢狀深度32、$and/$or長度1000、$in/$nin長度10000、節點總數100000(條件物件、運算子物件、純量各計1)、欄名128

    錯誤一律為同步拋出之Error('invalid find: 原因 at [路徑]'),不回顯值(鍵名為路徑之一部分,限30字元並以?取代控制字元、格式字元與方括號);走訪中任何例外(含呼叫端之getter、Proxy陷阱、Symbol.toStringTag getter)一律轉為此格式,不轉述呼叫端之錯誤訊息,描述子陷阱拋錯、存取器與不可列舉屬性之路徑含該屬性鍵或元素索引;可重入。opt須為物件、null或未給,僅可含下列五個鍵(自有資料屬性),設定錯誤另拋Error('invalid opt: 原因')。

    已知限制:判定內部標籤須讀Symbol.toStringTag,可能執行呼叫端之Proxy陷阱或getter(例外一律轉為invalid find);Proxy無法穿透,非空位置之Proxy包住原型被改之品牌物件視同其經陷阱暴露之條件物件;無法觀察品牌者(含私有欄位之類別實例與AbortController等宿主物件改原型為null、自有鍵為純量constructor之Promise)不被辨出;品牌檢查先以一次結構化複製(structuredClone)證明被記錄之條件物件皆非品牌物件,環境無可用之該全域、字串總長逾400萬碼元或樹內含Proxy時改逐物件探針(每物件約50微秒,含Proxy之近節點上限輸入可達數秒),且無可用之該全域時無法辨出空Proxy;被判為品牌物件而reject者,其內部資料(Map/Set之項目、ArrayBuffer之位元組)可能被複製讀取一次,成本不受節點預算約束;Proxy之ownKeys結果於引擎內先被完整具現化,其成本不受節點預算約束;共享參照(同一物件出現於多處)每出現一次即各讀各檢各複製,節點數亦逐次計入

    後端專屬之檢核(欄位型別、欄是否存在、主鍵正規化、DATE範圍、NULL三值語意)不在此,由各後端於本函數之後自行處理;契約案例表見wsemi/src/_cvOrmFindCases.mjs。

    Unit Test: Github

Source:
Example
console.log(Object.keys(cvOrmFind()))
// => []

console.log(Object.keys(cvOrmFind({})))
// => []

let r = cvOrmFind({ name: 'a', age: { $gt: 20, $in: [21, 22] }, $or: [{ g: 'A' }, { g: null }] })
console.log(Object.getPrototypeOf(r), Object.keys(r))
// => null [ 'name', 'age', '$or' ]

try {
    cvOrmFind({ name: { $regex: 'a' } })
}
catch (err) {
    console.log(err.message)
}
// => invalid find: unsupported operator [$regex] at [find.name.$regex]

try {
    cvOrmFind(null)
}
catch (err) {
    console.log(err.message)
}
// => invalid find: condition must be a plain object, got [null] at [find]
Parameters:
Name Type Attributes Default Description
find *

輸入條件,可為任意值,undefined或{}代表全部

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
maxDepth Integer <optional>
32

輸入邏輯巢狀深度上限整數,只可下調,預設32

maxList Integer <optional>
1000

輸入$and/$or長度上限整數,只可下調,預設1000

maxIn Integer <optional>
10000

輸入$in/$nin長度上限整數,只可下調,預設10000

maxNodes Integer <optional>
100000

輸入節點總數上限整數,只可下調,預設100000

maxKey Integer <optional>
128

輸入欄名長度上限整數,只可下調,預設128

Throws:

不合白名單者拋出Error('invalid find: 原因 at [路徑]'),opt錯誤者拋出Error('invalid opt: 原因')

Type
Error
Returns:

回傳全新之null原型條件物件,無自有鍵代表全部

Type
Object

(static) day2cht(t, bNoDisplayYearopt) → {String}

Description:
  • 日時間轉中文格式時間字串

    Unit Test: Github

Source:
Example
console.log(day2cht('2019-01-02'))
// => '民國 108 年 1 月 2 日'
Parameters:
Name Type Attributes Default Description
t String

輸入日時間字串

bNoDisplayYear boolean <optional>
false

輸入是否輸出民國年,預設為false

Returns:

回傳中文格式時間字串

Type
String

(static) debounce(msopt, optopt)

Description:
  • 函數去除抖動

    呼叫無回傳值,任務不被等待;任務同步拋錯或其Promise被reject時以console.log記錄而不外拋(不產生未處理之rejection),不影響後續佇列;輸入非函數時不排入佇列(不影響已排入之任務),以console.log記錄'func is not a function'

    Unit Test: Github

Source:
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(() => {})
Parameters:
Name Type Attributes Default Description
ms Integer <optional>
300

輸入未有調用的時間區間,為正整數,預設300ms

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'push'時為任務排入佇列(含size為排入後之佇列長度),'start'時為開始偵測(每10ms檢查一次是否已超過ms未有調用),'run'時為已超過ms未有調用而即將執行最後之任務(含dropped為被捨棄之較早任務數),'stop'時為佇列已空而停止偵測;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響去除抖動,預設null

(static) delay(msopt, optopt) → {Promise}

Description:
  • Promise延遲毫秒

    Unit Test: Github

Source:
Example
need test in browser

delay(1000)
// => 延遲1s才執行
Parameters:
Name Type Attributes Default Description
ms Number <optional>
10

輸入延遲毫秒整數,預設10ms

opt Object <optional>
null

輸入設定物件,預設null

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'start'時為開始延遲(含ms為實際使用之延遲毫秒數,已套用預設值與計時器上限),'done'時為延遲完畢而即將resolve(含ms同上);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響延遲,預設null

Returns:

回傳Promise,resolve代表延遲完畢

Type
Promise

(static) detectXSS(str) → {Array}

Description:
  • 偵測字串內之xss攻擊語法,回傳clearXSS會轉義或刪除之可疑項目,供提示用,不改寫資料

    Depend on: https://jsxss.com/zh/index.html

    與clearXSS共用同一份js-xss設定(白名單、網址判定、白名單外之style屬性保留),判定規則:

    1. 標籤(type為'tag'):白名單外之標籤,名稱由原始標籤片段取得且其後須接空白、斜線、大於或結尾,故含'<svg/onload=...>',不含'孔深 < 500 m'、'5<6>4'、'<o:p>';結尾未閉合且無屬性之白名單標籤(如'若 a<b'之'<b')亦不計

    2. 屬性(type為'attr'):白名單內標籤被刪除之事件屬性,名稱為on接英文字母且有值,故不含'ac'之once

    3. 網址(type為'url'):白名單內標籤之href或src被擋下,且依HTML5規則解碼字元參照、刪除tab與換行、刪除開頭控制字元與空白後,協定為javascript、vbscript或data,相對網址被擋不計

    4. 註解:js-xss先刪註解再解析,與瀏覽器之註解規則不同(如'--!>'亦結束註解),有註解時另保留註解再解析一次取聯集,註解本身不計

    5. 重複項目合併為1筆,依出現順序排列

    限制:style屬性比照clearXSS保留不檢查;非HTML之純文字(如'javascript:alert(1)')不偵測,網址欄位須另檢查協定

    Unit Test: Github

Source:
Example
console.log(detectXSS('<img src=x onerror=alert(1)><script>alert(1)</script>'))
// => [ { type: 'attr', tag: 'img', name: 'onerror' }, { type: 'tag', tag: 'script' } ]

console.log(detectXSS('<svg/onload=alert(1)>'))
// => [ { type: 'tag', tag: 'svg' } ]

console.log(detectXSS('<a href="jav&#x09;ascript:alert(1)">點我</a>'))
// => [ { type: 'url', tag: 'a', name: 'href', scheme: 'javascript' } ]

console.log(detectXSS('<!-- --!><script>alert(1)</script>-->'))
// => [ { type: 'tag', tag: 'script' } ]

console.log(detectXSS('&lt;b&gt; 為粗體標籤'))
// => []

console.log(detectXSS('孔深 < 500 m 且 > 10 m'))
// => []

console.log(detectXSS('若 a<b'))
// => []

console.log(detectXSS('a<b and once>c'))
// => []

console.log(detectXSS('<div style="text-align:center;">圖1 名稱</div>'))
// => []

console.log(detectXSS('<img src="pics/a.png" alt="圖">'))
// => []
Parameters:
Name Type Description
str String

輸入字串

Returns:

回傳偵測項目陣列,無則回傳空陣列,非字串或空字串回傳空陣列,項目為{ type: 'tag', tag }、{ type: 'attr', tag, name }或{ type: 'url', tag, name, scheme }

Type
Array

(async, static) detectXSSDyn(str, pathItems) → {Promise}

Description:
Source:
Example
let r

r = await detectXSSDyn('<img src=x onerror=alert(1)><script>alert(1)</script>')
console.log(r)
// => [ { type: 'attr', tag: 'img', name: 'onerror' }, { type: 'tag', tag: 'script' } ]

r = await detectXSSDyn('<a href="jav&#x09;ascript:alert(1)">點我</a>')
console.log(r)
// => [ { type: 'url', tag: 'a', name: 'href', scheme: 'javascript' } ]
Parameters:
Name Type Description
str String

輸入字串

pathItems String | Object | Array

輸入資源字串、字串陣列、物件、物件陣列

Returns:

回傳Promise,resolve回傳偵測項目陣列,無則回傳空陣列,reject回傳錯誤訊息

Type
Promise

(static) dig(v, idigopt) → {String}

Description:
  • 數字取指定小數位的字串 為字串故可自動補0 console.log(1.005.toFixed(2)) //1.00 console.log(dig(1.005, 2)) //1.01 若輸入不是數字或字串時則回傳空字串

    Unit Test: Github

Source:
Example
console.log(dig(0.123456789, 3))
// => '0.123'

console.log(dig(0.123456789, 4))
// => '0.1235'
Parameters:
Name Type Attributes Default Description
v Number | String

輸入數字或字串

idig Integer <optional>
0

輸入指定小數位整數,預設為0

Returns:

回傳取指定小數位的字串

Type
String

(static) digExp(v, idigopt) → {String}

Description:
  • 數字取指定小數位,小於1e-6改指數顯示 若輸入不是數字或字串時則回傳空字串

    Unit Test: Github

Source:
Example
console.log(digExp(0.000000123456789, 2))
// => '1.23e-7'
Parameters:
Name Type Attributes Default Description
v Number | String

輸入數字或字串

idig Integer <optional>
0

輸入指定小數位整數,預設為0

Returns:

回傳取指定小數位的字串

Type
String

(static) domAlert(msg, optopt) → {Promise}

Description:
  • 前端彈出訊息視窗

    Unit Test: Github

Source:
Example
need test in browser

domAlert('提示訊息')

domAlert('提示訊息', {type:'error'})

domAlert('提示訊息', {backgroundColor:'rgba(220,220,220,0.3)'})
Parameters:
Name Type Attributes Default Description
msg String

輸入彈窗訊息字串,可為html

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'success'

輸入訊息種類字串,可使用'success'、'warning'、'error',預設'success'

icon String <optional>
''

輸入圖標字串,可為html,預設依照type給予指定圖標,指定圖標詳見程式碼

position String <optional>
'top-right'

輸入位置字串,可使用'top-right'、'top-left'、'bottom-right'、'bottom-left',預設'top-right'

time Number <optional>
4000

輸入顯示時間長度數字,單位為毫秒ms,預設4000

paddingStyle Object <optional>
{top:14,right:26,bottom:14,left:14}

輸入內寬設定物件,可輸入top、bottom、left、right,單位為px,預設{top:14,right:26,bottom:14,left:14}

textColor String <optional>
'#000'

輸入文字顏色字串,預設'#000'

backgroundColor String <optional>
'#fff'

輸入背景顏色字串,預設'#fff'

borderWidth Number <optional>
1

輸入邊框寬度數字,單位px,預設1

borderColor String <optional>
'#ebeef5'

輸入邊框顏色字串,預設'#ebeef5'

borderRadius Number <optional>
8

輸入邊框圓角數字,單位px,預設8

shadowStyle String <optional>
'0 2px 12px 0 rgba(0,0,0,.1)'

輸入陰影樣式字串,預設'0 2px 12px 0 rgba(0,0,0,.1)'

closeIconShift Object <optional>
{top:10,right:10}

輸入關閉按鈕平移距離設定物件,可輸入top、bottom、left、right,單位為px,若需要位於右上角則需設定top與right,預設{top:10,right:10}

Returns:

回傳Promise,resolve代表彈窗關閉,無reject

Type
Promise

(static) domAppend(eleParent, itemChild)

Description:
  • 前端於DOM元素內最後處插入元素或Html文字等

    Unit Test: Github

Source:
Example
need test in browser

let html = '<div>abc</div>'
domAppend(document.querySelector('#id'),html)
Parameters:
Name Type Description
eleParent HTMLElement

輸入目標DOM元素

itemChild HTMLElement | String

輸入要插入DOM元素或Html文字等內容

(static) domCancelEvent(event)

Description:
  • 前端針對DOM元素事件設定停止傳遞事件

    Unit Test: Github

Source:
Example
need test in browser

window.addEventListener('touchstart', (e) => {
    domCancelEvent(e)
})
Parameters:
Name Type Description
event HTMLEvent

輸入event物件

(static) domCompareText(ele, title, strOld, strNew, optopt) → {Object}

Description:
  • 前端針對指定DOM上展示2文字差異比對資訊

    Unit Test: Github

Source:
Example
need test in browser

let r = domCompareText(ele, 'title', 'strOld', 'strNew')
console.log(r)
// => { diff, html }
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入元素

title String

輸入比對標題字串

strOld String

輸入舊文字字串

strNew String

輸入新文字字串

opt Object <optional>
{}

輸入設定物件,主要是提供給Diff2Html之設定物件,預設{}

Properties
Name Type Attributes Default Description
fmt String <optional>
''

輸入比對展示模式字串,給予'side'為依照左右對應區塊展示差異,給予'line'為依照各行展示差異,預設'side'

Returns:

回傳結果物件,內含屬性diff與html,分別代表2文字比對差異結果物件,以及轉出html文字

Type
Object

(async, static) domConvertToPic(ele, optopt) → {Promise}

Description:
  • 前端針對DOM元素轉圖片

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
domConvertToPic(ele, { scale: 3 })
    .then((b64)=>{
        // => data:image/png;base64,iVBORw0KGgoAAAANSU...
    })
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入元素

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
scale Number <optional>
1

輸入縮放比例數字,需大於等於1,預設1

toBase64 Boolean <optional>
true

輸入是否回傳圖片之data URL字串布林值,給true回傳字串(png與jpeg為base64編碼,svg為utf-8之URL編碼),給false回傳img元素(HTMLImageElement),預設true

picType String <optional>
'image/png'

輸入輸出圖片格式字串,可選'image/png'、'image/jpeg'(亦可給'image/jpg')與'svg',png與svg保留透明背景,jpeg之透明處填白色,給其他值則使用'image/png',預設'image/png'

Returns:

回傳Promise,resolve回傳產出圖片,toBase64為true時為data URL字串,否則為img元素,reject為失敗訊息

Type
Promise

(async, static) domConvertToPicDyn(ele, optopt, pathItemsopt) → {Promise}

Description:
  • 前端DOM元素轉圖片

    本函數為相容舊版之保留函數:舊版採動態加載CDN之@zumer/snapdom,現已將@zumer/snapdom直接打包進wsemi,無需再動態加載 內部直接委派domConvertToPic,pathItems參數保留簽名相容但已不使用

    Unit Test: Github

Source:
Example
need test in browser

domConvertToPicDyn(ele, { scale: 3 })
    .then((b64)=>{
        // => data:image/png;base64,iVBORw0KGgoAAAANSU...
    })
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入元素

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
scale Number <optional>
1

輸入縮放比例數字,需大於等於1,預設1

toBase64 Boolean <optional>
true

輸入是否回傳圖片之data URL字串布林值,給true回傳字串(png與jpeg為base64編碼,svg為utf-8之URL編碼),給false回傳img元素(HTMLImageElement),預設true

picType String <optional>
'image/png'

輸入輸出圖片格式字串,可選'image/png'、'image/jpeg'(亦可給'image/jpg')與'svg',png與svg保留透明背景,jpeg之透明處填白色,給其他值則使用'image/png',預設'image/png'

pathItems String | Object | Array <optional>

輸入資源字串、字串陣列、物件、物件陣列,已不使用,僅保留簽名相容

Returns:

回傳Promise,resolve回傳產出圖片,toBase64為true時為data URL字串,否則為img元素,reject回傳錯誤訊息

Type
Promise

(static) domDetect(f, optopt) → {Object}

Description:
  • 前端針對DOM元素監聽resize、resizeWithWindow事件,其中resizeWithWindow為dom resize與window resize皆會觸發的事件

    瀏覽器支援ResizeObserver與MutationObserver時以其偵測(即時且閒置時不耗資源),否則退回定期輪詢,兩者之比較規則與事件相同:以比較用尺寸(預設offsetWidth、offsetHeight)與比較基準之差超過容許誤差即發出,比較基準預設為上次發出事件時之尺寸,故緩慢之連續小變化累積超過容許誤差亦會發出;容許誤差內之變化亦照常發出(同向逐px之變化如動畫或拖曳之末段不被略過),惟為阻斷使用端於事件內改變尺寸所形成之回授,某軸之量測來回跳動(相鄰兩次量測之變化方向連續反向)達toleranceBounce次後,該軸容許誤差內之變化不再發出,直到該軸出現同向變化或超過容許誤差,且某軸容許誤差內之同向變化連續發出10次後,不再發出容許誤差內之同向變化,直到該軸反向或超過容許誤差(持續之逐px變化於下一次即累積超過容許誤差而照發,單向回授則至此中斷);尺寸為0(隱藏或移出DOM)不發出,由隱藏恢復顯示時會發出,首次取得非0尺寸時會發出;延後發出(非同步、節流或refresh)時,發出前元素已不被繪製(移出頁面,或自身或祖先為display:none)者不發出,待其再顯示時重新比較(同由隱藏恢復顯示,sold為0、smode兩軸為'larger'),此確認只計算樣式而不讀外框,故元素仍被繪製而尺寸於空檔內縮為0者照發;sync為true時refresh與節流之後續發出仍為延後發出,其量測早於已處理之事件者不發出

    元素可取不到、中途消失或重建為新節點:取不到、不在頁面中或尺寸為0時,於DOM變動時重新以f取得元素並改觀察之。行內元素(display:inline)ResizeObserver不回報,該偵測器改以定期量測,元素自身隱藏(display:none,如v-show)期間亦持續,顯示後即可量得;是否為行內於取得元素、ResizeObserver回報、定期量測及元素插入或移出頁面時判定

    事件內容:sold為上次判定變化時之量測,snew為本次量測(另含width、height為比較用尺寸),smode為寬與高相對比較基準之變化方向('larger'、'smaller'或''),僅該軸判定為變化時(超過容許誤差,或容許誤差內且未因回授而停止)給方向,否則為'',有給getBase者延後發出時依發出當下與使用端尺寸之比較重給,ele為元素;from為'window'之事件無ele,其snew為最新量測,其中視窗尺寸取事件當下之值,其smode寬與高恆為''(視窗事件不代表元素尺寸變化,且最新量測與比較基準之差必在容許誤差內);視窗事件於最新量測之任一維為0(隱藏、移出頁面、取不到元素、尚未量得尺寸,或寬高其一為0之元素),或元素當下不被繪製(例如媒體查詢隨視窗改變而隱藏)時不發出,與dom事件之規則一致

    已知限制:ResizeObserver模式下,僅屬性變化(如class)使f改指他元素、或Shadow DOM內之節點被替換時不會跟隨,行內元素自取得起即自身為display:none(未曾以行內顯示過)、之後僅以style或class改為顯示者不會開始量測,此類使用端請用mode:'polling';sync為true時監聽器不得使所監聽元素之尺寸於同一幀內再變,否則瀏覽器回報ResizeObserver loop錯誤;Safari 15.4以前不支援觀察border-box,只改padding或border之變化於該處不會發出;回授之阻斷僅限容許誤差內之來回跳動與單向兩種,每週期含超過容許誤差之變化者(例如+1、+1、-2之鋸齒)與超過容許誤差之回授不阻斷,此類屬使用端版面缺陷(例如以含框線之offsetHeight設定撐開自身之子元素高);因回授而停止時,以及同向逐px之變化恰於連續第11次結束時,最後一次發出之尺寸可與實際差在容許誤差內

    Unit Test: Github

Source:
Example
need test in browser

//監聽dom
let de = domDetect(() => {
    return document.querySelector('#id')
})
de.on('resize', (s) => {
    console.log('resize', s)
})
de.on('resizeWithWindow', (s) => {
    console.log('resizeWithWindow', s)
})

//釋放監聽
de.clear()
Parameters:
Name Type Attributes Default Description
f function

輸入取得dom函數,可回傳null表示目前取不到

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
mode String <optional>
''

輸入偵測模式字串,給'polling'則強制使用定期輪詢,預設''

timeInterval Integer <optional>
20

輸入定期偵測時間整數,單位毫秒,用於輪詢模式與行內元素,須為正整數(可為數字字串),超過計時器上限(含Infinity)者夾至上限,無效時用預設,預設20

tolerancePixel Integer <optional>
1

輸入容許誤差整數,單位px,差值不超過此值之變化為容許誤差內之變化(是否發出見toleranceBounce),可給0表示任何變化皆發出(無回授之阻斷),須為不小於0之整數(可為數字字串,Infinity表示任何變化皆不發出),無效時用預設,預設1

toleranceBounce Integer <optional>
3

輸入容許誤差內來回跳動之次數整數,容許誤差內之變化照常發出,惟某軸相鄰兩次量測(同一元素之前後兩次可見量測,元素換新、取不到或隱藏後重新計數)之變化方向連續反向達此次數後(視為使用端於事件內改變尺寸所形成之回授),該軸容許誤差內之變化不再發出,直到該軸出現同向變化或超過容許誤差;另某軸容許誤差內之同向變化連續發出10次後,不再發出容許誤差內之同向變化,直到該軸反向或超過容許誤差(阻斷單向回授);給0表示容許誤差內之變化一律不發出(舊版行為),給Infinity表示容許誤差內之變化一律發出(不阻斷回授),須為不小於0之整數(可為數字字串),無效時用預設,預設3

sync Boolean <optional>
false

輸入是否於ResizeObserver回呼內同步發出事件布林值,true時使用端可於瀏覽器繪製前更新版面(例如圖表重繪),僅ResizeObserver模式有效,預設false

watchIdentity Boolean <optional>
false

輸入元素可見時是否仍於DOM變動時重新取得元素布林值,f可能於舊元素仍可見時改回傳另一元素者給true,僅ResizeObserver模式有效,預設false

getSize function <optional>
null

輸入比較用尺寸函數,傳入元素,回傳{width,height},供依內容區或特定量測方式繪製之使用端(例如圖表依clientWidth扣除padding),給予時只改padding、元素內出現或消失捲軸等外框不變之變化亦會發出,回傳非數字或拋錯之軸視為0(不發出),預設null表示使用offsetWidth、offsetHeight

getBase function <optional>
null

輸入比較基準函數,回傳使用端目前套用之尺寸{width,height},該軸非數字(含拋錯)表示該軸不比較(例如固定寬度),給予時以其取代上次發出事件時之尺寸,故掛載時尺寸已一致者不發出(其後事件之sold於首次判定變化前為0),由隱藏恢復顯示時若與使用端尺寸相同亦不發出;使用端須於事件內套用新尺寸(getBase之回傳隨之更新),否則之後每次回報皆判定為變化而發出,預設null

throttle Integer <optional>
0

輸入節流時間整數,單位毫秒,首次立即發出,其後每throttle毫秒至多發出一次並以最後一次之量測發出(其sold為最後一次判定變化前之量測,可能未曾發出),clear時取消待發出者,僅作用於元素尺寸之事件(resize與from為'dom'之resizeWithWindow),視窗事件不節流,須為正整數(可為數字字串),超過計時器上限(含Infinity)者夾至上限,其餘視為0,預設0表示不節流

onTrace function <optional>
null

輸入觀察回調函數,於偵測器內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'check'時為每次量測與比較(含source為量測來源:'poll'輪詢、'observer'ResizeObserver回報、'inline'行內元素之定期量測、'refresh';found為是否取得元素;width、height為比較用尺寸,取不到元素時為0;changed為是否判定為變化而交付發出;seq為判定為變化之序號,否則null),'emit'時為即將發出resize事件(含seq、width、height,deferred為是否延後發出),'skip'時為判定為變化而不發出(含seq,reason為'stale'已處理較新之量測、'zero'尺寸為0、'hidden'延後發出前元素已不被繪製、'base'延後發出前與getBase之尺寸已一致),'inline'時為行內元素之定期量測開始或停止(含on);節流期間併入後續發出之判定無emit或skip事件;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響偵測,clear後不再呼叫,預設null

Returns:

回傳物件,可使用on、refresh與clear函數,on可監聽resize與resizeWithWindow事件,refresh為立即重新量測並比較(延後發出,給throttle時併入節流而至多延後throttle毫秒),供比較基準因尺寸以外之原因改變時使用,clear為釋放監聽,可於任何時點呼叫(含元素尚未取得),並取消已排定之事件

Type
Object

(static) domDrag(ele, optopt) → {Object}

Description:
  • 前端針對DOM元素設定監聽拖曳事件

    同群組(group)之元素間拖放;須帶順序指標屬性(attIndex,有限數字,同群組須唯一)者才可為拖曳起點或落點,是否同一項目以順序指標判定

    落點以指標座標判定(經過與放下相同,滑鼠與觸控相同):取該點由上而下第一個本群組之已綁定元素,含指標位於其子元素上,但子元素超出元素框外之部分不算;不屬本群組之元素(如遮罩)不阻擋其下之元素;巢狀時為最內層者,按下亦由最內層者起手;同時只有一個拖曳(跨群組亦同)

    滑鼠僅主鍵可起手,拖曳中其他鍵之按放不影響;移動時主鍵已放開(於視窗外放開而收不到mouseup,限真實事件)即結束。觸控以起手手指鎖定,他指之落下、移動、放開不影響,起手手指已離開(放開事件遺失)時新觸控可接手;滑鼠按下一律接手;觸控輕點後瀏覽器補發之相容滑鼠事件不起手

    拖曳於放開時結束(於項目上放開則發drop,放回原項目亦發);touchcancel、視窗失焦、群組停用時取消拖曳(不發drop)。拖曳中來源元素解除且未重新綁定者取消;先解除再立即重新綁定者(例如宿主重繪時重建)拖曳延續,後續事件送達新實例。結束時若正經過某項目而未於其上放下,對其發leave,使enter必以leave或drop收尾

    拖曳中阻止本元素起手之瀏覽器原生拖放(如項目內之文字、圖片、連結)與觸控捲動,未拖曳時不干涉;監聽器拋錯時拖曳狀態已先清除

    解除(unbind或clear)後本元素不再為起點或落點、不再收到事件,並於下一個microtask移除綁定時設定之屬性(同元素已由其他實例重新綁定時不動),可重複呼叫;群組最後一個元素解除後(同一輪內未再加入)群組刪除

    各元素之選項各自有效:預覽樣式與延遲取起手元素者,落點之順序指標取落點元素者

    Unit Test: Github

Source:
Example
need test in browser

//監聽dom
let dd = domDrag(document.querySelector('#id'), { group:'group', attIndex: 'dragindex' })

//change
dd.on('change', (msg) => {
    console.log('change', msg)
})
dd.on('start', (msg) => {
    console.log('start', msg)
})
dd.on('move', (msg) => {
    console.log('move', msg)
})
dd.on('enter', (msg) => {
    console.log('enter', msg)
})
dd.on('leave', (msg) => {
    console.log('leave', msg)
})
dd.on('drop', (msg) => {
    console.log('drop', msg)
})

//釋放監聽
dd.clear()
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入元素

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
attIdentify String <optional>
'dragid'

輸入預覽元素唯一識別用之屬性名稱字串,預設'dragid'

attIndex String <optional>
'dragindex'

輸入預覽元素順序指標之屬性名稱字串,預設'dragindex'

attGroup String <optional>
'draggroup'

輸入預覽元素群組之屬性名稱字串,預設'draggroup'

group String <optional>
'group'

輸入預覽元素群組字串,預設'group'

timeDragStartDelay Number <optional>
120

輸入預覽元素由點擊後延遲出現的時間數字,單位為毫秒ms,須為數字(可為數字字串),負數視為0,超過計時器上限(含Infinity)者夾至上限,無效時用預設,預設120。使用pointerEvents會導致游標樣式失效,故延遲顯示可用來讓點擊事件穿透

previewOpacity Number <optional>
0.4

輸入預覽元素透明度數字,須為有限數字(可為數字字串),無效時用預設,預設0.4

previewDisabledOpacity Number <optional>
0.2

輸入無效時(位於非可拖曳元素內)預覽元素透明度數字,須為有限數字(可為數字字串),無效時用預設,預設0.2

previewBackground String <optional>
'white'

輸入預覽元素背景顏色字串,預設'white'

previewBorderWidth Number <optional>
1

輸入預覽元素邊框寬度數字,須為有限數字(可為數字字串),無效時用預設,預設1

previewBorderColor String <optional>
'#f26'

輸入預覽元素邊框顏色字串,預設'#f26'

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;一次拖曳之事件皆由起手元素之回調通知(落點元素之回調不通知);type為'start'時為起手(含ind為起點順序指標、touch為布林代表觸控起手;先於start事件之派發通知),'timer'時為延遲建立預覽之計時器(含phase與ms;phase為'set'代表已排定、'fire'代表到點且確認仍屬本次拖曳而將建立預覽、'cancel'代表拖曳於到點前結束而取消;ms為timeDragStartDelay解析後之毫秒數),'preview'時為domDrag對預覽之建立或移除(含phase;phase為'create'代表已建立預覽、'remove'代表已移除預覽),'end'時為拖曳已結束(含ind為起點順序指標、reason;reason為'drop'代表放開於項目上、'release'代表放開於項目外、'buttons'代表移動時主鍵已放開、'touchcancel'代表起手手指被取消、'blur'代表視窗失焦、'inactive'代表群組被停用、'source-gone'代表來源元素解除且未重建、'takeover'代表新的按下接手前一次未收尾之拖曳、'teardown'代表群組刪除時尚有拖曳;通知當下計時器、會話監聽與預覽皆已清除,且先於leave與drop之派發),'unbind'時為本實例已解除(含drag為布林,代表解除當下是否有以本元素為來源之進行中拖曳;其後該拖曳之事件仍通知至結束,但不再起手新的拖曳,重複呼叫不通知);內部建立之預覽(domDragPreview)不轉傳回調;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響拖曳,預設null

Returns:

回傳物件,可使用on、unbind、clear與setIsActive函數,on可監聽change、start、move、enter、leave、drop事件,unbind與clear相同,為釋放監聽,setIsActive為設定群組是否可拖曳(群組層級,停用時取消進行中之拖曳,解除後呼叫無作用)

Type
Object

(static) domDragBarAndScroll(panel, bar, optopt) → {Object}

Description:
  • 前端針對指定panel與bar的DOM元素監聽滾輪與拖曳bar事件

    指標之按下、拖曳與放開全部交由domDragPointer(Pointer Events單一模型,滑鼠、觸控、觸控筆同一套規則)處理,本函數只負責通道判定、座標語義與滾輪。同時只服務一條通道(bar、panel觸控),pressBar與freeBar恆成對,dragBar只出現於兩者之間。原語同時掛於bar與panel(bar位於panel之內時冒泡上來之同一事件只處理一次,bar不在panel內亦可運作);通道於按下當下依target決定並記住:bar內起手者為bar通道(任何指標),其餘為panel觸控通道(僅觸控與觸控筆,滑鼠不進入,且需useTouchDragForPanel)。bar通道發絕對座標,panel觸控通道發經比例縮放之相對位移,兩者語義不同

    滑鼠僅主鍵可起手;拖曳中接觸位元消失即解鎖(拖出瀏覽器視窗外放開者回到視窗內移動時自癒),已按住主鍵再按下或放開其他鍵不影響拖曳(但會多發座標相同之dragBar)。已上鎖時滑鼠再按下主鍵視為接手,先替既有之鎖補發freeBar再發pressBar。觸控以起手指之pointerId鎖定,其餘手指之落下、移動與放開皆不影響本次拖曳,手勢被系統中斷(pointercancel,如瀏覽器接手捲動、拖曳中啟動原生拖放)時只解鎖不定值;起手元素於拖曳途中被移出DOM時瀏覽器會改派事件給指標下之元素,故不會殘鎖,殘鎖之復原規則見domDragPointer

    呼叫端須自備樣式:bar需user-select:none避免拖曳時選取文字,觸控場景另需touch-action:none避免瀏覽器接手捲動(此為契約);未設者由本函數於bar通道上鎖時取消bar之touchmove保住手勢,此為Chromium實測可行之退路而非跨瀏覽器保證。panel觸控通道覆蓋呼叫端之任意內容,不保證不選字;panel通道未以stopTouchDragPropagationForPanel擋下捲動時,瀏覽器接手捲動即以pointercancel結束該次拖曳(touch事件模型下捲動與拖曳會並行)。觸控或觸控筆拖曳中另抑制bar與panel之contextmenu

    已知限制:同一panel掛兩個實例時一次滾輪會各發一次scrollPanel;拖曳中呼叫clear只停止而不補發freeBar;和弦按鍵之限制與瀏覽器支援範圍見domDragPointer

    Unit Test: Github

Source:
Example
need test in browser

//監聽dom
let divPanel = document.querySelector('#id_panel')
let divBar = document.querySelector('#id_bar')
let getHeighRatio = () => 0.9
let das = domDragBarAndScroll(divPanel, divBar, { getHeighRatio, stopScrollPropagationForPanel: true, stopTouchDragPropagationForPanel: true })
das.on('scrollPanel', () => {})
das.on('pressBar', () => {})
das.on('dragBar', () => {})
das.on('freeBar', () => {})

//釋放監聽
das.clear()
Parameters:
Name Type Attributes Default Description
panel HTMLElement

輸入panel元素

bar HTMLElement

輸入bar元素

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
getHeighRatio function <optional>
()=>1

輸入取得高度比例函數,因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前heighRatio,預設()=>1

getWidthRatio function <optional>
()=>1

輸入取得寬度比例函數,因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前widthRatio,預設()=>1

stopScrollPropagationForPanel Boolean <optional>
false

輸入是否停用滑鼠捲動事件向上傳遞布林值,預設false

stopTouchDragPropagationForPanel Boolean <optional>
false

輸入是否停用手機拖曳事件向上傳遞布林值,預設false

useTouchDragForPanel Boolean <optional>
true

輸入是否使用手機拖曳事件布林值,預設true

Returns:

回傳物件,可使用on與clear函數,on可監聽scrollPanel、pressBar、dragBar、freeBar事件,clear為釋放監聽。scrollPanel帶{ratioY,ratioX}為該軸之滾動方向(1、-1或0),pressBar與dragBar帶{clientY,clientX,pointerType},pointerType為'mouse'、'touch'或'pen',freeBar不帶參數

Type
Object

(static) domDragPointer(ele, optopt) → {Object}

Description:
  • 前端針對DOM元素監聽指標(滑鼠、觸控、觸控筆)之按下、拖曳與放開

    以Pointer Events單一模型處理滑鼠、觸控與觸控筆,不另掛mouse與touch事件;本函數追蹤的是指標之按下→拖曳→放開,與HTML Drag and Drop無關。pointerdown掛於元素(可給多個元素,子孫冒泡亦算,同一事件經冒泡送達多個元素時只處理一次),pointermove、pointerup與pointercancel掛於window之capture階段,故指標移出元素或瀏覽器視窗仍持續追蹤,內層元素stopPropagation亦不會漏掉放開。同時只服務一個指標,press與free恆成對,drag只出現於兩者之間;狀態一律先於emit異動,監聽器拋錯時不會留下半套狀態

    滑鼠僅主鍵可起手,觸控筆之筆尖與橡皮擦端可起手、按住筆身側鍵接觸(相當於右鍵)不可,合成事件無button屬性者維持可起手。拖曳中以buttons判定接觸是否仍在:接觸位元(筆尖或主鍵為1、橡皮擦端為32)消失即解鎖,故拖出瀏覽器視窗外放開而收不到pointerup者,回到視窗內移動時自我解鎖;已按住主鍵再按下或放開其他鍵不影響拖曳(Pointer Events對和弦按鍵只發pointermove,故會多發座標相同之drag);以物件模擬之事件未帶buttons者視為仍按住,以new PointerEvent合成時須自行給buttons,其預設值0會被視為已放開。已上鎖時滑鼠再按下主鍵視為接手,先替既有之鎖補發free再發press;滑鼠之鎖不被觸控或觸控筆接手,觸控與觸控筆亦不互相接手

    觸控以起手指之pointerId與pointerType鎖定,其餘手指之落下、移動與放開皆不影響本次拖曳;手勢被系統中斷(pointercancel,如瀏覽器接手捲動、Android長按選單、拖曳中啟動原生拖放)時只解鎖不定值。起手元素於拖曳途中被移出DOM時,瀏覽器會把後續之pointer事件改派給指標下之元素,window仍收得到,故不會殘鎖(touch事件模型則會)。若仍遺失放開事件,下一次同型別之pointerdown其isPrimary為true(isPrimary依pointerType各自計算)即代表落下時無其他同型指標在場、原指標早已離開,視為殘鎖而先補發free再重新上鎖;isPrimary為false者為他指落下,不得重新定錨;未帶isPrimary之合成事件無從證明有效,一律視為殘鎖。Pointer Events無「當前全部觸點」清單可查,放開事件遺失後若他指先落於元素外、再有手指落於元素(isPrimary為false),本函數無從判定殘鎖,須待滑鼠接手;此情形於window收取事件之模型下僅合成事件可達

    呼叫端須自備樣式:元素需user-select:none避免拖曳時選取文字,touch-action:none避免瀏覽器接手捲動(此為契約,Pointer Events規範上無法取消原生捲動),-webkit-touch-callout:none避免iOS長按選單。元素未設touch-action者,由本函數於觸控或觸控筆上鎖時取消eleTouchMove之touchmove(非被動監聽)保住手勢,見cancelTouchMove,此為Chromium實測可行之退路而非跨瀏覽器保證;觸控或觸控筆上鎖時另抑制元素之contextmenu(僅非滑鼠來源者),避免Android長按選單以pointercancel中斷手勢,滑鼠右鍵選單不受影響

    本函數不對pointerdown與touchstart呼叫preventDefault亦不stopPropagation,故相容之mousedown、mouseup與click照常發出(輕點後瀏覽器仍產生click),內層元素可自行stopPropagation pointerdown以排除自己。pointerType未知(合成事件或瀏覽器無法判定裝置)者視為滑鼠

    已知限制:拖曳中呼叫clear只停止而不補發free;clear只移除DOM監聽,不移除以on掛上之監聽器;已按住其他鍵時再按下主鍵,瀏覽器只發pointermove不發pointerdown,故無法起手;需瀏覽器支援Pointer Events(Safari與iOS 13以上、Chrome 55以上、Firefox 59以上、IE11),不支援者收不到事件亦不拋錯

    Unit Test: Github

Source:
Example
need test in browser

//監聽dom
let dp = domDragPointer(document.querySelector('#id'), { canPress: () => editable })
dp.on('press', (msg) => {})
dp.on('drag', (msg) => {})
dp.on('free', (msg) => {})

//釋放監聽
dp.clear()
Parameters:
Name Type Attributes Default Description
ele HTMLElement | Array

輸入元素或元素陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
canPress function <optional>
()=>true

輸入是否可按下之判斷函數,於每次pointerdown時(含已上鎖時)呼叫一次並帶入事件,回傳false即忽略該次按下,既不上鎖亦不影響既有之鎖,供呼叫端判斷可編輯狀態或排除特定元素,須為純判斷且拖曳中不重新評估,拋錯時不上鎖且錯誤外拋,預設()=>true

cancelTouchMove Boolean | function <optional>
true

輸入是否於觸控或觸控筆上鎖時取消eleTouchMove之touchmove事件布林值或函數,函數時帶入事件並回傳布林值,為false時不掛touchmove監聽,預設true

eleTouchMove HTMLElement | Array <optional>
ele

輸入掛touchmove監聽之元素或元素陣列,預設為ele;把手位於大面板內時可指定把手,使非被動監聽不掛於整個面板而拖慢其內容之觸控捲動,預設ele

Returns:

回傳物件,可使用on與clear函數,on可監聽press、drag、free事件,clear為釋放監聽。press與drag帶{clientX,clientY,pointerType,event},free帶{pointerType,reason,event},pointerType為'mouse'、'touch'或'pen',reason為'up'(放開)、'cancel'(被系統取消)、'buttonsLost'(接觸位元消失之自癒)、'takeover'(被新按下接手)之一,event為觸發之原始事件

Type
Object

(static) domDragPreview(optopt) → {Object}

Description:
  • 前端針對DOM元素拖曳時產生其預覽(拷貝)對象

    同一時間只有一個預覽,createPreview時若已有預覽則先移除;預覽存在期間才監聽視窗之mousemove、touchmove(capture,內層元素stopPropagation仍跟隨指標)並定期確認來源元素仍在頁面中,來源被移出(例如可自我刪除之按鈕)時一併移除預覽;removeDragPreview與clear皆移除預覽並停止上述監聽與確認,可重複呼叫

    預覽為來源元素之深拷貝:拷貝內之radio移除name,避免已勾選之拷貝插入頁面後取消原件之勾選

    Unit Test: Github

Source:
Example
need test in browser

let pv = domDragPreview({
    attIdentify,
    containerOpacity: previewOpacity,
    containerBackground: previewBackground,
    containerBorderWidth: previewBorderWidth,
    containerBorderColor: previewBorderColor,
})
pv.createPreview()
pv.removeDragPreview()
pv.clear()
Parameters:
Name Type Attributes Default Description
opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
attIdentify String <optional>
'dragprevid'

輸入儲存於DOM的識別欄位名稱字串,預覽之拷貝會清除此欄位使其不與來源元素重複,預設'dragprevid'

containerOpacity Number <optional>
0.4

輸入預覽元素透明度數字,須為有限數字(可為數字字串),無效時用預設,預設0.4

containerBackground String <optional>
'white'

輸入預覽元素背景顏色字串,預設'white'

containerBorderWidth Number <optional>
1

輸入預覽元素邊框寬度數字,須為有限數字(可為數字字串),無效時用預設,預設1

containerBorderColor String <optional>
'#f26'

輸入預覽元素邊框顏色字串,預設'#f26'

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'create'時為createPreview已建立預覽並完成監聽、定位與確認來源之計時器排定(含replaced為布林,代表建立前已有預覽而被替換),'skip'時為createPreview無法建立預覽(含reason;reason為'no-box'代表無法取得來源位置、'build-error'代表建構預覽時拋錯如來源按鈕已自我刪除),'tick'時為每500毫秒確認來源元素是否仍在頁面中之一次判定(含connected為布林,false時緊接著通知reason為'source-gone'之remove),'remove'時為確有預覽而已被移除並已停止監聽與計時器(含reason;reason為'remove'代表removeDragPreview、'clear'代表clear、'replace'代表createPreview替換既有預覽、'source-gone'代表來源已被移出頁面;無預覽時重複呼叫不通知);事件皆於對應動作完成後通知(通知當下預覽、監聽與計時器之狀態已一致);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響預覽,預設null

Returns:

回傳物件,內含createPreview、updateDragPreview、pauseDragPreview、removeDragPreview、setNodeStyle、setCoverStyle、setSellStyle、setContainerStyle、clear事件,clear同removeDragPreview

Type
Object

(static) domDropFiles(ele, optopt) → {Object}

Description:
  • 前端取得拖曳進指定元素的檔案陣列

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
let ev = domDropFiles(ele)
ev.on('getFiles', ({ files, filesTree, entries, cb }) => {
    console.log(files, filesTree, entries)
    cb()
})
ev.on('dropIn', () => {
    console.log('dropIn')
})
ev.on('dropOut', () => {
    console.log('dropOut')
})
ev.on('error', (err) => {
    console.log('error', err)
})
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入dom元素

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'in'時為收到dragenter、dragover或drop而即將依狀態決定是否派發dropIn(含emit為是否為自上次移出後首次移入而即將派發dropIn),'out'時為收到dragleave或呼叫getFiles之cb而暫視為移出並排定100ms後判定(含by為'dragleave'或'cb'、ms固定為100),'leave'時為100ms後之判定(含emit為期間未再移入而即將派發dropOut,false代表期間又收到in而不派發),'drop'時為收到drop事件而開始讀取拖曳內容,'files'時為拖曳內容讀取完成而即將派發getFiles(含files為dataTransfer.files中有類型者之數量、filesTree為遍歷資料夾後之檔案數量、entries為遍歷後之檔案與資料夾項目總數),'error'時為讀取失敗或getFiles之監聽器拋錯而即將派發error,'off'時為呼叫off;事件皆於派發對應事件之前通知;呼叫off之後不再通知(含off前已排定之100ms判定與讀取中者,其對ev之派發維持原行為);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響拖曳處理,預設null

Returns:

回傳物件,包含emit與off事件,emit可監聽dropIn、dropOut、getFiles,通過監聽getFiles即可取得拖曳進指定元素的檔案陣列,並呼叫其內回調函數cb,方能繼續觸發dropOut事件

Type
Object

(static) domElementsFromPoint(clientX, clientY) → {Array}

Description:
  • 前端針對DOM元素事件內座標(clientX, clientY)取得DOM元素陣列

    Unit Test: Github

Source:
Example
need test in browser

window.addEventListener('mousemove', (e) => {
  console.log(domElementsFromPoint(e.clientX, e.clientY))
})
Parameters:
Name Type Description
clientX Number

輸入指定x座標浮點數,須基於顯示區(viewport)之x座標

clientY Number

輸入指定y座標浮點數,須基於顯示區(viewport)之x座標

Returns:

回傳DOM元素陣列

Type
Array

(static) domFadeIn(ele, optopt)

Description:
  • 前端針對DOM元素執行淡入效果

    Unit Test: Github

Source:
Example
need test in browser

domFadeIn(document.querySelector('#id'))

domFadeIn(document.querySelector('#id'), { duration: 500, delay: 100, opacityIni: 0.2 })
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入取得dom函數

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
duration Number <optional>
500

輸入動畫持續時間毫秒整數,預設500

delay Number <optional>
0

輸入動畫延遲啟動時間毫秒整數,預設0

opacityIni Number <optional>
0

輸入初始透明度浮點數,介於0至1之間,預設0

(static) domFadeOut(ele, optopt)

Description:
  • 前端針對DOM元素執行淡出效果

    Unit Test: Github

Source:
Example
need test in browser

domFadeOut(document.querySelector('#id'))

domFadeOut(document.querySelector('#id'), { duration: 500, delay: 100, opacityIni: 0.8 })
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入取得dom函數

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
duration Number <optional>
500

輸入動畫持續時間毫秒整數,預設500

delay Number <optional>
0

輸入動畫延遲啟動時間毫秒整數,預設0

opacityIni Number <optional>
1

輸入初始透明度浮點數,介於0至1之間,預設1

(static) domFind(query) → {Element}

Description:
  • 前端找尋DOM元素

    Unit Test: Github

Source:
Example
need test in browser

let ele = domFind('#id')
Parameters:
Name Type Description
query String

輸入查詢字串

Returns:

回傳DOM元素

Type
Element

(static) domFinds(query) → {Array}

Description:
  • 前端找尋多DOM元素

    Unit Test: Github

Source:
Example
need test in browser

let eles = domFinds('[name="abc"]')
Parameters:
Name Type Description
query String

輸入查詢字串

Returns:

回傳DOM元素陣列

Type
Array

(static) domGetAttr(ele, attr) → {String}

Description:
  • 前端針對DOM元素取得屬性

    Unit Test: Github

Source:
Example
need test in browser

//<div id="eid" cus_attr="abc"></div>
let r = domGetAttr(document.querySelector('#eid'), 'cus_attr')
conssole.log(r)
// => abc
Parameters:
Name Type Description
ele HTMLElement

輸入DOM元素

attr String

輸入欲取得屬性名稱字串

Returns:

回傳屬性值

Type
String

(static) domGetBoudRect(ele) → {Object}

Description:
  • 前端針對DOM元素取得getBoundingClientRect

    Unit Test: Github

Source:
Example
need test in browser

let r = domGetBoudRect(document.querySelector('#id'))
console.log(r)
// => {
  x:...,
  y:...,
  width:...,
  height:...,
  top:...,
  right:...,
  bottom:...,
  left:...,
}
Parameters:
Name Type Description
ele HTMLElement

輸入DOM元素

Returns:

回傳getBoundingClientRect結果物件

Type
Object

(static) domGetBoudRectRefSelf(p, ele) → {Object}

Description:
  • 前端由事件clientX與clientY座標,計算於DOM元素內的左上角座標x與y,以及該元素的寬度w與高度h

    Unit Test: Github

Source:
Example
need test in browser

let r = domGetBoudRectRefSelf(document.querySelector('#id'))
console.log(r)
// => {
  x:...,
  y:...,
  w:...,
  h:...,
}
Parameters:
Name Type Description
p Object

輸入座標點物件,主要需有DOM事件(event)的clientX與clientY,此為相對可視區域左上角的座標,單位皆為px

ele HTMLElement

輸入DOM元素

Returns:

回傳物件,內含x、y、寬度w、高度h,單位皆為px

Type
Object

(static) domGetFileAccept(kindopt) → {String}

Description:
  • 前端依照檔案類型或種類回傳input file的accept欄位所用字串 kind若需使用種類,可選為'docums', 'compress', 'image', 'data',或複選以陣列儲存傳入 kind若需使用通用檔案,可使用'common'

    Unit Test: Github

Source:
Example
need test in browser
Parameters:
Name Type Attributes Default Description
kind String | Array <optional>
'*'

輸入檔案類型或種類字串或陣列

Returns:

回傳input file的accept欄位所用字串

Type
String

(static) domGetFiles(ele, sizeMbLimitopt) → {Array}

Description:
  • 前端回傳Input元素所給予的檔案

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
let r = domGetFiles(ele)
Parameters:
Name Type Attributes Default Description
ele Element

輸入Element Input元素

sizeMbLimit Number <optional>
1000

輸入檔案大小上線,單位mb,預設為1000mb(約1g)

Returns:

回傳檔案陣列

Type
Array

(static) domGetParents(ele) → {Array}

Description:
  • 前端找尋DOM元素的所有父層

    Unit Test: Github

Source:
Example
need test in browser

let eleParents = domGetParents(document.querySelector('#id'))
Parameters:
Name Type Description
ele HTMLElement

輸入dom元素

Returns:

回傳DOM元素陣列

Type
Array

(static) domGetPointFromEvent(e) → {Object}

Description:
  • 前端針對DOM元素事件取得使用者滑鼠或第一觸控點座標

    Unit Test: Github

Source:
Example
need test in browser

window.addEventListener('mousemove', (e) => {
  console.log(domGetPointFromEvent(e))
})
Parameters:
Name Type Description
e HTEMLEvent

輸入dom事件物件

Returns:

回傳座標物件,其內有clientX、clientY、pageX、pageY,clientX、clientY代表基於顯示區(viewport)之x與y座標,pageX、pageY代表基於內容區(page)之x與y座標

Type
Object

(static) domGetScrollableParent(ele) → {Element}

Description:
  • 前端找尋DOM元素最近的可捲動父層

    Unit Test: Github

Source:
Example
need test in browser

let eleScrollableParent = domGetScrollableParent(document.querySelector('#id'))
Parameters:
Name Type Description
ele HTMLElement

輸入dom元素

Returns:

回傳DOM元素

Type
Element

(static) domGetWindowSize() → {Object}

Description:
  • 前端取得window內實際顯示寬度與高度

    Unit Test: Github

Source:
Example
need test in browser

let r = domGetWindowSize()
console.log(r)
// => {
  width:...,
  height:...,
}
Returns:

回傳window內實際顯示寬度與高度物件

Type
Object

(static) domIsClientXYIn(clientX, clientY, ele) → {Boolean}

Description:
  • 前端計算clientX與clientY是否位於DOM元素內

    Unit Test: Github

Source:
Example
need test in browser

let p = {
    clientX: 'from event',
    clientY: 'from event',
}
let ele = document.querySelector('#id')
let b = domIsClientXYIn(p.clientX, p.clientY, ele)
// => true or false
Parameters:
Name Type Description
clientX Number

輸入點的clientX座標

clientY Number

輸入點的clientY座標

ele HTMLElement

輸入元素

Returns:

回傳是否位於DOM元素內布林值

Type
Boolean

(static) domIsFocused(ele)

Description:
  • 前端檢測DOM元素是否為聚焦狀態

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
console.log(domIsFocused(ele))
// => true or false
Parameters:
Name Type Description
ele Element

輸入Element元素

(static) domIsRendered(ele) → {Boolean}

Description:
  • 前端檢測DOM元素當下是否被繪製:在頁面中,且自身與祖先皆非display:none

    有checkVisibility時以其判定(另涵蓋content-visibility:hidden等display以外之不被繪製),否則或其拋錯時逐層檢查display,Shadow DOM內之頂層節點改查其宿主

    只計算樣式而不讀外框(不強制重排版),故被繪製而尺寸為0者亦為true;未被繪製之元素量得之尺寸皆為0,可供量測前確認,避免把0寫入須持久化之狀態(如列高、內容高度)

    非元素(null、文字節點、document等)一律為false;因供頻繁呼叫之偵測使用,以nodeType判定是否為元素而不經isEle

    無checkVisibility之瀏覽器(逐層檢查display)不涵蓋:content-visibility:hidden之子孫、display:contents之元素自身、未分配至slot之宿主子元素、所在slot被隱藏、關閉之details內容,以上皆判為true

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
console.log(domIsRendered(ele))
// => true or false
Parameters:
Name Type Description
ele Element

輸入元素

Returns:

回傳是否被繪製布林值

Type
Boolean

(static) domIsStable(ele, optopt) → {Promise|Object}

Description:
  • 前端偵測DOM元素(及其子元素)是否已穩定

    一輪偵測: 相隔timeDiff取兩次位置與尺寸(getBoundingClientRect,含transform位移),並取子樹內進行中之動畫(getAnimations);等該批動畫結束或取消後再取一次位置與動畫數,整輪期間位置皆未變且從頭到尾皆無動畫才視為穩定,期間曾有動畫或位移即回報不穩定,由下一輪再判。動畫只計playState為running且iterations有限者:已結束但因fill:forwards仍被回傳之動畫不計,否則子元素做完進場動畫後父元素會永遠不穩定;無限循環動畫(如spinner)永不結束,不計亦不等待,否則整輪永不落定

    已知限制:等待中之動畫若被暫停(未取消亦未結束),該輪於其恢復或取消前不結束;未被繪製(未插入或display:none)之元素位置恆為0,判為穩定,須先確認被繪製者可用domIsRendered

    元素無效或無getBoundingClientRect時:promise模式回傳被拒絕之Promise(原因為'invalid element'或'invalid element.getBoundingClientRect');event模式仍回傳可使用create、on、dispose之物件但永不觸發,其error為原因,可偵測時error為null

    event模式之create於microtask啟動第一輪(晚於本次同步流程,故create後才插入之元素亦從插入後量起),重複呼叫無效,dispose後呼叫亦無效;stable之監聽器拋錯時有error監聽者則發出error事件,否則console.error,不中斷偵測

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')

//promise, 一次性偵測
let b = await domIsStable(ele)
console.log(b)
// => true or false

//event, 持續偵測
let ev = domIsStable(ele, { mode: 'event' })
ev.on('stable', (b) => {
    console.log('stable', b)
})
ev.create()
//...
ev.dispose()
Parameters:
Name Type Attributes Default Description
ele Element

輸入Element元素

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
mode String <optional>
'promise'

輸入模式字串,可使用'promise'與'event','promise'代表一次性偵測並回傳Promise(resolve回傳該輪是否穩定布林值),'event'代表持續性偵測並回傳EventEmitter,啟動時立即開始第一輪,之後每timeDetect啟動一輪(前一輪未完不重疊),穩定狀態改變時emit('stable', bool),初始視為不穩定故第一次穩定會emit true,預設'promise'

tolerance Number <optional>
0

輸入位置容許誤差數字,單位px,須為不小於0之數字(可為數字字串,Infinity代表任何變化皆容許),無效時用預設,預設0表示須完全相同

timeDiff Number <optional>
100

輸入位置前後比對之時間差數字,單位ms,須為數字(可為數字字串),0為立即比對,負數視為0,超過計時器上限(含Infinity)者夾至上限,無效時用預設,預設100

timeDetect Number <optional>
50

輸入event模式輪詢偵測間隔數字,單位ms,須為大於0之數字(可為數字字串),超過計時器上限(含Infinity)者夾至上限,無效時用預設,預設50

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;每輪以seq(自1起之輪序號,promise模式恆為1)對應:'round'(含seq)為一輪開始(取第一次位置前),'rect'(含seq、at、x、y、w、h)為第at次(1至3)位置取樣之結果,'anims'(含seq、at、n)為第at次(1至2)取得進行中且會結束之動畫數n,'judge'(含seq、stable、moved)為該輪判定結果(stable為是否穩定,moved為三次位置是否超出tolerance),'fail'(含seq)為量測拋錯而該輪失敗(promise模式隨後reject);event模式另有'skip'(含seq、reason)為輪詢時前一輪(seq)未完而略過(reason恆為'busy'),'emit'(含seq、stable)為穩定狀態改變而即將emit('stable'),'done'(含seq)為該輪(含emit之派發)已結束而可啟動下一輪,'dispose'(含seq,為已啟動之最後一輪序號)為釋放,釋放後不再通知(進行中之一輪亦然);promise模式於judge或fail之後才resolve或reject;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響偵測,預設null

Returns:

回傳物件,給予'promise'時回傳Promise(resolve回傳當前是否穩定布林值,reject回傳錯誤訊息),給予'event'時回傳EventEmitter,可使用create、on、dispose函數與error屬性,dispose回傳true

Type
Promise | Object

(static) domIsVisible(ele, optopt) → {Promise|Object}

Description:
  • 前端檢測DOM元素是否為顯示(使用者可見)狀態

    以IntersectionObserver判定元素是否與視窗可視區相交;同一次回報含多筆狀態時以時間最新者為準

    元素無效或瀏覽器不支援IntersectionObserver時:promise模式回傳被拒絕之Promise(原因為'invalid element'或'invalid IntersectionObserver');event模式仍回傳可使用create、on、dispose之物件但永不觸發,其error為原因,可偵測時error為null

    event模式之create於microtask啟動觀察(晚於本次同步流程,故create後才插入之元素、create後才掛之監聽皆可),重複呼叫無效,dispose後呼叫亦無效;觀察器建立失敗時不拋錯,error為所拋之值;visible之監聽器拋錯時有error監聽者則發出error事件,否則console.error,不中斷偵測

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')

domIsVisible(ele, { mode: 'promise' })
    .then(function(visible){
        console.log(visible)
        // => true or false
    })
    .catch(function(err){
        console.log(err)
    })

let ev = domIsVisible(ele, { mode: 'event' })
ev.create()
ev.on('visible',(visible) => {
    console.log(visible)
    // => true or false
})
// ev.dispose()
Parameters:
Name Type Attributes Default Description
ele Element

輸入Element元素

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
mode String <optional>
'promise'

輸入模式字串,可使用'promise'與'event',給予'promise'代表一次性偵測並回傳Promise,給予'event'代表持續性偵測並回傳EventEmitter,預設'promise'

Returns:

回傳物件,給予'promise'時回傳Promise,resolve回傳顯示與否布林值,reject回傳錯誤訊息,給予'event'時回傳物件,可使用create、on、dispose函數與error屬性,create代表開啟偵測,on代表監聽'visible'事件可得元素顯隱變化,dispose代表中止偵測並回傳true

Type
Promise | Object

(static) domPrepend(eleParent, itemChild)

Description:
  • 前端於DOM元素內最前處插入元素或Html文字等

    Unit Test: Github

Source:
Example
need test in browser

domPrepend(document.querySelector('#id-parent'),document.querySelector('#id-child'))
Parameters:
Name Type Description
eleParent HTMLElement

輸入目標DOM元素

itemChild HTMLElement | String

輸入要插入DOM元素或Html文字等內容

(static) domRemove(inp)

Description:
  • 前端找尋DOM元素並刪除

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
domRemove(ele)
Parameters:
Name Type Description
inp String | HTMLElement

輸入查詢字串或dom函數

(static) domRenderMarkdownMind(ele, markdown, optopt) → {Object}

Description:
  • 前端針對指定DOM上展示由markdown轉出的心智圖

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#markmap')

let markdown=`
# markmap

## Links

- <https://markmap.js.org/>
- [GitHub](https://github.com/gera2ld/markmap)

## Related

- [coc-markmap](https://github.com/gera2ld/coc-markmap)
- [gatsby-remark-markmap](https://github.com/gera2ld/gatsby-remark-markmap)

## Features

- links
- **inline** ~~text~~ *styles*
- multiline
  text

`
let r = domRenderMarkdownMind(ele, markdown)
console.log(r)
// => { root, features, styles, scripts }
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入元素

markdown String

輸入markdown字串

opt Object <optional>
{}

輸入設定物件,預設{}

Returns:

回傳Markmap.create後物件vmm,並於其內添加transMarkdown函數,可針對變更後之markdown重新計算取得結果內屬性root資訊,並再使用vmm.setData(root)可達到差異更新功能

Type
Object

(async, static) domRenderMarkdownMindDyn(ele, markdown, optopt, pathItems) → {Promise}

Description:
  • 前端針對指定DOM上展示由markdown轉出的心智圖,採用動態加載技術

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#markmap')

let markdown=`
# markmap

## Links

- <https://markmap.js.org/>
- [GitHub](https://github.com/gera2ld/markmap)

## Related

- [coc-markmap](https://github.com/gera2ld/coc-markmap)
- [gatsby-remark-markmap](https://github.com/gera2ld/gatsby-remark-markmap)

## Features

- links
- **inline** ~~text~~ *styles*
- multiline
  text

`
domRenderMarkdownMindDyn(ele, markdown)
    .then((res)=>{
        // => { root, features, styles, scripts }
    })
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入元素

markdown String

輸入markdown字串

opt Object <optional>
{}

輸入設定物件,預設{}

pathItems String | Object | Array

輸入資源字串、字串陣列、物件、物件陣列

Returns:

回傳Promise,resolve回傳domRenderMarkdownMind產生結果,reject回傳錯誤訊息

Type
Promise

(static) domRipple(ele, event)

Description:
  • 前端針對DOM元素設定點擊Ripple效果

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
ele.addEventListener('click', (e)=>{
    domRipple(e.currentTarget,e)
})
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入dom

event Object

輸入dom點擊事件

opt.timeDuration Integer <optional>
1000

輸入Ripple效果持續時間整數,單位為毫秒ms,預設1000

opt.color String <optional>
'rgba(255, 255, 255, 0.5)'

輸入Ripple顏色字串,預設'rgba(255, 255, 255, 0.5)'

(static) domScrollIntoView(ele, optopt)

Description:
  • 前端平滑捲動至元素,讓指定元素出現在可視範圍中,為自動捲動父容器

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
domScrollIntoView(ele, {})
Parameters:
Name Type Attributes Default Description
ele Element

輸入Element元素

opt Object <optional>
{behavior:'smooth'}

輸入scrollIntoView的設定物件,預設{behavior:'smooth'}

(static) domScrollTo(ele, optopt)

Description:
  • 前端平滑捲動至元素,讓捲動容器元素,捲動到特定座標(left或top)位置

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
domScrollTo(ele, { left: 0, top: 100 })
Parameters:
Name Type Attributes Default Description
ele Element

輸入Element元素

opt Object <optional>
{behavior:'smooth'}

輸入scrollTo的設定物件,預設{behavior:'smooth'}

(static) domSetAttr(ele, attr, val) → {String}

Description:
  • 前端針對DOM元素給予屬性值

    Unit Test: Github

Source:
Example
need test in browser

//<div id="eid" cus_attr="abc"></div>
domSetAttr(document.querySelector('#eid'), 'cus_attr', 'def')
Parameters:
Name Type Description
ele HTMLElement

輸入DOM元素

attr String

輸入欲給予屬性名稱字串

val String | Number

輸入值字串或數字

Returns:

回傳屬性值

Type
String

(async, static) domShowImages(eleImg, eleGroupopt, optopt) → {Promise}

Description:
  • 前端呼叫viewer.js顯示指定元素內圖片或圖片陣列

    Unit Test: Github

Source:
Example
need test in browser

<img src="001.jpg" onclick="domShowImages(this)">
<img src="002.jpg" onclick="domShowImages(this,this.parentElement)">
Parameters:
Name Type Attributes Default Description
eleImg HTMLElement

輸入圖片元素

eleGroup HTMLElement <optional>
null

輸入元素內含有多圖片元素,預設null

opt Object <optional>
{}

輸入viewerjs設定物件,預設使用optOne或optMuti,若img僅一個則使用optOne,反之使用optMuti

Properties
Name Type Attributes Default Description
disTouchMoveToHide Integer <optional>
4

輸入手機touch觸發移動距離視為可隱藏整數,單位px,預設4

Returns:

回傳Promise,resolve回傳close訊息,reject回傳錯誤訊息

Type
Promise

(async, static) domShowImagesDyn(eleImg, eleGroupopt, optopt, pathItemsopt) → {Promise}

Description:
  • 前端彈窗顯示指定元素內圖片或圖片陣列

    本函數為相容舊版之保留函數:舊版採動態加載CDN之viewerjs(js與css),現已將viewerjs直接打包進wsemi(含css注入),無需再動態加載 內部直接委派domShowImages,pathItems參數保留簽名相容但已不使用

    Unit Test: Github

Source:
Example
need test in browser

<img src="001.jpg" onclick="domShowImagesDyn(this)">
<img src="002.jpg" onclick="domShowImagesDyn(this,this.parentElement)">
Parameters:
Name Type Attributes Default Description
eleImg HTMLElement

輸入圖片元素

eleGroup HTMLElement <optional>
null

輸入元素內含有多圖片元素,預設null

opt Object <optional>
{}

輸入viewerjs設定物件,預設使用optOne或optMuti,若img僅一個則使用optOne,反之使用optMuti

pathItems String | Object | Array <optional>

輸入資源字串、字串陣列、物件、物件陣列,已不使用,僅保留簽名相容

Returns:

回傳Promise,resolve回傳close訊息,reject回傳錯誤訊息

Type
Promise

(static) domShowInputAndGetFiles(optopt) → {Promise}

Description:
  • 前端開啟上傳視窗並回傳檔案

    Unit Test: Github

Source:
Example
need test in browser

domShowInputAndGetFiles()
    .then((files)=>{})
Parameters:
Name Type Attributes Default Description
opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
kind String | Array <optional>
'*'

輸入檔案類型或種類字串或陣列,可選'docums'、'compress'、'image'、'data',若給予'common'則代表前述4種,也可給予有限的副檔名(詳見getFileAccept),預設為全部'*'

multiple Boolean <optional>
false

輸入是否可選多檔案,預設為false

entireHierarchy Boolean <optional>
false

輸入是否遍歷資料夾內之資料夾與檔案,使用Chrome實驗性語法webkitdirectory,預設為false

sizeMbLimit Number <optional>
1000

輸入檔案大小上線,單位mb,預設為1000mb(約1g)

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'open'時為已建立input並綁定監聽而即將觸發點擊開啟選檔視窗(含multiple、entireHierarchy為生效後之布林值),'cancel'時為收到input之cancel事件(取消選檔之主要偵測)而即將以空檔案結束,'focus'時為收到window之focus事件(舊瀏覽器之備援偵測,entireHierarchy時不綁定)而排定1500ms後判定(含ms固定為1500),'judge'時為1500ms後之判定(含cancel為此時尚未收到change而視為取消,false代表change已先處理而不再結束),'settle'時為即將resolve(含by為'change'、'cancel'或'focus'代表由change事件、cancel事件或focus判定結束,files為檔案數量,errs為超過sizeMbLimit之檔案數量);事件皆於對應動作之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響選檔處理,預設null

Returns:

回傳Promise,resolve回傳各檔案陣列,無reject

Type
Promise

(static) domShowInputAndGetFilesU8Arrs(optopt) → {Promise}

Description:
  • 前端開啟上傳視窗並讀取各檔案Uint8Array資料陣列

    Unit Test: Github

Source:
Example
need test in browser

domShowInputAndGetFilesU8Arrs()
    .then((res)=>{})
Parameters:
Name Type Attributes Default Description
opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
kind String | Array <optional>
'*'

輸入檔案類型或種類字串或陣列,預設為全部'*'

multiple Boolean <optional>
false

輸入是否可選多檔案,預設為false

entireHierarchy Boolean <optional>
false

輸入是否遍歷資料夾內之資料夾與檔案,使用Chrome實驗性語法webkitdirectory,預設為false

sizeMbLimit Number <optional>
1000

輸入檔案大小上線,單位mb,預設為1000mb(約1g)

Returns:

回傳Promise,resolve回傳各檔案的Uint8Array資料陣列,reject回傳錯誤訊息

Type
Promise

(static) domShowInputDatatime(timeopt, optopt) → {Promise}

Description:
  • 前端開啟選擇時間視窗並回傳新時間

    Unit Test: Github

Source:
Example
need test in browser

let timePrev

timePrev = '2000-01-01T00:00:00'
domShowInputDatatime(timePrev)
    .then((timeNew)=>{
        console.log('timeNew', timeNew)
    })

timePrev = '2000-01-01'
domShowInputDatatime(timePrev, { type: 'date' })
    .then((timeNew)=>{
        console.log('timeNew', timeNew)
    })
Parameters:
Name Type Attributes Default Description
time String <optional>
''

輸入時間字串,可不提供,時間視窗預設為今日,預設為''

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
eleRef Element <optional>
null

輸入觸發點擊Element元素,若不給則使用document.activeElement取得,預設為null

type String <optional>
'datetime-local'

輸入時間類型字串,可選為'datetime-local'與'date',預設為'datetime-local'

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'mount'時為已將時間輸入框放入頁面而排定1ms後延遲彈出(含inpType為生效後之輸入框類型'datetime-local'或'date'、withValue為輸入框是否帶有time之預設值),'picker'時為延遲(1ms)後已綁定change監聽而即將彈出日曆視窗,'focusout'時為觸發元素收到focusout(取消選取之偵測)而排定50ms後觸發(含ms固定為50),'change'時為收到輸入框之change事件(含value為此次取得之值字串,取值失敗時沿用先前之值,初始為空字串),'skip'時為觸發時已有先前之觸發而略過(含by為'change'或'focusout'代表略過者之來源),'settle'時為首次觸發而即將結束(含by為'change'或'focusout'代表觸發來源、changed為是否取得變更時間、value為將resolve之新時間字串,未變更時為null且Promise不會resolve);事件皆於對應動作之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響時間選取,預設null

Returns:

回傳Promise,resolve回傳選擇新時間,無reject

Type
Promise

(static) domTooltip(ele, content, optionopt)

Description:
  • 滑鼠移入元素時彈出提示訊息

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
domTooltip(ele, '<b>Hello</b>World')
Parameters:
Name Type Attributes Default Description
ele HTMLElement

輸入DOM元素

content String

輸入提示html訊息字串,若不給則使用ele.innerHTML

option Object <optional>
{}

輸入tippy提示設定物件,預設{},設定物件詳見tippy官網

(static) domTriggerEvent(ele, eventName)

Description:
  • 前端觸發ele基於MouseEvent的eventName

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
domTriggerEvent(ele, 'click')
Parameters:
Name Type Description
ele Element

輸入Element元素

eventName String

輸入事件名稱字串

(async, static) domVirtualCreate(fun, optopt) → {Promise}

Description:
  • 前端產生臨時DOM元素為對象並進行客製化處理

    Unit Test: Github

Source:
Example
need test in browser

let fun = (ele) => {
    return ele2pic(ele) //use sync or async fun
}
let pic = await domVirtualCreate(fun, { width: 500, height: 350 })
Parameters:
Name Type Attributes Default Description
fun function

輸入處理函數,函數會傳入臨時產生的DOM元素,處理後例如產生base64圖片,並將其回傳,函數可為sync或async函數

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
width Number | String <optional>
null

輸入目標DOM元素寬度數字,單位px,預設null

height Number | String <optional>
null

輸入目標DOM元素高度數字,單位px,預設null

Returns:

回傳Promise,resolve回傳為fun處理後數據,reject回傳錯誤訊息

Type
Promise

(static) domVirtualCreateQueue() → {function}

Description:
  • 前端產生臨時DOM元素為對象並進行客製化處理,並通過佇列管控,限定一次處理一個。初始化後將回傳封裝後的domVirtualCreate,輸入與輸出皆與其相同,詳見domVirtualCreate

    Unit Test: Github

Source:
Example
need test in browser

let dpq = domVirtualCreateQueue()

//先給予圖片寬高與產製函數, 內部通過佇列逐次運行產圖與回傳base64
let fun = async (ele) => {

    //chart
    let highchartsOpt = {...} //給予highcharts設定物件
    window.Highcharts.chart(ele, highchartsOpt)

    //domConvertToPic, 預設轉出base64
    let opt = { scale: 3 } //放大3倍, 提高解析度
    b64 = await domConvertToPic(ele, opt)

    return b64
}

let b64 = await dpq(fun, { width: 500, height: 350 })
Returns:

回傳domVirtualCreate函數

Type
function

(async, static) downloadExcelFileFromData(fileName, sheetNameopt, data) → {Promise}

Description:
  • 下載資料成為Excel檔案,前後端都可用

    本函數為async function,前端觸發瀏覽器下載,後端寫入檔案(fileName可含路徑)

    Unit Test: Github

Source:
Example
let data

data = [
    ['a', 'b', 'c'],
    [1, 23.45, 'xyz']
]
await downloadExcelFileFromData('data(mat).xlsx', 'data', data)

data = [
    { x: 'a', y: 'b', z: 'c' },
    { x: 1, y: 23.45, zzz: 'xyz' },
]
await downloadExcelFileFromData('data(ltdt).xlsx', 'data', data)
Parameters:
Name Type Attributes Default Description
fileName String

輸入檔名字串,後端可含路徑

sheetName String <optional>
'data'

輸入分頁(sheet)名稱字串,預設為'data'

data Array

輸入內容陣列,可為二維陣列(mat)或由物件組成的一維陣列(ltdt)

Returns:

回傳Promise,resolve代表已觸發下載或寫檔完成,輸入無效或轉換失敗時resolve回傳{ error }物件

Type
Promise

(async, static) downloadExcelFileFromDataDyn(fileName, sheetNameopt, data, pathItemsopt) → {Promise}

Description:
  • 前端下載資料成為Excel檔案

    本函數為相容舊版之保留函數:舊版採動態加載CDN之SheetJS(xlsx),現Excel引擎已改為hucre並直接打包進wsemi,無需再動態加載 內部直接委派downloadExcelFileFromData,pathItems參數保留簽名相容但已不使用 與downloadExcelFileFromData之差異僅在錯誤處理:本函數以reject回傳錯誤訊息,非resolve回傳{ error }物件

    Unit Test: Github

Source:
Example
need test in browser

let data

data = [
    ['a', 'b', 'c'],
    [1, 23.45, 'xyz']
]
downloadExcelFileFromDataDyn('data(mat).xlsx', 'data', data)

data = [
    { x: 'a', y: 'b', z: 'c' },
    { x: 1, y: 23.45, zzz: 'xyz' },
]
downloadExcelFileFromDataDyn('data(ltdt).xlsx', 'data', data)
Parameters:
Name Type Attributes Default Description
fileName String

輸入檔名字串

sheetName String <optional>
'data'

輸入分頁(sheet)名稱字串,預設為'data'

data Array

輸入內容陣列,可為二維陣列(mat)或由物件組成的一維陣列(ltdt)

pathItems String | Object | Array <optional>

輸入資源字串、字串陣列、物件、物件陣列,已不使用,僅保留簽名相容

Returns:

回傳Promise,resolve代表成功,reject回傳錯誤訊息

Type
Promise

(static) downloadFileFromB64(fileName, b64)

Description:
  • 前端下載base64資料成為檔案

    Unit Test: Github

Source:
Example
need test in browser

//icon.png to base64
let b64='iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACGklEQVQ4T6WTzWsTQRjGZ/YrEk1oofYgglURCqHb0QrBdLfNQQTPag9V624Sowh6FLz5F3hRqBSym1TsJWDx4EVESXdjWojYTdkqilAEL1ZaMGubNrMzsoGUxqyN4Nzm/fjx8jzvC8F/Ptipfz6eOhxyN8MR4+myX60vwJDGu0M8fwi9ydmWlJgAAB6gFBdQMWf/CfEFWLJ6D1JKSICfhXVXHpzTMovSNRUI9CV6Pf1tN6QNsCSnrjA1/ArvZ3uAS5OU4klkTn/6OJwI1SC5jszsg78CluQJ0aXccWRqsxTcZ5bj33vtg6urY/m86zUtjiTOMRBUxYJWakJaJrBGlVt1R8iefje14SdYeSjNC8H6DdHQH7UBrNjVXsByFwYNfXIvZyxJvQPB1hPRnFn36nYmeB9TEMvCI6KhP98LUJGUSxhC+5ShN2zdAVjDikwBQ1FRM5uA+ejlcBcXoP1FrdqMVWR1lKWgFjH1hRZA5YzS7wrswMlCJu8lykPpIBfESQAIwBtCpqlLRUqOQ4IXBt7mvrQAvE9FVu862HkYK+U3LVm9SAn5wLCQIQCcQHPZZ35WtrhQjqd7eLw9tsb1ad3uioKM7OOGfbJyc53ty3aRr0mW1GaaArZN4AWskdRRSPFZQpljkGGmICGQQpqCDFkh2/QFKnXYRA/y+fztsPOzGt3C9YZQgX18VPjxqxSx884/3UKnC92d/w3GFd4RMMe9pgAAAABJRU5ErkJggg=='
downloadFileFromB64('icon.png',b64)
Parameters:
Name Type Description
fileName String

輸入檔名字串

b64 String

輸入base64資料字串

(static) downloadFileFromBlob(fileName, blob)

Description:
  • 前端下載Blob資料成為檔案

    Unit Test: Github

Source:
Example
need test in browser

//icon.png to u8a to blob
let u8a = new Uint8Array([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,16,0,0,0,16,8,6,0,0,0,31,243,255,97,0,0,2,26,73,68,65,84,56,79,165,147,205,107,19,65,24,198,103,246,43,18,77,104,161,246,32,130,85,17,10,161,219,209,10,193,116,183,205,65,4,207,106,15,85,235,110,18,163,8,122,20,188,249,23,120,81,168,20,178,155,84,236,37,96,241,224,69,68,73,119,99,90,136,216,77,217,42,138,80,4,47,86,90,48,107,155,54,179,51,178,129,148,198,172,141,224,220,230,253,248,241,242,60,239,11,193,127,62,216,169,127,62,158,58,28,114,55,195,17,227,233,178,95,173,47,192,144,198,187,67,60,127,8,189,201,217,150,148,152,0,0,30,160,20,23,80,49,103,255,9,241,5,88,178,122,15,82,74,72,128,159,133,117,87,30,156,211,50,139,210,53,21,8,244,37,122,61,253,109,55,164,13,176,36,167,174,48,53,252,10,239,103,123,128,75,147,148,226,73,100,78,127,250,56,156,8,213,32,185,142,204,236,131,191,2,150,228,9,209,165,220,113,100,106,179,20,220,103,150,227,223,123,237,131,171,171,99,249,188,235,53,45,142,36,206,49,16,84,197,130,86,106,66,90,38,176,70,149,91,117,71,200,158,126,55,181,225,39,88,121,40,205,11,193,250,13,209,208,31,181,1,172,216,213,94,192,114,23,6,13,125,114,47,103,44,73,189,3,193,214,19,209,156,89,247,234,118,38,120,31,83,16,203,194,35,162,161,63,223,11,80,145,148,75,24,66,251,148,161,55,108,221,1,88,195,138,76,1,67,81,81,51,155,128,249,232,229,112,23,23,160,253,69,173,218,140,85,100,117,148,165,160,22,49,245,133,22,64,229,140,210,239,10,236,192,201,66,38,239,37,202,67,233,32,23,196,73,0,8,192,27,66,166,169,75,69,74,142,67,130,23,6,222,230,190,180,0,188,79,69,86,239,58,216,121,24,43,229,55,45,89,189,72,9,249,192,176,144,33,0,156,64,115,217,103,126,86,182,184,80,142,167,123,120,188,61,182,198,245,105,221,238,138,130,140,236,227,134,125,178,114,115,157,237,203,118,145,175,73,150,212,102,154,2,182,77,224,5,172,145,212,81,72,241,89,66,153,99,144,97,166,32,33,144,66,154,130,12,89,33,219,244,5,42,117,216,68,15,242,249,252,237,176,243,179,26,221,194,245,134,80,129,125,124,84,248,241,171,20,177,243,206,63,221,66,167,11,221,157,255,13,198,21,222,17,48,199,189,166,0,0,0,0,73,69,78,68,174,66,96,130])
let bb = new Blob([u8a])
downloadFileFromBlob('icon.png',bb)
Parameters:
Name Type Description
fileName String

輸入檔名字串

blob Blob

輸入資料Blob

(static) downloadFileFromText(fileName, ccont, withBOMopt)

Description:
  • 前端下載text資料成為utf-8(含BOM)檔案

    Unit Test: Github

Source:
Example
need test in browser

let ctxt = 'abc\r\nde測試'
downloadFileFromText('data.txt',ctxt,false)

let ccsv = 'a,b,c\r\nd,e,測試'
downloadFileFromText('data.csv',ccsv,true)
Parameters:
Name Type Attributes Default Description
fileName String

輸入檔名字串

ccont String

輸入內容字串

withBOM Boolean <optional>
true

輸入是否添加BOM,預設true

(static) downloadFileFromU8Arr(fileName, u8a)

Description:
  • 前端下載binary資料成為檔案

    Unit Test: Github

Source:
Example
need test in browser

//icon.png to u8a
let u8a = new Uint8Array([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,16,0,0,0,16,8,6,0,0,0,31,243,255,97,0,0,2,26,73,68,65,84,56,79,165,147,205,107,19,65,24,198,103,246,43,18,77,104,161,246,32,130,85,17,10,161,219,209,10,193,116,183,205,65,4,207,106,15,85,235,110,18,163,8,122,20,188,249,23,120,81,168,20,178,155,84,236,37,96,241,224,69,68,73,119,99,90,136,216,77,217,42,138,80,4,47,86,90,48,107,155,54,179,51,178,129,148,198,172,141,224,220,230,253,248,241,242,60,239,11,193,127,62,216,169,127,62,158,58,28,114,55,195,17,227,233,178,95,173,47,192,144,198,187,67,60,127,8,189,201,217,150,148,152,0,0,30,160,20,23,80,49,103,255,9,241,5,88,178,122,15,82,74,72,128,159,133,117,87,30,156,211,50,139,210,53,21,8,244,37,122,61,253,109,55,164,13,176,36,167,174,48,53,252,10,239,103,123,128,75,147,148,226,73,100,78,127,250,56,156,8,213,32,185,142,204,236,131,191,2,150,228,9,209,165,220,113,100,106,179,20,220,103,150,227,223,123,237,131,171,171,99,249,188,235,53,45,142,36,206,49,16,84,197,130,86,106,66,90,38,176,70,149,91,117,71,200,158,126,55,181,225,39,88,121,40,205,11,193,250,13,209,208,31,181,1,172,216,213,94,192,114,23,6,13,125,114,47,103,44,73,189,3,193,214,19,209,156,89,247,234,118,38,120,31,83,16,203,194,35,162,161,63,223,11,80,145,148,75,24,66,251,148,161,55,108,221,1,88,195,138,76,1,67,81,81,51,155,128,249,232,229,112,23,23,160,253,69,173,218,140,85,100,117,148,165,160,22,49,245,133,22,64,229,140,210,239,10,236,192,201,66,38,239,37,202,67,233,32,23,196,73,0,8,192,27,66,166,169,75,69,74,142,67,130,23,6,222,230,190,180,0,188,79,69,86,239,58,216,121,24,43,229,55,45,89,189,72,9,249,192,176,144,33,0,156,64,115,217,103,126,86,182,184,80,142,167,123,120,188,61,182,198,245,105,221,238,138,130,140,236,227,134,125,178,114,115,157,237,203,118,145,175,73,150,212,102,154,2,182,77,224,5,172,145,212,81,72,241,89,66,153,99,144,97,166,32,33,144,66,154,130,12,89,33,219,244,5,42,117,216,68,15,242,249,252,237,176,243,179,26,221,194,245,134,80,129,125,124,84,248,241,171,20,177,243,206,63,221,66,167,11,221,157,255,13,198,21,222,17,48,199,189,166,0,0,0,0,73,69,78,68,174,66,96,130])
downloadFileFromU8Arr('icon.png',u8a)
Parameters:
Name Type Description
fileName String

輸入檔名字串

u8a Uint8Array

輸入Uint8Array資料

(static) dtmapping(dt, keys, defopt) → {Object}

Description:
  • 由dt物件提取指定keys欄位出來成為新物件,若物件不存在keys的欄位時,則會自動填入指定數據def

    Unit Test: Github

Source:
Example
console.log(dtmapping({ a: 'a123', b: 234, c: '345' }, ['a', 'b']))
// => { a: 'a123', b: 234 }

console.log(dtmapping({ a: 'a123', b: 234, c: '345' }, ['a', 'b', 'x']))
// => { a: 'a123', b: 234, x: '' }
Parameters:
Name Type Attributes Default Description
dt Object

輸入資料物件

keys Array

輸入要提取欄位集合的字串陣列

def * <optional>
''

輸入若無提取欄位時所給予的預設值,預設為''

Returns:

回傳處理後物件

Type
Object

(static) dtpick(dt, keys) → {Object}

Description:
  • 由dt物件提取指定keys欄位出來成為新物件,僅保留有keys的欄位,若不存在欄位就不提取

    Unit Test: Github

Source:
Example
console.log(dtpick({ a: 'a123', b: 234, c: '345' }, ['a', 'b']))
// => { a: 'a123', b: 234 }

console.log(dtpick({ a: 'a123', b: 234, c: '345' }, ['a', 'b', 'x']))
// => { a: 'a123', b: 234 }
Parameters:
Name Type Description
dt Object

輸入資料物件

keys Array

輸入要提取欄位集合的字串陣列

Returns:

回傳處理後物件

Type
Object

(static) estimateTicks(rmin, rmax, optopt) → {Object}

Description:
  • 給予最小與最大值,估計適合的繪圖刻度

    Unit Test: Github

Source:
Example
let rmin = null
let rmax = null
let r = null

// -4.66~-3.11
rmin = -4.66
rmax = -3.11
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin -4.66 rmax -3.11 r { tickNum: 3, tickInterval: 0.8, tickPositions: [ -4.7, -3.9, -3.1 ], tickDig: 1 }

// 0~0.9
rmin = 0
rmax = 0.9
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0 rmax 0.9 r { tickNum: 3, tickInterval: 0.5, tickPositions: [ 0, 0.5, 1 ], tickDig: 1 }

// 0~1
rmin = 0
rmax = 1
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0 rmax 1 r { tickNum: 3, tickInterval: 0.5, tickPositions: [ 0, 0.5, 1 ], tickDig: 1 }

// 0~99
rmin = 0
rmax = 99
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0 rmax 99 r { tickNum: 3, tickInterval: 50, tickPositions: [ 0, 50, 100 ], tickDig: 0 }

// 0~100
rmin = 0
rmax = 100
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0 rmax 100 r { tickNum: 3, tickInterval: 50, tickPositions: [ 0, 50, 100 ], tickDig: 0 }

// 0.1~0.9
rmin = 0.1
rmax = 0.9
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.1 rmax 0.9 r { tickNum: 3, tickInterval: 0.4, tickPositions: [ 0.1, 0.5, 0.9 ], tickDig: 1 }

// 0.1~1
rmin = 0.1
rmax = 1
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.1 rmax 1 r { tickNum: 3, tickInterval: 0.5, tickPositions: [ 0, 0.5, 1 ], tickDig: 1 }

// 0.1~99
rmin = 0.1
rmax = 99
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.1 rmax 99 r { tickNum: 3, tickInterval: 50, tickPositions: [ 0, 50, 100 ], tickDig: 0 }

// 0.1~100
rmin = 0.1
rmax = 100
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.1 rmax 100 r { tickNum: 3, tickInterval: 50, tickPositions: [ 0, 50, 100 ], tickDig: 0 }

// 0.1~100.1
rmin = 0.1
rmax = 100.1
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.1 rmax 100.1 r { tickNum: 4, tickInterval: 34, tickPositions: [ 0, 34, 68, 102 ], tickDig: 0 }

// 0.89~0.9
rmin = 0.89
rmax = 0.9
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.89 rmax 0.9 r { tickNum: 3, tickInterval: 0.01, tickPositions: [ 0.88, 0.89, 0.9 ], tickDig: 1 }

// 0.89~1
rmin = 0.89
rmax = 1
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.89 rmax 1 r { tickNum: 3, tickInterval: 0.1, tickPositions: [ 0.8, 0.9, 1 ], tickDig: 1 }

// 0.89~99
rmin = 0.89
rmax = 99
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.89 rmax 99 r { tickNum: 3, tickInterval: 50, tickPositions: [ 0, 50, 100 ], tickDig: 0 }

// 0.89~100
rmin = 0.89
rmax = 100
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.89 rmax 100 r { tickNum: 3, tickInterval: 50, tickPositions: [ 0, 50, 100 ], tickDig: 0 }

// 0.89~100.89
rmin = 0.89
rmax = 100.89
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 0.89 rmax 100.89 r { tickNum: 4, tickInterval: 34, tickPositions: [ 0, 34, 68, 102 ], tickDig: 0 }

// 50.89~99
rmin = 50.89
rmax = 99
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 50.89 rmax 99 r { tickNum: 3, tickInterval: 25, tickPositions: [ 50, 75, 100 ], tickDig: 0 }

// 50.89~100
rmin = 50.89
rmax = 100
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 50.89 rmax 100 r { tickNum: 3, tickInterval: 25, tickPositions: [ 50, 75, 100 ], tickDig: 0 }

// 90.89~99
rmin = 90.89
rmax = 99
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 90.89 rmax 99 r { tickNum: 4, tickInterval: 3, tickPositions: [ 90, 93, 96, 99 ], tickDig: 0 }

// 90.89~100
rmin = 90.89
rmax = 100
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 90.89 rmax 100 r { tickNum: 3, tickInterval: 5, tickPositions: [ 90, 95, 100 ], tickDig: 0 }

// 98.9~99
rmin = 98.9
rmax = 99
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 98.9 rmax 99 r { tickNum: 3, tickInterval: 1, tickPositions: [ 98, 99, 100 ], tickDig: 0 }

// 98.9~100
rmin = 98.9
rmax = 100
r = estimateTicks(rmin, rmax)
console.log('rmin', rmin, 'rmax', rmax, 'r', r)
// => rmin 98.9 rmax 100 r { tickNum: 3, tickInterval: 1, tickPositions: [ 98, 99, 100 ], tickDig: 0 }
Parameters:
Name Type Attributes Default Description
rmin Number | String

輸入數字或字串

rmax Number | String

輸入數字或字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
testTickNums Array <optional>
[3, 4, 5]

輸入可供測試刻度數量陣列,預設[3, 4, 5]

Returns:

回傳繪圖刻度物件,包含刻度數量tickNum、刻度間距tickInterval、刻度位置tickPositions

Type
Object

(static) evEmit(ev, name, argsopt, optopt) → {Boolean}

Description:
  • 於呼叫端安全派發事件,攔截監聽器之同步拋錯

    【為何在呼叫端派發,而非包裝監聽器】

    EventEmitter之規範語意為「監聽器同步拋錯時由emit外拋至emit之呼叫端」,該行為本身沒有問題。 會出事的是派發位置:於timer或I/O回呼內派發時,那個外拋即成uncaughtException而殺整個行程。 所以要處理的是派發位置,不是emitter之契約 —— 若改為包裝監聽器,就必須維護「包裝函數↔原函數」 之對應表,而該對應表會使on、off、once、listeners之行為偏離規範(事件名型別別名、以異物移除、 once自動移除等),封裝層自身反而成為「調用方拿到非預期」之來源。

    【為何不以setTimeout脫勾來迴避】

    setTimeout會開一個「沒有呼叫者」的堆疊,監聽器之同步拋錯於該處即為uncaughtException, 且呼叫端無論如何try都攔不到(此與監聽器是否為async無關,純同步監聽器亦然)。 於呼叫端之堆疊上派發,一個普通的try即可攔下。確實需要脫勾者請改用evEmitDelay, 其內部即以本函數派發,故仍有攔截。

    【為何不處理async監聽器之reject】

    emit依EventEmitter規範丟棄監聽器之回傳值,其rejection無人觀察;nodejs文件亦明載 「使用async函數作為事件處理器有問題」,並將captureRejections設計為opt-in而非預設, 而eventemitter3與瀏覽器端之events polyfill皆未提供該選項。 故監聽器應為同步,非同步之結果請以事件所帶之pm回覆 —— pm即為回覆通道,監聽器不需要第二條。

    【settle為何排在通報之前】

    settle該次派發所帶之pm為唯一「非做不可」者,通報排其後,如此縱使通報那幾行拋錯, 該次流程也已settle,懸置在結構上不可能發生。此順序為明文要求,不得對調。

    Unit Test: Github

Source:
Example
let ev = evem()

ev.on('error', (msg) => {
    console.log(msg.fun, msg.name, msg.msg.message)
    // => listener ev boom
})
ev.on('ev', () => {
    throw new Error('boom')
})

let r = evEmit(ev, 'ev', [{ a: 1 }], { tag: 'demo' })
console.log(r)
// => false
Parameters:
Name Type Attributes Default Description
ev Object

輸入事件物件,為evem所建立之eventemitter3實例

name String

輸入事件名稱字串

args Array <optional>
[]

輸入事件參數陣列,預設[]

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
funSettle function <optional>
null

輸入監聽器出錯時之收尾函數,傳入(err),用於settle該次派發所帶之pm使流程不懸置。不指定即代表本事件無回覆通道,不得由本函數猜測pm之位置

funEmit function <optional>
null

輸入實際派發函數,傳入(name, ...args),預設使用ev.emit。供已覆寫ev.emit之模組指定以原生emit於本程序派發,否則通報之error亦會走覆寫後之路徑

tag String <optional>
'evem'

輸入模組名稱字串,用於console.error之前綴,預設'evem'

Returns:

回傳emit之結果布林值,監聽器出錯時回傳false

Type
Boolean

(static) evEmitDelay(ev, name, argsopt, optopt) → {Object}

Description:
  • 以timer脫勾後於呼叫端安全派發事件,攔截監聽器之同步拋錯

    內部以setTimeout延後,再交由evEmit派發,故監聽器之同步拋錯仍被攔截。

    【何時需要脫勾】

    當派發當下之狀態尚未更新完成、或監聽器內會回頭存取本模組之狀態時,需先讓當前堆疊跑完再派發, 使監聽器取得的是更新後之狀態。此為業務需求而非防護需求。

    【脫勾為何不可直接用setTimeout包ev.emit】

    setTimeout會開一個「沒有呼叫者」的堆疊,監聽器之同步拋錯於該處即為uncaughtException, 且呼叫端無論如何try都攔不到(此與監聽器是否為async無關,純同步監聽器亦然)。 故脫勾之後仍須於該新堆疊內以try攔截,本函數即為此而設。

    【與evEmit之差異】

    因已脫勾,本函數無法同步取得emit之結果,故不回傳值;需要回傳值者請用evEmit。

    Unit Test: Github

Source:
Example
let ev = evem()

let rs = []
ev.on('ev', (msg) => {
    rs.push(msg)
})

evEmitDelay(ev, 'ev', [{ a: 1 }])
console.log(rs)
// => []   //尚未派發, 因已脫勾

setTimeout(() => {
    console.log(rs)
    // => [ { a: 1 } ]
}, 50)
Parameters:
Name Type Attributes Default Description
ev Object

輸入事件物件,為evem所建立之eventemitter3實例

name String

輸入事件名稱字串

args Array <optional>
[]

輸入事件參數陣列,預設[]

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
ms Integer <optional>
1

輸入延後派發之毫秒正整數,預設1,超過計時器上限者夾至上限,見_const.mjs

funSettle function <optional>
null

輸入監聽器出錯時之收尾函數,同evEmit

funEmit function <optional>
null

輸入實際派發函數,同evEmit

tag String <optional>
'evem'

輸入模組名稱字串,同evEmit

Returns:

回傳timer物件,可供呼叫端以clearTimeout取消該次派發

Type
Object

(static) evem() → {Object}

Description:
  • 建立事件物件(EventEmitter from eventemitter3)

    本函數僅回傳原生eventemitter3實例, 不做任何包裝, 故其行為完全遵循EventEmitter之規範語意: 監聽器同步拋錯時由emit外拋至emit之呼叫端(且該次派發之後續監聽器不再被呼叫); emit為同步且回傳布林值, 監聽器所回傳之值(含Promise)一律丟棄。

    關於async監聽器: nodejs文件明載「使用async函數作為事件處理器有問題, 會導致unhandled rejection」, 並將captureRejections設計為opt-in而非預設; eventemitter3與瀏覽器端之events polyfill皆未提供該選項。 故async監聽器應自行處理其錯誤(如以wsemi之pm2resolve包裝待執行函數), 本函數不代為攔截, 亦不應代為攔截 —— 一旦為此包裝監聽器, 就必須維護「包裝函數↔原函數」之對應表, 而該對應表會使on/off/once/listeners 之行為偏離規範(事件名型別別名、以異物移除、once自動移除等), 使封裝層自身成為「調用方拿到非預期」之來源。

    若模組於timer、stream、watcher等回呼內派發事件, 監聽器之同步拋錯即成uncaughtException而殺行程 —— 該情形屬「派發位置」之問題而非emitter契約之問題, 應於派發處自行以try catch攔截(wsemi提供evEmit與evEmitDelay為之)。

    See: eventemitter3

    Unit Test: Github

Source:
Example
let ev = evem()

ev.on('evName', function(msg) {
    console.log(msg)
    // => {abc: 12.34}
})

let data = { abc: 12.34 }
ev.emit('evName', data)
Returns:

回傳eventemitter3實例

Type
Object

(async, static) execCli(command, argsopt, optopt) → {Promise}

Description:
  • 通用CLI子程序調用,封裝逾時、進程樹清理、輸出驗證、結構化錯誤回報與自動重試 各參數原樣傳遞給目標CLI,支援中文 Windows下npm全域安裝之命令為.cmd批次檔,Node.js spawn無法直接執行(CVE-2024-27980安全修正後回EINVAL),內部已參考cross-spawn自動解析.cmd入口並繞過cmd.exe,故可支援含多行文字之參數 本函數不throw亦不reject,一律以結果物件之ok與error欄位回報成敗,error不含args與env之值(子程序自身之輸出另見stdout、stderr);command、args、opt或其選項值不可讀(getter拋錯、已撤銷之Proxy等)時亦以結果物件回報,error為'<錯誤碼>: <訊息>'(無錯誤碼者為UNKNOWN,拋出非Error之值時訊息為該值之文字)

    Unit Test: Github

Source:
Example
//need test in nodejs

async function test() {

    let nodeBin = process.execPath

    let r1 = await execCli(nodeBin, ['-e', 'process.stdout.write("hello")'])
    console.log('r1', r1.ok, r1.stdout)
    // => r1 true hello

    let r2 = await execCli(nodeBin, ['-e', 'process.exit(3)'])
    console.log('r2', r2.ok, r2.code, r2.error)
    // => r2 false 3 Exit code 3

    let r3 = await execCli(nodeBin, ['-e', 'process.stdin.on("data",(d)=>process.stdout.write(d))'], { input: '中文測試' })
    console.log('r3', r3.ok, r3.stdout)
    // => r3 true 中文測試

    let r4 = await execCli(nodeBin, ['-e', 'process.stdout.write("abc")'], { validate: 'json' })
    console.log('r4', r4.ok, r4.error)
    // => r4 false OUTPUT_VALIDATION_FAILED

    let r5 = await execCli(nodeBin, ['-e', 'setTimeout(()=>{},30000)'], { timeoutMs: 500 })
    console.log('r5', r5.ok, r5.error)
    // => r5 false TIMEOUT after 0.5s

    let r6 = await execCli(nodeBin, ['-e', 'process.stdout.write(process.env.API_KEY)'], { env: { API_KEY: 'KEY_A' } })
    console.log('r6', r6.ok, r6.stdout, process.env.API_KEY)
    // => r6 true KEY_A undefined (僅作用於該次子進程, 本進程process.env不受影響)

}
test()
    .catch((err) => {
        console.log(err)
    })
Parameters:
Name Type Attributes Default Description
command String

輸入執行檔名稱字串,例如'node'、'curl'、'claude'等,可為註冊系統的全域指令或執行檔路徑

args Array <optional>
[]

輸入參數字串陣列,內容原樣傳遞給目標CLI,預設[]

opt Object <optional>
{}

輸入設定物件

Properties
Name Type Attributes Default Description
timeoutMs Number <optional>
120000

輸入逾時毫秒正整數,逾時將調用execProcessKillPid強制關閉子進程及其子孫程序,預設120000

cwd String <optional>
process.cwd()

輸入子進程工作目錄字串,預設process.cwd();不存在或非資料夾時以結果物件回報error為'ENOENT: cwd is not an existing folder: '(未能啟動,不重試)

input String <optional>

輸入傳入子進程stdin之字串,預設undefined代表不寫入stdin

validate String | function <optional>

輸入stdout驗證規則字串或自訂驗證函數,規則字串支援'nonempty'(非空)、'json'(可JSON.parse)、'min:100'(長度下限),多規則可用逗號串接例如'nonempty,json',自訂函數為(stdout)=>Boolean或回傳Promise(以其resolve值判定),回傳真值為通過;自訂函數拋錯或其Promise被reject視同驗證失敗(error為'OUTPUT_VALIDATION_FAILED',stderr前綴'validate threw: 錯誤訊息',訊息截斷至500字元);自訂函數回傳之Promise不受timeoutMs約束(子進程已結束),永不落定時本函數亦不落定,durationMs不含驗證時間,預設undefined代表不驗證

maxBuffer Number <optional>
10485760

輸入stdout與stderr各自累積之最大字元數正整數,超過部分將不再累積,預設10485760(10MB)

onStdout function <optional>

輸入stdout串流回調函數,格式為(chunk)=>{},回調內拋出之錯誤不會被攔截,將成為未捕捉例外,請於回調內自行處理(該錯誤於回調返回後立即(微任務)原樣重拋,不影響本函數之輸出累積與結果),預設undefined

onStderr function <optional>

輸入stderr串流回調函數,格式為(chunk)=>{},行為同onStdout,預設undefined

env Object <optional>

輸入本次調用額外注入之環境變數物件,會併入process.env之後(同名以此為準,含PYTHONIOENCODING),僅作用於該次子進程而不影響本進程之process.env,故並行調用可各自帶不同值;值為undefined代表移除該變數(可遮蔽繼承值),值為null會成為字面字串'null'故建議勿傳;Windows下環境變數大小寫不敏感,同名不同大小寫(如Path與PATH)亦視為同名覆蓋,預設undefined代表不覆寫任何變數

maxRetries Number <optional>
0

輸入失敗後最大重試次數非負整數,遇ENOENT(命令不存在、不存在之.cmd/.bat、npm shim之入口檔不存在)、exit code 2(參數錯誤)或無法啟動子進程之同步錯誤(如參數過長、權限不足、非有效執行檔、參數含空字元)視為不可重試而立即中止,預設0

retryDelayMs Number <optional>
5000

輸入重試間隔毫秒正整數,實際間隔為retryDelayMs乘以重試次數且上限15000ms,預設5000

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;欄位皆為原始值,不含stdout與stderr之內文(只有長度);type為'attempt'時為即將啟動第n次嘗試(含n,由1起算),'retry'時為上次嘗試失敗而即將等待ms毫秒後啟動第n次嘗試(含n、ms;n由2起算,ms即實際間隔),'spawn'時為子進程已建立(含pid,未能啟動者為null),'data'時為收到一段已解碼之輸出(含stream為'stdout'或'stderr'、len為該段字元數、size為處理後該串流已累積之字元數、kept為該段是否被累積;累積量已達maxBuffer者kept為false且size不變;子進程結束時殘餘之解碼不通知,settle之後亦不通知),'full'時為該串流累積量首次達到maxBuffer(含stream、size),'timeout'時為timeoutMs逾時計時器觸發而即將樹殺(含ms為timeoutMs),'kill'時為即將以execProcessKillPid樹殺(含pid),'settle'時為本次嘗試即將定案(含reason:'ok'為離開碼0且通過驗證,'exit'為非零離開碼或被訊號終止,'timeout'為逾時樹殺後close,'validate'為驗證失敗,'error'為子進程未能啟動,'stuck'為樹殺後3秒仍未收到close而強制定案;另含code為離開碼數字或null、signal為終止訊號字串或null),'done'時為整個呼叫即將回傳(含n為實際嘗試次數、ok、reason:成功為'ok',失敗為'spawn'(無法啟動子進程之同步錯誤)、'enoent'(命令不存在)、'code2'(離開碼2)等不可重試者,其餘含maxRetries為0者為'exhausted');command無效或opt不可讀而在啟動前即回報者無事件;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響本函數,預設null

Returns:

回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、durationMs(耗時毫秒)、pid(子進程pid)、attempts(實際嘗試次數)。子進程未能啟動時(spawn同步拋錯、'error'事件、不存在之.cmd/.bat、npm shim之入口檔不存在、cwd不存在、輸入不可讀等)code為null且結果帶pid鍵、其值為undefined;已啟動者(含逾時、訊號結束、非零離開、驗證失敗)皆帶pid數值;command非非空字串時不啟動,attempts為0且不帶pid鍵

Type
Promise

(async, static) execCliJsonRpc(command, argsopt, requestsopt, optopt) → {Promise}

Description:
  • 對stdio協定型CLI依序送出一行一則JSON之請求並收集回應(疊於execCliSession之上),適用codex app-server、MCP之stdio伺服器等以換行分隔JSON之協定(LSP為Content-Length分幀,不適用) 不內建任何協定知識,initialize/initialized等握手由呼叫端以請求陣列表達 本函數不throw亦不reject,一律以結果物件之ok與error、errorType欄位回報成敗,error不含args與env之值;command、args、requests、opt或其選項值不可讀(getter拋錯、已撤銷之Proxy等)時不啟動子進程,errorType為params;請求之params無法序列化為JSON(如含BigInt、循環參照)時errorType亦為params,error為': 無法序列化為JSON: <訊息>',已啟動之子進程照常經stop()結束

    規則:

    • 請求物件含method者為請求,自動配id(自0遞增)並等待同id之回應;含notify者為通知,不配id,送出即繼續
    • 回應之判定為「有id且含result或error」;帶id但含method者為伺服器發給客戶端之請求,交由onServerRequest處理(未提供則忽略);無id者為伺服器通知,交由onNotify(未提供則忽略)
    • 依序送出、逐一等待;任一回應含error即中止(results[該方法]=null,errorType='rpc'),除非continueOnError=true
    • 全部完成後才stop();此時之退出不視為失敗

    Unit Test: Github

Source:
Example
//need test in nodejs

async function test() {

    let nodeBin = process.execPath

    //模擬一行一則JSON之伺服器: 收到請求回{ id, result }, 方法為fail者回error, 收到EOF即結束
    let sc = `
        let rl = require('readline').createInterface({ input: process.stdin })
        rl.on('line', (l) => {
            let m = JSON.parse(l)
            if (m.id === undefined) return
            if (m.method === 'fail') {
                process.stdout.write(JSON.stringify({ id: m.id, error: { code: -1, message: 'bad' } }) + '\\n')
            }
            else {
                process.stdout.write(JSON.stringify({ id: m.id, result: { method: m.method, params: m.params } }) + '\\n')
            }
        })
        rl.on('close', () => process.exit(0))
    `

    let r = await execCliJsonRpc(nodeBin, ['-e', sc], [
        { method: 'initialize', params: { clientInfo: { name: 'demo' } } },
        { notify: 'initialized' },
        { method: 'account/read' },
    ])
    console.log(r.ok, JSON.stringify(r.errorType), r.results['account/read'], r.exitCode)
    // => true "" { method: 'account/read' } 0

    let r2 = await execCliJsonRpc(nodeBin, ['-e', sc], [
        { method: 'fail' },
        { method: 'never' },
    ])
    console.log(r2.ok, r2.errorType, r2.results, r2.error)
    // => false rpc { fail: null } fail: bad

}
test()
    .catch((err) => {
        console.log(err)
    })
Parameters:
Name Type Attributes Default Description
command String

輸入執行檔名稱字串

args Array <optional>
[]

輸入參數字串陣列,預設[]

requests Array <optional>
[]

輸入請求物件陣列,每項為{ method, params }(請求,等回應)或{ notify, params }(通知,不等回應),params可省略,陣列於呼叫時讀取為快照(其後對該陣列之增刪不影響本次送出),預設[]

opt Object <optional>
{}

輸入設定物件

Properties
Name Type Attributes Default Description
timeoutMs Number <optional>
120000

輸入整體逾時毫秒正整數,涵蓋全部請求與收尾,逾時即樹殺,預設120000

cwd String <optional>
process.cwd()

輸入子進程工作目錄字串,預設process.cwd()

env Object <optional>

輸入額外注入之環境變數物件,語意同execCli,預設undefined

exitGraceMs Number <optional>
2000

輸入收尾stop()之寬限毫秒,語意同execCliSession,預設2000

jsonrpc String | Boolean <optional>

輸入每則訊息附加之jsonrpc欄位值,true代表'2.0'(MCP等JSON-RPC 2.0協定需要),字串則原樣附加,預設undefined代表不附加(codex app-server不需要)

continueOnError Boolean <optional>
false

輸入回應含error時是否繼續送出後續請求布林值,預設false

onNotify function <optional>

輸入伺服器通知(無id訊息)回調函數,格式為(msg)=>{},預設undefined

onServerRequest function <optional>

輸入伺服器發給客戶端之請求(帶id且含method)回調函數,格式為(msg, session)=>{},可用session.writeLine回覆,預設undefined

onStderr function <optional>

輸入stderr片段回調函數,格式為(chunk)=>{},預設undefined。onNotify、onServerRequest、onStderr內拋出之錯誤不會被攔截,將成為未捕捉例外,請於回調內自行處理(該錯誤於回調返回後立即(微任務)原樣重拋,不影響其餘訊息之處理與本函數之結果)

onTrace function <optional>
null

輸入觀察回調函數,原樣轉交execCliSession,事件即其工作階段之事件(type為spawn、data、write、stop、grace、timeout、kill、settle,欄位與時機見execCliSession之opt.onTrace,其中write為每則請求或通知寫入stdin一次,timeout之ms為本函數之timeoutMs,stop與grace之ms為exitGraceMs),本函數自身無計時器亦不另通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響本函數;opt.onTrace屬性本身不可讀(getter拋錯)與onStderr相同,errorType為params,預設null

Returns:

回傳Promise,resolve回傳結果物件,內含ok(是否全部成功布林值)、results(以method為鍵之result物件,失敗者為null)、responses(依序之回應陣列,每項為{ id, method, result, error })、error(錯誤訊息字串,成功時為空字串)、errorType(錯誤類型字串: params(參數錯誤,含輸入不可讀與params無法序列化)、notfound(子進程未能啟動,如命令不存在、npm shim之入口檔不存在、非有效執行檔、權限不足、cwd不存在、交execCliSession之cwd、env、exitGraceMs之值不可讀)、timeout(逾時)、exit(已啟動之子進程於回應前結束)、rpc(回應含error),成功時為空字串)、durationMs(耗時毫秒)、exitCode(子進程離開碼)

Type
Promise

(static) execCliSession(command, argsopt, optopt) → {Object}

Description:
  • 建立互動式stdio工作階段(session),用於stdio協定型之長駐CLI(如MCP之stdio伺服器、codex app-server等一行一則JSON之程式),與execCli「寫完input即關閉stdin並等待結束」之一次性契約不同,本函數維持stdin開啟,由呼叫端逐次write,直到呼叫stop()才關閉stdin並等待子進程自行退出,逾寬限期則以execProcessKillPid樹殺 Windows下npm全域安裝之命令為.cmd批次檔,內部經execCliCore.buildSpawnArgs解析為以node執行其JS入口,不使用shell:true,故樹殺可確實涵蓋子孫程序 本函數不throw亦不reject,spawn失敗(如ENOENT)以onExit與stop()之結果物件回報,其error不含args與env之值;command、args、opt或其選項值不可讀(getter拋錯、已撤銷之Proxy等)時亦不spawn,同樣以onExit與stop()之結果回報(pid為null,error為'<錯誤碼>: <訊息>',無錯誤碼者為UNKNOWN,拋出非Error之值時訊息為該值之文字;onExit本身不可讀時僅以stop()回報)

    Unit Test: Github

Source:
Example
//need test in nodejs

async function test() {

    let nodeBin = process.execPath

    //逐行回顯程式, 收到EOF即結束
    let sc = `
        let rl = require('readline').createInterface({ input: process.stdin })
        rl.on('line', (l) => process.stdout.write('echo:' + l + '\\n'))
        rl.on('close', () => process.exit(0))
    `

    let lines = []
    let s = execCliSession(nodeBin, ['-e', sc], {
        onLine: (line) => {
            lines.push(line)
        },
    })
    await s.writeLine('abc')
    await s.writeLine('中文')
    let r = await s.stop() //關閉stdin, 子進程收到EOF自行退出
    console.log(lines, r.code, r.exited, r.killed)
    // => [ 'echo:abc', 'echo:中文' ] 0 true false

    //不理會EOF之程式, stop()逾寬限期即樹殺
    let s2 = execCliSession(nodeBin, ['-e', 'setInterval(() => {}, 1000)'], { exitGraceMs: 300 })
    let r2 = await s2.stop()
    console.log(r2.exited, r2.killed)
    // => true true

}
test()
    .catch((err) => {
        console.log(err)
    })
Parameters:
Name Type Attributes Default Description
command String

輸入執行檔名稱字串,可為註冊系統的全域指令或執行檔路徑

args Array <optional>
[]

輸入參數字串陣列,預設[]

opt Object <optional>
{}

輸入設定物件

Properties
Name Type Attributes Default Description
cwd String <optional>
process.cwd()

輸入子進程工作目錄字串,預設process.cwd();不存在或非資料夾時以onExit與stop()之結果回報error為'ENOENT: cwd is not an existing folder: '

env Object <optional>

輸入本次額外注入之環境變數物件,語意同execCli,預設undefined

timeoutMs Number <optional>
null

輸入整體逾時毫秒正整數,逾時即樹殺並於結果標timeout:true,預設null代表不限制(工作階段為長駐,不沿用execCli之120000)

exitGraceMs Number <optional>
2000

輸入stop()關閉stdin後等待子進程自行退出之寬限毫秒非負整數,逾期即樹殺,預設2000

killWaitMs Number <optional>
3000

輸入樹殺後等待exit事件之上限毫秒非負整數,逾期仍未退出則於結果標exited:false並結束等待,預設3000

stderrKeep Number <optional>
4000

輸入stderr保留尾端字元數非負整數,預設4000

onLine function <optional>

輸入stdout逐行回調函數,格式為(line)=>{},line不含換行(\r\n與\n皆視為換行),子進程結束時殘餘之不完整行亦會發出,預設undefined

onStdout function <optional>

輸入stdout原始片段回調函數,格式為(chunk)=>{},已經UTF-8跨chunk解碼,供非行式協定(如LSP之Content-Length分幀)自行分幀,預設undefined

onStderr function <optional>

輸入stderr片段回調函數,格式為(chunk)=>{},預設undefined

onExit function <optional>

輸入子進程結束回調函數,格式為(result)=>{},result同stop()之回傳,預設undefined。onLine、onStdout、onStderr、onExit內拋出之錯誤不會被攔截,將成為未捕捉例外,請於回調內自行處理(該錯誤於回調返回後立即(微任務)原樣重拋,不影響其餘回調之送達、stop()之落定與結果)

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;欄位皆為原始值,不含stdout與stderr之內文(只有長度);type為'spawn'時為子進程已建立(含pid,未能啟動者為null),'data'時為收到一段已解碼之輸出(含stream為'stdout'或'stderr'、len為該段字元數;子進程結束時殘餘之解碼不通知,settle之後亦不通知),'write'時為一次寫入stdin定案(含ok為是否寫入成功、len為字元數;因已結束、已stop()或stdin不可寫而拒絕者len為null,已settle後之寫入不通知),'stop'時為首次呼叫stop()而即將關閉stdin並起算寬限(含ms為exitGraceMs;已settle或未能spawn者不通知,重複呼叫不通知),'grace'時為寬限期已到而子進程仍未退出,即將樹殺(含ms為exitGraceMs),'timeout'時為timeoutMs整體逾時計時器觸發而即將樹殺(含ms為timeoutMs),'kill'時為即將以execProcessKillPid樹殺並起算killWaitMs(含pid、ms為killWaitMs),'settle'時為工作階段即將定案而於onExit與stop()落定之前通知(含code為離開碼數字或null、signal為終止訊號字串或null、exited、timeout、killed、error字串,欄位同結果物件,exited為false代表樹殺後逾killWaitMs仍未退出);輸入不可讀、command無效、spawn失敗者只有settle事件;opt.onTrace屬性本身不可讀(getter拋錯)與其他回調相同,視為輸入不可讀;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響工作階段,預設null

Returns:

回傳工作階段物件s,內含pid(子進程pid,spawn失敗為null)、exited(是否已結束布林值)、write(str)(寫入stdin,回傳Promise,resolve為是否寫入成功布林值,子進程已結束時靜默resolve(false))、writeLine(str)(補換行後寫入,str無法轉為字串時resolve(false))、stop()(關閉stdin並等待結束,回傳Promise,resolve回傳結果物件,內含code、signal、exited、timeout、killed、error、stderr、durationMs、pid;多次呼叫回傳同一Promise)

Type
Object

(async, static) execJsCode(fpJsCode, optopt) → {Promise}

Description:
  • 後端nodejs動態載入並執行指定mjs檔之default匯出函數

    該mjs須export default一個函數(可為async或sync),opt將整包傳入該函數作為其輸入參數,並回傳該函數之執行結果

    fpJsCode為相對於專案根(process.cwd())之路徑字串,例如'./d06_p1Factors/genP1Factors_rp.mjs',因觸發腳本由專案根執行故cwd即為專案根,path.resolve(fpJsCode)以cwd為基準解析成絕對路徑。Windows下動態import絕對路徑須轉file:// URL(否則'd:'被當成protocol而擲ERR_UNSUPPORTED_ESM_URL_SCHEME),內部已用pathToFileURL處理

    Unit Test: Github

Source:
Example
//need test in nodejs

//假設有檔案 ./myCode.mjs 內容為:
//   async function run(opt) {
//       return { sum: opt.a + opt.b }
//   }
//   export default run

if (true) {
    let r = await execJsCode('./myCode.mjs', { a: 3, b: 4 })
    console.log(r)
    // => { sum: 7 }
}

if (true) {
    execJsCode('./myCode.mjs', { a: 1, b: 2 })
        .then(function(data) {
            console.log('then', data)
            // => then { sum: 3 }
        })
        .catch(function(err) {
            console.log('catch', err)
        })
}
Parameters:
Name Type Attributes Default Description
fpJsCode String

輸入待執行mjs檔之路徑字串,相對於專案根(process.cwd())解析

opt Object <optional>
{}

輸入設定物件,將整包傳入目標mjs之default函數作為其輸入參數,預設{}

Returns:

回傳Promise,resolve回傳目標mjs之default函數執行結果,reject回傳錯誤訊息(如檔案不存在、無default匯出、或目標函數內部拋錯)

Type
Promise

(static) execProcess(prog, args, optopt) → {Promise}

Description:
  • 呼叫執行檔執行

    Unit Test: Github

Source:
Example
//need test in nodejs

if (true) {
    let prog = 'taskkill'
    let args = ['/pid', '{pid}', '/T', '/F']
    let r = await execProcess(prog, args) //預設spawn
}

if (true) {
    //Windows原生指令輸出為系統字碼頁(中文系統為big5), 給codeCmd:'auto'可自動判定, 或直接給'big5'
    let r = await execProcess('tasklist', ['/FI', 'IMAGENAME eq node.exe'], { codeCmd: 'auto' })
}

if (true) {
    //execFile與spawn之args須為陣列, 每個元素為一個參數; 給字串會被當成單一參數而失敗
    //密碼置於命令列參數時同機其他使用者可由程序列表看到; 失敗訊息不含args, 但子程序自身之輸出可能回顯
    let prog = `C:\\Program Files\\7-Zip\\7z.exe`
    let pw = 'AbcD1234'
    let args = ['a', 'abc.7z', 'abc.txt', `-p${pw}`]
    execProcess(prog, args, { mode: 'execFile' })
        .then(function(data) {
            console.log('then', data)
        })
        .catch(function(data) {
            console.log('catch', data)
        })
}
Parameters:
Name Type Attributes Default Description
prog String

輸入執行檔或程式語言位置字串,若為註冊系統的全域指令,例如可直接給'Python',腳本需自行接收呼叫引數,並將回傳資料轉json字串後print/log到dos視窗,即可由nodejs接收

args String | Array

輸入參數陣列或字串,例如腳本檔案位置字串,空陣列或空字串代表無參數;非陣列亦非字串時reject錯誤訊息(不拋錯)

opt Object <optional>
{}

輸入設定物件

Properties
Name Type Attributes Default Description
mode String <optional>
'spawn'

輸入執行模式字串,可選'spawn'、'exec'、'execFile',預設'spawn'。'spawn'不經shell直接執行,輸出以串流處理,適合大量輸出或長時間執行,prog與args不需也不可加引號;'exec'經shell執行,prog與args以空白串接成指令字串,適合須用到shell語法之情形,陣列型args之元素含空白且未含雙引號者會自動以雙引號包住,prog含空白亦同,已含雙引號者視為呼叫端自行處理而原樣串接,字串型args視為呼叫端已自組之指令列,原樣串接不加引號;'execFile'不經shell直接執行可執行檔,args須為陣列,給字串會被當成單一參數。三種模式之stdout與stderr皆以Buffer接收後走同一條解碼路徑;惟'exec'與'execFile'之stdout或stderr超過1MB(node之maxBuffer預設值)時子程序會被node終止,reject為code[null]:\n訊息,大量輸出請用'spawn'

cbStdout function <optional>
null

輸入回調stdout函數,預設null。收到的是經串流解碼之字串,多位元組字元跨chunk時會等到完整才給,故不會收到空字串;codeCmd為'auto'時回調為盡力判定,該串流首次遇到非utf-8位元組後才改用系統字碼頁,之前已回調之字串不追溯,最終結果以resolve或reject之字串為準。回調內拋出之錯誤不會被攔截,將成為未捕捉例外,請於回調內自行處理(該錯誤於回調返回後立即(微任務)原樣重拋,不影響本函數之落定與結果字串)

cbStderr function <optional>
null

輸入回調stderr函數,預設null,行為同cbStdout

codeCmd String <optional>
'utf8'

輸入解碼stdout與stderr之編碼字串,預設'utf8'。可給TextDecoder支援之標籤,例如'utf8'、'big5'、'gbk'、'gb18030'、'shift_jis'、'euc-kr'、'euc-jp'、'latin1'、'utf-16le',亦可給Windows字碼頁編號寫法如'cp950'、'950'、'cp936'、'cp932'、'cp949'、'cp437'、'cp1252';給'system'表示用當前作業系統字碼頁,win32以chcp查詢一次後快取,其他平台為utf-8;給'auto'表示先以utf-8嚴格解碼,遇非utf-8位元組之串流改用系統字碼頁,適合不確定子程序輸出編碼之情形,例如Windows原生指令與以ANSI字碼頁輸出之打包程式。非有效字串視為未設定而用預設值,有效字串但不支援之編碼則於執行前reject並帶該標籤。輸出開頭之utf-8 BOM會被移除

useChcp Boolean <optional>
false

輸入是否於指令前置chcp 65001,僅'exec'模式生效,預設false。可使主控台工具改以utf-8輸出,副作用是其訊息語言可能變為英文;使用時codeCmd請維持預設'utf8'或給'auto',不要搭配'system'

timeout Number <optional>
null

輸入逾時毫秒數,預設null表示不限制;逾時觸發時會調用execProcessKillPid強制關閉子進程及其子孫程序,並reject回傳逾時訊息

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷,事件不含args之內容;type為'spawn'時為子程序已建立(含mode為執行模式字串、pid為子程序pid數字,無pid者為null;exec模式之pid為shell之pid),'data'時為收到stdout或stderr之一個chunk並已解碼(含which為'out'或'err'、bytes為chunk位元組數、text為解碼後字串,該chunk僅含尚未完整之多位元組序列時為空字串;於呼叫cbStdout、cbStderr之前通知),'switch'時為codeCmd為'auto'之該串流首次遇到非utf-8位元組而改用系統字碼頁(含which、label為改用之解碼標籤;每串流至多一次,通知於觸發改判之那個data事件之前),'timeout'時為已達opt.timeout而即將殺子進程(含ms為逾時毫秒數),'kill'時為即將調用execProcessKillPid殺整棵程序樹(含pid;不轉傳onTrace,其事件不混入本回調),'settle'時為即將resolve或reject(含ok為是否resolve、reason為落定原因字串、code為子程序離開碼數字,因訊號結束或未執行至結束者為null;reason為'ok'代表離開碼0而resolve,'code'代表離開碼非0,'timeout'代表逾時,'spawn'代表無法啟動子程序(同步拋錯、error事件或取系統字碼頁失敗;error事件者其後node仍會發出之close不再通知,故每次呼叫至多一個settle),'args'代表args型別不符,'codeCmd'代表編碼不支援,'prog'代表spawn模式prog含引號);opt不可讀者(含onTrace屬性之getter拋錯)屬取得回調前之失敗,reject說明字串且不通知;事件皆於resolve或reject之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響本函數,預設null

Returns:

回傳Promise,resolve回傳stdout與stderr依到達順序合併之字串,不另加換行,子程序自己的換行原樣保留;reject回傳錯誤訊息字串:離開碼非0時為code[N]:\n訊息,訊息為子程序之輸出,子程序因訊號結束時N為null;逾時為timeout[Nms]:\n訊息;無法啟動子程序時(找不到執行檔、權限不足、非有效執行檔、參數過長、參數含空字元、系統資源用盡等)為spawn[錯誤碼]:\n訊息,訊息為node之錯誤說明(可含執行檔路徑),不含args之內容;args型別不符(含判型即拋錯之不可讀之值)、codeCmd不支援、spawn模式prog含引號或opt不可讀(opt或其選項值之getter拋錯、已撤銷之Proxy等,為opt is not readable: 訊息)時為說明字串;prog不可讀時為spawn[UNKNOWN]:\n訊息;不可讀而拋出非Error之值時訊息為該值之文字;本函數不同步拋錯

Type
Promise

(static) execProcessKillPid(pid, optopt) → {Promise}

Description:
  • 強制關閉指定PID程序及其子孫程序(跨平台,Windows/Linux/macOS)

    Unit Test: Github

Source:
Example
//need test in nodejs

if (true) {
    let pid = 123456
    execProcessKillPid(pid)
        .then(function(data) {
            console.log('then', data)
        })
        .catch(function(data) {
            console.log('catch', data)
        })
}
Parameters:
Name Type Attributes Default Description
pid Number | String

輸入程序pid

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'start'時為即將呼叫tree-kill(含pid為傳給tree-kill之數字,非有限數字者為null),'done'時為tree-kill回報成功而即將resolve(含pid),'fail'時為tree-kill回報失敗而即將reject(含pid;msg為reject訊息中冒號後之錯誤訊息字串);事件皆於resolve或reject之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響本函數;opt或onTrace不可讀(getter拋錯、已撤銷之Proxy)時視同未給回調,預設null

Returns:

回傳Promise,resolve回傳成功訊息pid[N] killed,reject回傳錯誤訊息字串pid[N] kill failed: 訊息;Windows下訊息含taskkill之輸出,其為系統字碼頁而可能無法正確顯示

Type
Promise

(static) filepathToTree(fps, optopt, delimiteropt, bindRootopt, soryItemsopt) → {Object}

Description:
  • 轉換檔案路徑陣列成為樹狀物件與關聯資料物件

    Unit Test: Github

Source:
Example
let fps1 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa/mmm/z1.txt' }]
let r1 = filepathToTree(fps1)
fs.writeFileSync('r1.json', JSON.stringify(r1), 'utf8')
console.log(r1)
console.log('\n\n')
// => {
//   treeItems: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   treeItemsFolder: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   kpPath: {
//     root: '0',
//     'root❯aaa': '0.children.0',
//     'root❯aaa❯mmm': '0.children.0.children.0',
//     'root❯aaa❯mmm❯z1.txt': '0.children.0.children.0.children.0'
//   },
//   fpsNormalize: [
//     { type: 'folder', path: '/root/aaa' },
//     { type: 'folder', path: '/root/aaa/mmm' },
//     { type: 'file', path: '/root/aaa/mmm/z1.txt' }
//   ]
// }

let fps2 = [{ 'type': 'file', 'path': '/aaa/za.txt' }, { 'type': 'file', 'path': '/bbb/zb.txt' }]
let r2 = filepathToTree(fps2)
fs.writeFileSync('r2.json', JSON.stringify(r2), 'utf8')
console.log(r2)
console.log('\n\n')
// => {
//   treeItems: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   treeItemsFolder: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   kpPath: {
//     root: '0',
//     'root❯aaa': '0.children.0',
//     'root❯bbb': '0.children.1',
//     'root❯aaa❯za.txt': '0.children.0.children.0',
//     'root❯bbb❯zb.txt': '0.children.1.children.0'
//   },
//   fpsNormalize: [
//     { type: 'folder', path: '/root/aaa' },
//     { type: 'file', path: '/root/aaa/za.txt' },
//     { type: 'folder', path: '/root/bbb' },
//     { type: 'file', path: '/root/bbb/zb.txt' }
//   ]
// }

let fps3 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa/mmm/z1.txt' }]
let r3 = filepathToTree(fps3, { delimiter: '>' })
fs.writeFileSync('r3.json', JSON.stringify(r3), 'utf8')
console.log(r3)
console.log('\n\n')
// => {
//   treeItems: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   treeItemsFolder: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   kpPath: {
//     root: '0',
//     'root>aaa': '0.children.0',
//     'root>aaa>mmm': '0.children.0.children.0',
//     'root>aaa>mmm>z1.txt': '0.children.0.children.0.children.0'
//   },
//   fpsNormalize: [
//     { type: 'folder', path: '/root/aaa' },
//     { type: 'folder', path: '/root/aaa/mmm' },
//     { type: 'file', path: '/root/aaa/mmm/z1.txt' }
//   ]
// }

let fps4 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa/mmm/z1.txt' }]
let r4 = filepathToTree(fps4, { bindRoot: 'root' })
fs.writeFileSync('r4.json', JSON.stringify(r4), 'utf8')
console.log(r4)
console.log('\n\n')
// => {
//   treeItems: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   treeItemsFolder: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   kpPath: {
//     root: '0',
//     'root❯aaa': '0.children.0',
//     'root❯aaa❯mmm': '0.children.0.children.0',
//     'root❯aaa❯mmm❯z1.txt': '0.children.0.children.0.children.0'
//   },
//   fpsNormalize: [
//     { type: 'folder', path: '/root/aaa' },
//     { type: 'folder', path: '/root/aaa/mmm' },
//     { type: 'file', path: '/root/aaa/mmm/z1.txt' }
//   ]
// }

let fps5 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa1.txt' }, { 'type': 'file', 'path': '/aaa2.txt' }, { 'type': 'folder', 'path': '/aaa/aaammm' }, { 'type': 'file', 'path': '/aaa/aaammm.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/aaammmccc.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcd/abcde.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdef1.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdef2.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdef3 aaa mmm ccc dddddd eeeeeee fffffffffff ggggggggggggg.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg01.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg02.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg03.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg04.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg05.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg06.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg07.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg08.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg09.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg10.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg11.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg12.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg13.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg14.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg15.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg16.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg17.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg18.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg19.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg20.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcd1.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcd2.txt' }, { 'type': 'folder', 'path': '/mmm' }, { 'type': 'file', 'path': '/ccc/cccddd/cccdddeee.txt' }, { 'type': 'folder', 'path': '/eee' }, { 'type': 'folder', 'path': '/eee/eeefff1' }, { 'type': 'folder', 'path': '/eee/eeefff2' }, { 'type': 'folder', 'path': '/ggg/' }, { 'type': 'folder', 'path': 'c:\\hhh' }, { 'type': 'folder', 'path': '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff' }, { 'type': 'file', 'path': '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/gfedcba.txt' }, { 'type': 'file', 'path': '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/ggg/hhh.txt' }, { 'type': 'folder', 'path': 'd:\\中文路徑1' }, { 'type': 'folder', 'path': '/中文路徑2' }, { 'type': 'file', 'path': '/中文路徑2/aaa/aaammm/abc/測試.txt' }]
let r5 = filepathToTree(fps5)
fs.writeFileSync('r5.json', JSON.stringify(r5), 'utf8')
console.log(r5)
console.log('\n\n')
// => {
//   treeItems: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   treeItemsFolder: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   kpPath: {
//     root: '0',
//     'root❯aaa': '0.children.0',
//     'root❯aaaa': '0.children.1',
//     'root❯c:': '0.children.2',
//     'root❯ccc': '0.children.3',
//     'root❯d:': '0.children.4',
//     'root❯eee': '0.children.5',
//     'root❯ggg': '0.children.6',
//     'root❯mmm': '0.children.7',
//     'root❯中文路徑2': '0.children.8',
//     'root❯aaaa❯mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff': '0.children.1.children.0',
//     'root❯aaa❯aaammm': '0.children.0.children.0',
//     'root❯c:❯hhh': '0.children.2.children.0',
//     'root❯ccc❯cccddd': '0.children.3.children.0',
//     'root❯d:❯中文路徑1': '0.children.4.children.0',
//     'root❯eee❯eeefff1': '0.children.5.children.0',
//     'root❯eee❯eeefff2': '0.children.5.children.1',
//     'root❯中文路徑2❯aaa': '0.children.8.children.0',
//     'root❯aaaa❯mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff❯ggg': '0.children.1.children.0.children.0',
//     'root❯aaa❯aaammm❯abc': '0.children.0.children.0.children.0',
//     'root❯中文路徑2❯aaa❯aaammm': '0.children.8.children.0.children.0',
//     'root❯aaa❯aaammm❯abc❯abcd': '0.children.0.children.0.children.0.children.0',
//     'root❯aaa❯aaammm❯abc❯abcde': '0.children.0.children.0.children.0.children.1',
//     'root❯中文路徑2❯aaa❯aaammm❯abc': '0.children.8.children.0.children.0.children.0',
//     'root❯aaa1.txt': '0.children.9',
//     'root❯aaa2.txt': '0.children.10',
//     'root❯aaa❯aaammm.txt': '0.children.0.children.1',
//     'root❯aaaa❯mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff❯gfedcba.txt': '0.children.1.children.0.children.1',
//     'root❯aaa❯aaammm❯aaammmccc.txt': '0.children.0.children.0.children.1',
//     'root❯ccc❯cccddd❯cccdddeee.txt': '0.children.3.children.0.children.0',
//     'root❯aaaa❯mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff❯ggg❯hhh.txt': '0.children.1.children.0.children.0.children.0',
//     'root❯aaa❯aaammm❯abc❯abcd1.txt': '0.children.0.children.0.children.0.children.2',
//     'root❯aaa❯aaammm❯abc❯abcd2.txt': '0.children.0.children.0.children.0.children.3',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdef1.txt': '0.children.0.children.0.children.0.children.1.children.0',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdef2.txt': '0.children.0.children.0.children.0.children.1.children.1',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdef3 aaa mmm ccc dddddd eeeeeee fffffffffff ggggggggggggg.txt': '0.children.0.children.0.children.0.children.1.children.2',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg01.txt': '0.children.0.children.0.children.0.children.1.children.3',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg02.txt': '0.children.0.children.0.children.0.children.1.children.4',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg03.txt': '0.children.0.children.0.children.0.children.1.children.5',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg04.txt': '0.children.0.children.0.children.0.children.1.children.6',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg05.txt': '0.children.0.children.0.children.0.children.1.children.7',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg06.txt': '0.children.0.children.0.children.0.children.1.children.8',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg07.txt': '0.children.0.children.0.children.0.children.1.children.9',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg08.txt': '0.children.0.children.0.children.0.children.1.children.10',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg09.txt': '0.children.0.children.0.children.0.children.1.children.11',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg10.txt': '0.children.0.children.0.children.0.children.1.children.12',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg11.txt': '0.children.0.children.0.children.0.children.1.children.13',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg12.txt': '0.children.0.children.0.children.0.children.1.children.14',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg13.txt': '0.children.0.children.0.children.0.children.1.children.15',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg14.txt': '0.children.0.children.0.children.0.children.1.children.16',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg15.txt': '0.children.0.children.0.children.0.children.1.children.17',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg16.txt': '0.children.0.children.0.children.0.children.1.children.18',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg17.txt': '0.children.0.children.0.children.0.children.1.children.19',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg18.txt': '0.children.0.children.0.children.0.children.1.children.20',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg19.txt': '0.children.0.children.0.children.0.children.1.children.21',
//     'root❯aaa❯aaammm❯abc❯abcde❯abcdefg20.txt': '0.children.0.children.0.children.0.children.1.children.22',
//     'root❯aaa❯aaammm❯abc❯abcd❯abcde.txt': '0.children.0.children.0.children.0.children.0.children.0',
//     'root❯中文路徑2❯aaa❯aaammm❯abc❯測試.txt': '0.children.8.children.0.children.0.children.0.children.0'
//   },
//   fpsNormalize: [
//     { type: 'folder', path: '/root/aaa' },
//     { type: 'file', path: '/root/aaa1.txt' },
//     { type: 'file', path: '/root/aaa2.txt' },
//     { type: 'folder', path: '/root/aaa/aaammm' },
//     { type: 'file', path: '/root/aaa/aaammm.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/aaammmccc.txt' },
//     { type: 'folder', path: '/root/aaa/aaammm/abc' },
//     { type: 'folder', path: '/root/aaa/aaammm/abc/abcd' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcd/abcde.txt' },
//     { type: 'folder', path: '/root/aaa/aaammm/abc/abcde' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdef1.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdef2.txt' },
//     {
//       type: 'file',
//       path: '/root/aaa/aaammm/abc/abcde/abcdef3 aaa mmm ccc dddddd eeeeeee fffffffffff ggggggggggggg.txt'
//     },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg01.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg02.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg03.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg04.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg05.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg06.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg07.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg08.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg09.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg10.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg11.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg12.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg13.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg14.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg15.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg16.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg17.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg18.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg19.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcde/abcdefg20.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcd1.txt' },
//     { type: 'file', path: '/root/aaa/aaammm/abc/abcd2.txt' },
//     { type: 'folder', path: '/root/mmm' },
//     { type: 'folder', path: '/root/ccc' },
//     { type: 'folder', path: '/root/ccc/cccddd' },
//     { type: 'file', path: '/root/ccc/cccddd/cccdddeee.txt' },
//     { type: 'folder', path: '/root/eee' },
//     { type: 'folder', path: '/root/eee/eeefff1' },
//     { type: 'folder', path: '/root/eee/eeefff2' },
//     { type: 'folder', path: '/root/ggg' },
//     { type: 'folder', path: '/root/c:' },
//     { type: 'folder', path: '/root/c:/hhh' },
//     { type: 'folder', path: '/root/aaaa' },
//     {
//       type: 'folder',
//       path: '/root/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff'
//     },
//     {
//       type: 'file',
//       path: '/root/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/gfedcba.txt'
//     },
//     {
//       type: 'folder',
//       path: '/root/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/ggg'
//     },
//     {
//       type: 'file',
//       path: '/root/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/ggg/hhh.txt'
//     },
//     { type: 'folder', path: '/root/d:' },
//     { type: 'folder', path: '/root/d:/中文路徑1' },
//     { type: 'folder', path: '/root/中文路徑2' },
//     { type: 'folder', path: '/root/中文路徑2/aaa' },
//     { type: 'folder', path: '/root/中文路徑2/aaa/aaammm' },
//     { type: 'folder', path: '/root/中文路徑2/aaa/aaammm/abc' },
//     { type: 'file', path: '/root/中文路徑2/aaa/aaammm/abc/測試.txt' }
//   ]
// }

let fps6 = [{ 'type': 'folder', 'path': '/aaa1' }, { 'type': 'folder', 'path': '/aaa2' }, { 'type': 'folder', 'path': '/aaa10' }, { 'type': 'file', 'path': '/aaa/mmm/z1.txt' }, { 'type': 'file', 'path': '/aaa/mmm/z2.txt' }, { 'type': 'file', 'path': '/aaa/mmm/z10.txt' }]
let soryItems6 = (rs, pid, ns) => {
    // console.log('soryItems', 'pid=', pid, 'ns=', ns, 'rs=', rs)
    rs = arrSort(rs, { compareKey: 'text' })
    return rs
}
let r6 = filepathToTree(fps6, { soryItems: soryItems6 })
fs.writeFileSync('r6.json', JSON.stringify(r6), 'utf8')
console.log(r6)
console.log('\n\n')
// => {
//   treeItems: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   treeItemsFolder: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   kpPath: {
//     root: '0',
//     'root❯aaa': '0.children.0',
//     'root❯aaa1': '0.children.1',
//     'root❯aaa10': '0.children.2',
//     'root❯aaa2': '0.children.3',
//     'root❯aaa❯mmm': '0.children.0.children.0',
//     'root❯aaa❯mmm❯z1.txt': '0.children.0.children.0.children.0',
//     'root❯aaa❯mmm❯z2.txt': '0.children.0.children.0.children.1',
//     'root❯aaa❯mmm❯z10.txt': '0.children.0.children.0.children.2'
//   },
//   fpsNormalize: [
//     { type: 'folder', path: '/root/aaa1' },
//     { type: 'folder', path: '/root/aaa2' },
//     { type: 'folder', path: '/root/aaa10' },
//     { type: 'folder', path: '/root/aaa' },
//     { type: 'folder', path: '/root/aaa/mmm' },
//     { type: 'file', path: '/root/aaa/mmm/z1.txt' },
//     { type: 'file', path: '/root/aaa/mmm/z2.txt' },
//     { type: 'file', path: '/root/aaa/mmm/z10.txt' }
//   ]
// }

let fps7 = [{ 'type': 'folder', 'path': '/aaa1' }, { 'type': 'folder', 'path': '/aaa2' }, { 'type': 'folder', 'path': '/aaa10' }, { 'type': 'file', 'path': '/aaa/mmm/z1.txt' }, { 'type': 'file', 'path': '/aaa/mmm/z2.txt' }, { 'type': 'file', 'path': '/aaa/mmm/z10.txt' }]
let soryItems7 = (rs, pid, ns) => {
    // console.log('soryItems', 'pid=', pid, 'ns=', ns, 'rs=', rs)
    rs = arrSort(rs, { compareKey: 'text', localeCompare: true })
    return rs
}
let r7 = filepathToTree(fps7, { soryItems: soryItems7 })
fs.writeFileSync('r7.json', JSON.stringify(r7), 'utf8')
console.log(r7)
console.log('\n\n')
// => {
//   treeItems: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   treeItemsFolder: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   kpPath: {
//     root: '0',
//     'root❯aaa': '0.children.0',
//     'root❯aaa1': '0.children.1',
//     'root❯aaa2': '0.children.2',
//     'root❯aaa10': '0.children.3',
//     'root❯aaa❯mmm': '0.children.0.children.0',
//     'root❯aaa❯mmm❯z1.txt': '0.children.0.children.0.children.0',
//     'root❯aaa❯mmm❯z2.txt': '0.children.0.children.0.children.1',
//     'root❯aaa❯mmm❯z10.txt': '0.children.0.children.0.children.2'
//   },
//   fpsNormalize: [
//     { type: 'folder', path: '/root/aaa1' },
//     { type: 'folder', path: '/root/aaa2' },
//     { type: 'folder', path: '/root/aaa10' },
//     { type: 'folder', path: '/root/aaa' },
//     { type: 'folder', path: '/root/aaa/mmm' },
//     { type: 'file', path: '/root/aaa/mmm/z1.txt' },
//     { type: 'file', path: '/root/aaa/mmm/z2.txt' },
//     { type: 'file', path: '/root/aaa/mmm/z10.txt' }
//   ]
// }

//先建虛擬資料夾再補建資料夾
let fps8 = [
    { 'type': 'folder', 'path': '/aaa', 'ext': 'ext1' },
    { 'type': 'file', 'path': '/aaa/mmm/z1.txt', 'ext': 'ext2' },
    { 'type': 'folder', 'path': '/aaa/mmm', 'ext': 'ext3' },
]
let r8 = filepathToTree(fps8)
fs.writeFileSync('r8.json', JSON.stringify(r8), 'utf8')
console.log(r8)
console.log('\n\n')
// => {
//   treeItems: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   treeItemsFolder: [
//     {
//       _indOri: null,
//       _indNormalize: null,
//       ns: 1,
//       ts: [Array],
//       pathInfors: [Array],
//       _type: 'folder',
//       type: 'array',
//       numOfChilren: -1,
//       id: 'root',
//       parentId: '',
//       text: 'root',
//       children: [Array],
//       data: null
//     }
//   ],
//   kpPath: {
//     root: '0',
//     'root❯aaa': '0.children.0',
//     'root❯aaa❯mmm': '0.children.0.children.0',
//     'root❯aaa❯mmm❯z1.txt': '0.children.0.children.0.children.0'
//   },
//   fpsNormalize: [
//     { type: 'folder', path: '/root/aaa', ext: 'ext1' },
//     { type: 'folder', path: '/root/aaa/mmm', ext: 'ext3' },
//     { type: 'file', path: '/root/aaa/mmm/z1.txt', ext: 'ext2' }
//   ]
// }

//先建虛擬資料夾再補建檔案須報錯
let fps9 = [
    { 'type': 'folder', 'path': '/aaa', 'ext': 'ext1' },
    { 'type': 'file', 'path': '/aaa/mmm/z1.txt', 'ext': 'ext2' },
    { 'type': 'file', 'path': '/aaa/mmm', 'ext': 'ext3' },
]
let r9
try {
    r9 = filepathToTree(fps9)
}
catch (err) {
    r9 = err.toString()
}
fs.writeFileSync('r9.json', JSON.stringify(r9), 'utf8')
console.log(r9)
console.log('\n\n')
// => Error: id[root❯aaa❯mmm] is unrecognized from a folder to file

//先建檔案再補建虛擬資料夾須報錯
let fps10 = [
    { 'type': 'folder', 'path': '/aaa', 'ext': 'ext1' },
    { 'type': 'file', 'path': '/aaa/mmm', 'ext': 'ext3' },
    { 'type': 'file', 'path': '/aaa/mmm/z1.txt', 'ext': 'ext2' },
]
let r10
try {
    r10 = filepathToTree(fps10)
}
catch (err) {
    r10 = err.toString()
}
fs.writeFileSync('r10.json', JSON.stringify(r10), 'utf8')
console.log(r10)
console.log('\n\n')
// => Error: id[root❯aaa❯mmm] is unrecognized from a file to folder

//先建檔案再補建資料夾須報錯
let fps11 = [
    { 'type': 'folder', 'path': '/aaa', 'ext': 'ext1' },
    { 'type': 'file', 'path': '/aaa/mmm', 'ext': 'ext3' },
    { 'type': 'folder', 'path': '/aaa/mmm', 'ext': 'ext4' },
    { 'type': 'file', 'path': '/aaa/mmm/z1.txt', 'ext': 'ext2' },
]
let r11
try {
    r11 = filepathToTree(fps11)
}
catch (err) {
    r11 = err.toString()
}
fs.writeFileSync('r11.json', JSON.stringify(r11), 'utf8')
console.log(r11)
console.log('\n\n')
// => Error: id[root❯aaa❯mmm] is unrecognized from a file to folder
Parameters:
Name Type Attributes Default Description
fps Array

輸入項目陣列,每個項目需為物件,並提供path與type兩鍵值,其中type需為'folder'或'file'

opt Object <optional>
{}

輸入設定物件,預設{}

delimiter String <optional>
'❯'

輸入路徑分隔字串,預設'❯'

bindRoot String <optional>
''

輸入指定根目錄字串,預設''

soryItems function <optional>
null

輸入排序產生樹狀物件函數,預設null

Returns:

回傳結果物件,其內treeItems為資料夾與檔案之樹狀物件,treeItemsFolder為僅資料夾之樹狀物件,kpPath為各項目id之取樹狀物件路徑物件,fpsNormalize為重新正規化之檔案路徑陣列

Type
Object

(static) files2data(files) → {Promise}

Description:
  • 前端input檔案物件陣列轉資料物件陣列,各檔案將轉為Base64字串

    Unit Test: Github

Source:
Example
need test in browser
Parameters:
Name Type Description
files Array

輸入File陣列

Returns:

回傳Promise,resolve回傳File的資料物件陣列,各檔案將轉為Base64字串

Type
Promise

(static) flattenToConn(data, optopt, bindKeyopt, bindParentopt, bindBelongopt, bindTextopt, bindTypeopt, bindNumOfChilrenopt) → {Array}

Description:
  • 展平物件或陣列成為關聯陣列,真陣列(含帶其他Symbol.toStringTag者)依索引,物件依自有鍵(length視為一般鍵),以Symbol.toStringTag偽稱Array之物件視為物件

    Unit Test: Github

Source:
Example
let r

let obj = {
    a: 1,
    b: 12.3,
    c: 'abc',
    d: '45-de',
    x: true,
    y: null,
    z: function() {},
    e: [],
    f: [
        91,
        912.3,
        'abc',
        '945-de',
        true,
        null,
        function() {},
        [
            5,
            54.3,
            'xyz',
        ]
    ],
    g: {},
    h: {
        ga: 81,
        gb: 812.3,
        gc: 'abc',
        gd: '845-de',
        ge: [
            71,
            712.3,
            'abc',
            '745-de',
            true,
            null,
            function() {},
        ],
        gf: {
            gfa: 61,
            gfb: 612.3,
            gfc: 'abc',
            gfd: '645-de',
            gfe: true,
            gff: null,
            gfg: function() {},
        },
        gx: true,
        gy: null,
        gz: function() {},
    },
    i: Symbol('foo'),
    [Symbol('i-sym-key-a')]: 'i-sym-value',
    [Symbol('i-sym-key-b')]: {
        symfa: 61,
        symfb: 612.3,
        symfc: 'abc',
        symfd: '645-de',
        symfe: true,
        symff: null,
        symfg: function() {},
    },
}
r = flattenToConn(obj)
console.log('flattenToConn obj', r)
// => flattenToConn obj [
//   { id: 'a', parentId: '', type: 'node', key: 'a', text: 1 },
//   { id: 'b', parentId: '', type: 'node', key: 'b', text: 12.3 },
//   { id: 'c', parentId: '', type: 'node', key: 'c', text: 'abc' },
//   { id: 'd', parentId: '', type: 'node', key: 'd', text: '45-de' },
//   { id: 'x', parentId: '', type: 'node', key: 'x', text: true },
//   { id: 'y', parentId: '', type: 'node', key: 'y', text: null },
//   {
//     id: 'z',
//     parentId: '',
//     type: 'node',
//     key: 'z',
//     text: [Function: z]
//   },
//   { id: 'e', parentId: '', type: 'array', key: 'e', numOfChilren: 0 },
//   { id: 'f', parentId: '', type: 'array', key: 'f', numOfChilren: 8 },
//   { id: 'f-0', parentId: 'f', type: 'node', key: 0, text: 91 },
//   { id: 'f-1', parentId: 'f', type: 'node', key: 1, text: 912.3 },
//   { id: 'f-2', parentId: 'f', type: 'node', key: 2, text: 'abc' },
//   { id: 'f-3', parentId: 'f', type: 'node', key: 3, text: '945-de' },
//   { id: 'f-4', parentId: 'f', type: 'node', key: 4, text: true },
//   { id: 'f-5', parentId: 'f', type: 'node', key: 5, text: null },
//   {
//     id: 'f-6',
//     parentId: 'f',
//     type: 'node',
//     key: 6,
//     text: [Function (anonymous)]
//   },
//   { id: 'f-7', parentId: 'f', type: 'array', key: 7, numOfChilren: 3 },
//   { id: 'f-7-0', parentId: 'f-7', type: 'node', key: 0, text: 5 },
//   { id: 'f-7-1', parentId: 'f-7', type: 'node', key: 1, text: 54.3 },
//   { id: 'f-7-2', parentId: 'f-7', type: 'node', key: 2, text: 'xyz' },
//   { id: 'g', parentId: '', type: 'object', key: 'g', numOfChilren: 0 },
//   { id: 'h', parentId: '', type: 'object', key: 'h', numOfChilren: 9 },
//   { id: 'h-ga', parentId: 'h', type: 'node', key: 'ga', text: 81 },
//   { id: 'h-gb', parentId: 'h', type: 'node', key: 'gb', text: 812.3 },
//   { id: 'h-gc', parentId: 'h', type: 'node', key: 'gc', text: 'abc' },
//   {
//     id: 'h-gd',
//     parentId: 'h',
//     type: 'node',
//     key: 'gd',
//     text: '845-de'
//   },
//   {
//     id: 'h-ge',
//     parentId: 'h',
//     type: 'array',
//     key: 'ge',
//     numOfChilren: 7
//   },
//   { id: 'h-ge-0', parentId: 'h-ge', type: 'node', key: 0, text: 71 },
//   { id: 'h-ge-1', parentId: 'h-ge', type: 'node', key: 1, text: 712.3 },
//   { id: 'h-ge-2', parentId: 'h-ge', type: 'node', key: 2, text: 'abc' },
//   {
//     id: 'h-ge-3',
//     parentId: 'h-ge',
//     type: 'node',
//     key: 3,
//     text: '745-de'
//   },
//   { id: 'h-ge-4', parentId: 'h-ge', type: 'node', key: 4, text: true },
//   { id: 'h-ge-5', parentId: 'h-ge', type: 'node', key: 5, text: null },
//   {
//     id: 'h-ge-6',
//     parentId: 'h-ge',
//     type: 'node',
//     key: 6,
//     text: [Function (anonymous)]
//   },
//   {
//     id: 'h-gf',
//     parentId: 'h',
//     type: 'object',
//     key: 'gf',
//     numOfChilren: 7
//   },
//   {
//     id: 'h-gf-gfa',
//     parentId: 'h-gf',
//     type: 'node',
//     key: 'gfa',
//     text: 61
//   },
//   {
//     id: 'h-gf-gfb',
//     parentId: 'h-gf',
//     type: 'node',
//     key: 'gfb',
//     text: 612.3
//   },
//   {
//     id: 'h-gf-gfc',
//     parentId: 'h-gf',
//     type: 'node',
//     key: 'gfc',
//     text: 'abc'
//   },
//   {
//     id: 'h-gf-gfd',
//     parentId: 'h-gf',
//     type: 'node',
//     key: 'gfd',
//     text: '645-de'
//   },
//   {
//     id: 'h-gf-gfe',
//     parentId: 'h-gf',
//     type: 'node',
//     key: 'gfe',
//     text: true
//   },
//   {
//     id: 'h-gf-gff',
//     parentId: 'h-gf',
//     type: 'node',
//     key: 'gff',
//     text: null
//   },
//   {
//     id: 'h-gf-gfg',
//     parentId: 'h-gf',
//     type: 'node',
//     key: 'gfg',
//     text: [Function: gfg]
//   },
//   { id: 'h-gx', parentId: 'h', type: 'node', key: 'gx', text: true },
//   { id: 'h-gy', parentId: 'h', type: 'node', key: 'gy', text: null },
//   {
//     id: 'h-gz',
//     parentId: 'h',
//     type: 'node',
//     key: 'gz',
//     text: [Function: gz]
//   },
//   { id: 'i', parentId: '', type: 'node', key: 'i', text: Symbol(foo) },
//   {
//     id: 'Symbol(i-sym-key-a)',
//     parentId: '',
//     type: 'node',
//     text: 'i-sym-value'
//   },
//   {
//     id: 'Symbol(i-sym-key-b)',
//     parentId: '',
//     type: 'object',
//     numOfChilren: 7
//   },
//   {
//     id: 'Symbol(i-sym-key-b)-symfa',
//     parentId: 'Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfa',
//     text: 61
//   },
//   {
//     id: 'Symbol(i-sym-key-b)-symfb',
//     parentId: 'Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfb',
//     text: 612.3
//   },
//   {
//     id: 'Symbol(i-sym-key-b)-symfc',
//     parentId: 'Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfc',
//     text: 'abc'
//   },
//   {
//     id: 'Symbol(i-sym-key-b)-symfd',
//     parentId: 'Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfd',
//     text: '645-de'
//   },
//   {
//     id: 'Symbol(i-sym-key-b)-symfe',
//     parentId: 'Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfe',
//     text: true
//   },
//   {
//     id: 'Symbol(i-sym-key-b)-symff',
//     parentId: 'Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symff',
//     text: null
//   },
//   {
//     id: 'Symbol(i-sym-key-b)-symfg',
//     parentId: 'Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfg',
//     text: [Function: symfg]
//   }
// ]

let arr = [
    91,
    912.3,
    'abc',
    '945-de',
    true,
    null,
    function() {},
    [
        5,
        54.3,
        'xyz',
        {
            gf: {
                gfa: 61,
                gfb: 612.3,
                gfc: 'abc',
                gfd: '645-de',
                gfe: true,
                gff: null,
                gfg: function() {},
            },
        },
    ],
    {
        h: {
            ga: 81,
            gb: 812.3,
            gc: 'abc',
            gd: '845-de',
            ge: [
                71,
                712.3,
                'abc',
                '745-de',
                true,
                null,
                function() {},
            ],
            gx: true,
            gy: null,
            gz: function() {},
        },
        i: Symbol('foo'),
        [Symbol('i-sym-key-a')]: 'i-sym-value',
        [Symbol('i-sym-key-b')]: {
            symfa: 61,
            symfb: 612.3,
            symfc: 'abc',
            symfd: '645-de',
            symfe: true,
            symff: null,
            symfg: function() {},
        },
    },
]
r = flattenToConn(arr)
console.log('flattenToConn arr', r)
// => flattenToConn arr [
//   { id: '0', parentId: '', type: 'node', key: 0, text: 91 },
//   { id: '1', parentId: '', type: 'node', key: 1, text: 912.3 },
//   { id: '2', parentId: '', type: 'node', key: 2, text: 'abc' },
//   { id: '3', parentId: '', type: 'node', key: 3, text: '945-de' },
//   { id: '4', parentId: '', type: 'node', key: 4, text: true },
//   { id: '5', parentId: '', type: 'node', key: 5, text: null },
//   {
//     id: '6',
//     parentId: '',
//     type: 'node',
//     key: 6,
//     text: [Function (anonymous)]
//   },
//   { id: '7', parentId: '', type: 'array', key: 7, numOfChilren: 4 },
//   { id: '7-0', parentId: '7', type: 'node', key: 0, text: 5 },
//   { id: '7-1', parentId: '7', type: 'node', key: 1, text: 54.3 },
//   { id: '7-2', parentId: '7', type: 'node', key: 2, text: 'xyz' },
//   { id: '7-3', parentId: '7', type: 'object', key: 3, numOfChilren: 1 },
//   {
//     id: '7-3-gf',
//     parentId: '7-3',
//     type: 'object',
//     key: 'gf',
//     numOfChilren: 7
//   },
//   {
//     id: '7-3-gf-gfa',
//     parentId: '7-3-gf',
//     type: 'node',
//     key: 'gfa',
//     text: 61
//   },
//   {
//     id: '7-3-gf-gfb',
//     parentId: '7-3-gf',
//     type: 'node',
//     key: 'gfb',
//     text: 612.3
//   },
//   {
//     id: '7-3-gf-gfc',
//     parentId: '7-3-gf',
//     type: 'node',
//     key: 'gfc',
//     text: 'abc'
//   },
//   {
//     id: '7-3-gf-gfd',
//     parentId: '7-3-gf',
//     type: 'node',
//     key: 'gfd',
//     text: '645-de'
//   },
//   {
//     id: '7-3-gf-gfe',
//     parentId: '7-3-gf',
//     type: 'node',
//     key: 'gfe',
//     text: true
//   },
//   {
//     id: '7-3-gf-gff',
//     parentId: '7-3-gf',
//     type: 'node',
//     key: 'gff',
//     text: null
//   },
//   {
//     id: '7-3-gf-gfg',
//     parentId: '7-3-gf',
//     type: 'node',
//     key: 'gfg',
//     text: [Function: gfg]
//   },
//   { id: '8', parentId: '', type: 'object', key: 8, numOfChilren: 2 },
//   {
//     id: '8-h',
//     parentId: '8',
//     type: 'object',
//     key: 'h',
//     numOfChilren: 8
//   },
//   { id: '8-h-ga', parentId: '8-h', type: 'node', key: 'ga', text: 81 },
//   {
//     id: '8-h-gb',
//     parentId: '8-h',
//     type: 'node',
//     key: 'gb',
//     text: 812.3
//   },
//   {
//     id: '8-h-gc',
//     parentId: '8-h',
//     type: 'node',
//     key: 'gc',
//     text: 'abc'
//   },
//   {
//     id: '8-h-gd',
//     parentId: '8-h',
//     type: 'node',
//     key: 'gd',
//     text: '845-de'
//   },
//   {
//     id: '8-h-ge',
//     parentId: '8-h',
//     type: 'array',
//     key: 'ge',
//     numOfChilren: 7
//   },
//   {
//     id: '8-h-ge-0',
//     parentId: '8-h-ge',
//     type: 'node',
//     key: 0,
//     text: 71
//   },
//   {
//     id: '8-h-ge-1',
//     parentId: '8-h-ge',
//     type: 'node',
//     key: 1,
//     text: 712.3
//   },
//   {
//     id: '8-h-ge-2',
//     parentId: '8-h-ge',
//     type: 'node',
//     key: 2,
//     text: 'abc'
//   },
//   {
//     id: '8-h-ge-3',
//     parentId: '8-h-ge',
//     type: 'node',
//     key: 3,
//     text: '745-de'
//   },
//   {
//     id: '8-h-ge-4',
//     parentId: '8-h-ge',
//     type: 'node',
//     key: 4,
//     text: true
//   },
//   {
//     id: '8-h-ge-5',
//     parentId: '8-h-ge',
//     type: 'node',
//     key: 5,
//     text: null
//   },
//   {
//     id: '8-h-ge-6',
//     parentId: '8-h-ge',
//     type: 'node',
//     key: 6,
//     text: [Function (anonymous)]
//   },
//   {
//     id: '8-h-gx',
//     parentId: '8-h',
//     type: 'node',
//     key: 'gx',
//     text: true
//   },
//   {
//     id: '8-h-gy',
//     parentId: '8-h',
//     type: 'node',
//     key: 'gy',
//     text: null
//   },
//   {
//     id: '8-h-gz',
//     parentId: '8-h',
//     type: 'node',
//     key: 'gz',
//     text: [Function: gz]
//   },
//   {
//     id: '8-i',
//     parentId: '8',
//     type: 'node',
//     key: 'i',
//     text: Symbol(foo)
//   },
//   {
//     id: '8-Symbol(i-sym-key-a)',
//     parentId: '8',
//     type: 'node',
//     text: 'i-sym-value'
//   },
//   {
//     id: '8-Symbol(i-sym-key-b)',
//     parentId: '8',
//     type: 'object',
//     numOfChilren: 7
//   },
//   {
//     id: '8-Symbol(i-sym-key-b)-symfa',
//     parentId: '8-Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfa',
//     text: 61
//   },
//   {
//     id: '8-Symbol(i-sym-key-b)-symfb',
//     parentId: '8-Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfb',
//     text: 612.3
//   },
//   {
//     id: '8-Symbol(i-sym-key-b)-symfc',
//     parentId: '8-Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfc',
//     text: 'abc'
//   },
//   {
//     id: '8-Symbol(i-sym-key-b)-symfd',
//     parentId: '8-Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfd',
//     text: '645-de'
//   },
//   {
//     id: '8-Symbol(i-sym-key-b)-symfe',
//     parentId: '8-Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfe',
//     text: true
//   },
//   {
//     id: '8-Symbol(i-sym-key-b)-symff',
//     parentId: '8-Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symff',
//     text: null
//   },
//   {
//     id: '8-Symbol(i-sym-key-b)-symfg',
//     parentId: '8-Symbol(i-sym-key-b)',
//     type: 'node',
//     key: 'symfg',
//     text: [Function: symfg]
//   }
// ]
Parameters:
Name Type Attributes Default Description
data Array | Object

輸入項目物件或陣列

opt Object <optional>
{}

輸入設定物件,預設{}

bindKey String <optional>
'id'

輸入識別用欄位字串,預設'id'

bindParent String <optional>
'parentId'

輸入存放父節點識別用欄位字串,預設'parentId'

bindBelong String <optional>
'key'

輸入當父節點為物件或陣列時,存放自身所屬鍵值字串,預設'key'

bindText String <optional>
'text'

輸入存放值欄位字串,預設'text'

bindType String <optional>
'type'

輸入存放值種類欄位字串,預設'type'

bindNumOfChilren String <optional>
'numOfChilren'

輸入存放值為物件或陣列時所屬子節點數量欄位字串,預設'numOfChilren'

Returns:

回傳關聯陣列

Type
Array

(static) flattenTree(data, optopt, bindKeyopt, bindChildrenopt) → {Array}

Description:
  • 展平樹狀物件或陣列,以Symbol.toStringTag偽稱Array之物件視為物件

    Unit Test: Github

Source:
Example
let obj = {
    id: 1,
    data: '1-abc',
    children: [
        {
            id: 2,
            data: '2-def',
        },
        {
            id: 3,
            data: '3-ghi',
            children: [
                {
                    id: 4,
                    data: '4-jkl',
                },
            ],
        },
        {
            id: 5,
            data: '5-mno',
        },
    ],
}
let crobj = flattenTree(obj)
console.log(JSON.stringify(crobj))
// => {"id":1,"data":"1-abc","children":[{"id":2,"data":"2-def"},{"id":3,"data":"3-ghi","children":[{"id":4,"data":"4-jkl"}]},{"id":5,"data":"5-mno"}],"level":0,"nk":[0]}

let arr = [
    {
        id: 1,
        text: '1x',
    },
    {
        id: 2,
        text: '2y',
        children: [
            {
                id: 3,
                text: '3z',
            },
        ],
    },
    {
        id: 4,
        text: 'empty',
    },
]
let crarr = flattenTree(arr)
console.log(JSON.stringify(crarr))
// => [{"id":1,"text":"1x","level":0,"nk":[0]},{"id":2,"text":"2y","children":[{"id":3,"text":"3z"}],"level":0,"nk":[1]},{"id":3,"text":"3z","level":1,"nk":[1,"children",0]},{"id":4,"text":"empty","level":0,"nk":[2]}]
Parameters:
Name Type Attributes Default Description
data Array | Object

輸入樹狀物件或陣列

opt Object <optional>
{}

輸入設定物件,預設{}

bindKey String <optional>
'id'

輸入節點物件識別用欄位字串,預設'id'

bindChildren String <optional>
'children'

輸入節點物件內存放子節點欄位字串,預設'children'

Returns:

回傳節點物件陣列

Type
Array

(static) floor(v, idigopt) → {Number}

Description:
  • 數字或字串無條件捨去至指定位數(0位為整數)

    Unit Test: Github

Source:
Example
console.log(floor(1.5))
// => 1

console.log(floor('1.5'))
// => 1

console.log(floor(-1.5))
// => -2

console.log(floor('125abc'))
// => ''

console.log(floor(9.83501, 2))
// => 9.83

console.log(floor(9.8350, 2))
// => 9.83

console.log(floor(9.82501, 2))
// => 9.82

console.log(floor(9.8250, 2))
// => 9.82

console.log(floor(9.8249, 2))
// => 9.82

console.log(floor(12.6449, 2))
// => 12.64

console.log(floor(12.645, 2))
// => 12.64

console.log(floor(12.6451, 2))
// => 12.64

console.log(floor(12.65, 2))
// => 12.65

console.log(floor(12.64, 2))
// => 12.64

console.log(floor(12.6, 2))
// => 12.6

console.log(floor(12, 2))
// => 12
Parameters:
Name Type Attributes Default Description
v Number | String

輸入數字或字串

idig Integer <optional>
0

輸入指定位數整數,預設0

Returns:

回傳無條件進位的數字

Type
Number

(static) fsBuildReadStreamText(fp) → {EventEmitter}

Description:
  • 使用stream從檔案讀utf8文字

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = async () => {

    let ms = []

    let fdt = './_test_fsBuildReadStreamText'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/${fn}`

    fs.writeFileSync(fp, `abc
測試中文
def123xyz
`, 'utf8')

    let ev = fsBuildReadStreamText(fp)

    ev.on('create', () => {
        console.log('create')
        ms.push({ 'create': '' })
    })

    ev.on('line', (line) => {
        console.log('line', line)
        ms.push({ line })
    })

    let pm = genPm()
    ev.on('close', () => {
        console.log('close')
        pm.resolve()
        ms.push({ 'close': '' })
    })
    await pm

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
// create
// line abc
// line 測試中文
// line def123xyz
// close
// ms [
//   { create: '' },
//   { line: 'abc' },
//   { line: '測試中文' },
//   { line: 'def123xyz' },
//   { close: '' }
// ]
Parameters:
Name Type Description
fp String

輸入讀取檔案路徑字串

Returns:

回傳EventEmitter,可監聽create、line、close、error事件,line事件接收讀入各列字串,create於回傳後延後派發故可被監聽。底層stream出錯(如開檔時檔案已消失、無讀取權限)以error事件回報{ fun: 'stream', msg }且隨後仍派發close。事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯,故其不會使行程崩潰,會改以error事件回報{ fun: 'listener', name, msg, args },無error監聽者則console.error;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection,監聽error時請先以fun欄位分流。fp非檔案(不存在、為資料夾)時同步拋出Error物件(fp is not a file)而不回傳EventEmitter,呼叫端須自行try

Type
EventEmitter

(static) fsBuildWriteStreamText() → {Object}

Description:
  • 使用stream寫入utf8文字至檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = async () => {

    let ms = []

    let fdt = './_test_fsBuildWriteStreamText'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/${fn}`

    let bdw = fsBuildWriteStreamText()

    let pm = bdw.create(fp)
    ms.push({ 'create': '' })

    bdw.write('abc')
    ms.push({ 'write': 'abc' })
    bdw.write('中文')
    ms.push({ 'write': '中文' })
    bdw.end()

    await pm

    let c = fs.readFileSync(fp, 'utf8')
    ms.push({ 'readFileSync': c })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
// ms [
//   { create: '' },
//   { write: 'abc' },
//   { write: '中文' },
//   { readFileSync: 'abc\n中文\n' }
// ]
Returns:

回傳函數物件,提供creat、write、end函數,create代表創建stream,可輸入fp為檔案路徑字串,回傳Promise,寫入完成(finish)時resolve,開檔或寫入失敗(含fp無效)時reject錯誤訊息字串,不同步拋出,該Promise已標記為已處理(呼叫端未接時不成為未處理之rejection,await者仍收到reject),write代表寫入字串,輸入c為字串(預設為行),須於create後呼叫(未create即呼叫則同步拋錯),create失敗後呼叫則不動作,end代表結束stream,無輸入,回傳Promise,於stream關閉(close)後resolve,未create或stream已關閉(如開檔失敗)時直接resolve

Type
Object

(static) fsCleanFolder(pah) → {Object}

Description:
  • 後端nodejs清空資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsCleanFolder'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    fsCreateFolder(`${fdt}/abc1`)
    fsCreateFolder(`${fdt}/def1/def2/def3`)
    fsCreateFile(`${fdt}/zzz.txt`, 'zzz')
    fsCreateFile(`${fdt}/def1/def2/def3/def3.txt`, 'def')

    let b1 = fsIsFile(`${fdt}/zzz.txt`)
    console.log('fsIsFile1(before)', b1)
    ms.push({ 'fsIsFile1(before)': b1 })

    let b2 = fsIsFile(`${fdt}/def1/def2/def3/def3.txt`)
    console.log('fsIsFile2(before)', b2)
    ms.push({ 'fsIsFile2(before)': b2 })

    let b3 = fsIsFolder(`${fdt}/abc1`)
    console.log('fsIsFolder1(before)', b3)
    ms.push({ 'fsIsFolder1(before)': b3 })

    let b4 = fsIsFolder(`${fdt}/def1/def2/def3`)
    console.log('fsIsFolder2(before)', b4)
    ms.push({ 'fsIsFolder2(before)': b4 })

    let r = fsCleanFolder(fdt)
    ms.push({ 'fsCleanFolder': r })

    let b5 = fsIsFile(`${fdt}/zzz.txt`)
    console.log('fsIsFile1(after)', b5)
    ms.push({ 'fsIsFile1(after)': b5 })

    let b6 = fsIsFile(`${fdt}/def1/def2/def3/def3.txt`)
    console.log('fsIsFile2(after)', b6)
    ms.push({ 'fsIsFile2(after)': b6 })

    let b7 = fsIsFolder(`${fdt}/abc1`)
    console.log('fsIsFolder1(after)', b7)
    ms.push({ 'fsIsFolder1(after)': b7 })

    let b8 = fsIsFolder(`${fdt}/def1/def2/def3`)
    console.log('fsIsFolder2(after)', b8)
    ms.push({ 'fsIsFolder2(after)': b8 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsIsFile1(before) true
// fsIsFile2(before) true
// fsIsFolder1(before) true
// fsIsFolder2(before) true
// fsIsFile1(after) false
// fsIsFile2(after) false
// fsIsFolder1(after) false
// fsIsFolder2(after) false
// ms [
//   { 'fsIsFile1(before)': true },
//   { 'fsIsFile2(before)': true },
//   { 'fsIsFolder1(before)': true },
//   { 'fsIsFolder2(before)': true },
//   { fsCleanFolder: { success: 'done: ./_test_fsCleanFolder' } },
//   { 'fsIsFile1(after)': false },
//   { 'fsIsFile2(after)': false },
//   { 'fsIsFolder1(after)': false },
//   { 'fsIsFolder2(after)': false }
// ]
Parameters:
Name Type Description
pah String

輸入欲清空資料夾路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件);資料夾不存在時自動建立;資料夾內之連結(符號連結、junction,含目標已不存在者)只移除連結本身不跟隨;目標本身為連結時不清空並回傳error

Type
Object

(static) fsCleanFolderCore(pah) → {Object}

Description:
  • 後端nodejs清空資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsCleanFolder
Parameters:
Name Type Description
pah String

輸入欲清空資料夾路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件);資料夾不存在時自動建立;資料夾內之連結(符號連結、junction,含目標已不存在者)只移除連結本身不跟隨;目標本身為連結時不清空並回傳error

Type
Object

(static) fsCopyFile(fpSrc, fpTar, optopt) → {Object|Promise}

Description:
  • 後端nodejs複製檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

let testSync = async () => {
    let ms = []

    let fpSrc = './_test_fsCopyFile_src'
    let fpTar = './_test_fsCopyFile_tar'
    fsCreateFolder(fpSrc)
    fsCreateFolder(fpTar)

    fs.writeFileSync(`${fpSrc}/t1.txt`, 'abc', 'utf8')

    let rc = fsCopyFile(`${fpSrc}/t1.txt`, `${fpTar}/_t1.txt`)
    ms.push({ 'sync-copy-file': rc })

    let b1 = fsIsFile(`${fpTar}/_t1.txt`)
    ms.push({ 'sync-is-file': b1 })

    fsDeleteFolder(fpSrc)
    fsDeleteFolder(fpTar)

    // console.log('ms', ms)
    return ms
}

let testAsync = async () => {
    let ms = []

    let fpSrc = './_test_fsCopyFile_src'
    let fpTar = './_test_fsCopyFile_tar'
    fsCreateFolder(fpSrc)
    fsCreateFolder(fpTar)

    fs.writeFileSync(`${fpSrc}/t1.txt`, 'abc', 'utf8')

    await fsCopyFile(`${fpSrc}/t1.txt`, `${fpTar}/_t1.txt`, { useSync: false })
        .then((res) => {
            // console.log('res', res)
            ms.push({ 'async-copy-folder': res })
        })
        .catch((err) => {
            console.log('err', err)
        })

    let b1 = fsIsFile(`${fpTar}/_t1.txt`)
    ms.push({ 'async-is-file': b1 })

    fsDeleteFolder(fpSrc)
    fsDeleteFolder(fpTar)

    // console.log('ms', ms)
    return ms
}

let test = async () => {
    let ms = []
    let msSync = await testSync()
    ms = [...ms, ...msSync]
    let msAsync = await testAsync()
    ms = [...ms, ...msAsync]
    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// => ms [
//   {
//     'sync-copy-file': { success: 'done: ./_test_fsCopyFile_tar/abc/_t1.txt' }
//   },
//   { 'sync-is-file': false },
//   {
//     'async-copy-folder': { success: 'done: ./_test_fsCopyFile_tar/abc/_t1.txt' }
//   },
//   { 'async-is-file': false }
// ]
Parameters:
Name Type Attributes Default Description
fpSrc String

輸入來源檔案路徑字串

fpTar String

輸入目的檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useSync Boolean <optional>
true

輸入是否使用同步函數布林值,預設true

Returns:

若useSync=true回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),若useSync=false則回傳Promise,resolve回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),不觸發reject

Type
Object | Promise

(static) fsCopyFileCore(fpSrc, fpTar, optopt) → {Object|Promise}

Description:
  • 後端nodejs複製檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsCopyFile
Parameters:
Name Type Attributes Default Description
fpSrc String

輸入來源檔案路徑字串

fpTar String

輸入目的檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useSync Boolean <optional>
true

輸入是否使用同步函數布林值,預設true

Returns:

若useSync=true回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),若useSync=false則回傳Promise,resolve回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),不觸發reject

Type
Object | Promise

(static) fsCopyFolder(fpSrc, fpTar, optopt) → {Object|Promise}

Description:
  • 後端nodejs複製資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

let testSyncEmpty = async () => {
    let ms = []

    let fpSrc = './_test_fsCopyFolder_src'
    let fpTar = './_test_fsCopyFolder_tar'
    fsCreateFolder(fpSrc)
    fsDeleteFolder(fpTar)

    fsCreateFolder(`${fpSrc}/lay1/lay2`)
    fsCreateFolder(`${fpSrc}/lay1/lay2/lay3/lay4`)

    // fs.writeFileSync(`${fpSrc}/lay1/t1.txt`, 'abc', 'utf8')
    // fs.writeFileSync(`${fpSrc}/lay1/lay2/t2.txt`, 'def', 'utf8')
    // fs.writeFileSync(`${fpSrc}/lay1/lay2/lay3/t3.txt`, '中文', 'utf8')
    // fs.writeFileSync(`${fpSrc}/lay1/lay2/lay3/lay4/t4.txt`, '測 試', 'utf8')

    let rc = fsCopyFolder(fpSrc, fpTar)
    ms.push({ 'sync-empty-copy-folder': rc })

    let b1 = fsIsFolder(`${fpTar}/lay1/lay2/lay3`)
    ms.push({ 'sync-empty-is-folder-1': b1 })
    let b2 = fsIsFolder(`${fpTar}/lay1/lay2/lay3/lay4`)
    ms.push({ 'sync-empty-is-folder-2': b2 })
    let b3 = fsIsFile(`${fpTar}/lay1/lay2/lay3/t3.txt`)
    ms.push({ 'sync-empty-is-file-1': b3 })
    let b4 = fsIsFile(`${fpTar}/lay1/lay2/lay3/lay4/t4.txt`)
    ms.push({ 'sync-empty-is-file-2': b4 })

    fsDeleteFolder(fpSrc)
    fsDeleteFolder(fpTar)

    // console.log('ms', ms)
    return ms
}

let testSync = async () => {
    let ms = []

    let fpSrc = './_test_fsCopyFolder_src'
    let fpTar = './_test_fsCopyFolder_tar'
    fsCreateFolder(fpSrc)

    fsCreateFolder(`${fpSrc}/lay1/lay2`)
    fsCreateFolder(`${fpSrc}/lay1/lay2/lay3/lay4`)

    fs.writeFileSync(`${fpSrc}/lay1/t1.txt`, 'abc', 'utf8')
    fs.writeFileSync(`${fpSrc}/lay1/lay2/t2.txt`, 'def', 'utf8')
    fs.writeFileSync(`${fpSrc}/lay1/lay2/lay3/t3.txt`, '中文', 'utf8')
    fs.writeFileSync(`${fpSrc}/lay1/lay2/lay3/lay4/t4.txt`, '測 試', 'utf8')

    let rc = fsCopyFolder(fpSrc, fpTar)
    ms.push({ 'sync-copy-folder': rc })

    let b1 = fsIsFolder(`${fpTar}/lay1/lay2/lay3`)
    ms.push({ 'sync-is-folder-1': b1 })
    let b2 = fsIsFolder(`${fpTar}/lay1/lay2/lay3/lay4`)
    ms.push({ 'sync-is-folder-2': b2 })
    let b3 = fsIsFile(`${fpTar}/lay1/lay2/lay3/t3.txt`)
    ms.push({ 'sync-is-file-1': b3 })
    let b4 = fsIsFile(`${fpTar}/lay1/lay2/lay3/lay4/t4.txt`)
    ms.push({ 'sync-is-file-2': b4 })

    fsDeleteFolder(fpSrc)
    fsDeleteFolder(fpTar)

    // console.log('ms', ms)
    return ms
}

let testAsyncEmpty = async () => {
    let ms = []

    let fpSrc = './_test_fsCopyFolder_src'
    let fpTar = './_test_fsCopyFolder_tar'
    fsCreateFolder(fpSrc)

    fsCreateFolder(`${fpSrc}/lay1/lay2`)
    fsCreateFolder(`${fpSrc}/lay1/lay2/lay3/lay4`)

    // fs.writeFileSync(`${fpSrc}/lay1/t1.txt`, 'abc', 'utf8')
    // fs.writeFileSync(`${fpSrc}/lay1/lay2/t2.txt`, 'def', 'utf8')
    // fs.writeFileSync(`${fpSrc}/lay1/lay2/lay3/t3.txt`, '中文', 'utf8')
    // fs.writeFileSync(`${fpSrc}/lay1/lay2/lay3/lay4/t4.txt`, '測 試', 'utf8')

    await fsCopyFolder(fpSrc, fpTar, { useSync: false })
        .then((res) => {
            // console.log('res', res)
            ms.push({ 'async-empty-copy-folder': res })
        })
        .catch((err) => {
            console.log('err', err)
        })

    let b1 = fsIsFolder(`${fpTar}/lay1/lay2/lay3`)
    ms.push({ 'async-empty-is-folder-1': b1 })
    let b2 = fsIsFolder(`${fpTar}/lay1/lay2/lay3/lay4`)
    ms.push({ 'async-empty-is-folder-2': b2 })
    let b3 = fsIsFile(`${fpTar}/lay1/lay2/lay3/t3.txt`)
    ms.push({ 'async-empty-is-file-1': b3 })
    let b4 = fsIsFile(`${fpTar}/lay1/lay2/lay3/lay4/t4.txt`)
    ms.push({ 'async-empty-is-file-2': b4 })

    fsDeleteFolder(fpSrc)
    fsDeleteFolder(fpTar)

    // console.log('ms', ms)
    return ms
}

let testAsync = async () => {
    let ms = []

    let fpSrc = './_test_fsCopyFolder_src'
    let fpTar = './_test_fsCopyFolder_tar'
    fsCreateFolder(fpSrc)

    fsCreateFolder(`${fpSrc}/lay1/lay2`)
    fsCreateFolder(`${fpSrc}/lay1/lay2/lay3/lay4`)

    fs.writeFileSync(`${fpSrc}/lay1/t1.txt`, 'abc', 'utf8')
    fs.writeFileSync(`${fpSrc}/lay1/lay2/t2.txt`, 'def', 'utf8')
    fs.writeFileSync(`${fpSrc}/lay1/lay2/lay3/t3.txt`, '中文', 'utf8')
    fs.writeFileSync(`${fpSrc}/lay1/lay2/lay3/lay4/t4.txt`, '測 試', 'utf8')

    await fsCopyFolder(fpSrc, fpTar, { useSync: false })
        .then((res) => {
            // console.log('res', res)
            ms.push({ 'async-copy-folder': res })
        })
        .catch((err) => {
            console.log('err', err)
        })

    let b1 = fsIsFolder(`${fpTar}/lay1/lay2/lay3`)
    ms.push({ 'async-is-folder-1': b1 })
    let b2 = fsIsFolder(`${fpTar}/lay1/lay2/lay3/lay4`)
    ms.push({ 'async-is-folder-2': b2 })
    let b3 = fsIsFile(`${fpTar}/lay1/lay2/lay3/t3.txt`)
    ms.push({ 'async-is-file-1': b3 })
    let b4 = fsIsFile(`${fpTar}/lay1/lay2/lay3/lay4/t4.txt`)
    ms.push({ 'async-is-file-2': b4 })

    fsDeleteFolder(fpSrc)
    fsDeleteFolder(fpTar)

    // console.log('ms', ms)
    return ms
}

let test = async () => {
    let ms = []
    ms = [...ms, ...await testSyncEmpty()]
    ms = [...ms, ...await testSync()]
    ms = [...ms, ...await testAsyncEmpty()]
    ms = [...ms, ...await testAsync()]
    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// => ms [
//   {
//     'sync-empty-copy-folder': { success: 'done: ./_test_fsCopyFolder_tar' }
//   },
//   { 'sync-empty-is-folder-1': true },
//   { 'sync-empty-is-folder-2': true },
//   { 'sync-empty-is-file-1': false },
//   { 'sync-empty-is-file-2': false },
//   { 'sync-copy-folder': { success: 'done: ./_test_fsCopyFolder_tar' } },
//   { 'sync-is-folder-1': true },
//   { 'sync-is-folder-2': true },
//   { 'sync-is-file-1': true },
//   { 'sync-is-file-2': true },
//   {
//     'async-empty-copy-folder': { success: 'done: ./_test_fsCopyFolder_tar' }
//   },
//   { 'async-empty-is-folder-1': true },
//   { 'async-empty-is-folder-2': true },
//   { 'async-empty-is-file-1': false },
//   { 'async-empty-is-file-2': false },
//   {
//     'async-copy-folder': { success: 'done: ./_test_fsCopyFolder_tar' }
//   },
//   { 'async-is-folder-1': true },
//   { 'async-is-folder-2': true },
//   { 'async-is-file-1': true },
//   { 'async-is-file-2': true }
// ]
Parameters:
Name Type Attributes Default Description
fpSrc String

輸入來源資料夾路徑字串

fpTar String

輸入目的資料夾路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useSync Boolean <optional>
true

輸入是否使用同步函數布林值,預設true

Returns:

若useSync=true回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),若useSync=false則回傳Promise,resolve回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),不觸發reject

Type
Object | Promise

(static) fsCopyFolderCore(fpSrc, fpTar, optopt) → {Object|Promise}

Description:
  • 後端nodejs複製資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsCopyFolder
Parameters:
Name Type Attributes Default Description
fpSrc String

輸入來源資料夾路徑字串

fpTar String

輸入目的資料夾路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useSync Boolean <optional>
true

輸入是否使用同步函數布林值,預設true

Returns:

若useSync=true回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),若useSync=false則回傳Promise,resolve回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),不觸發reject

Type
Object | Promise

(static) fsCreateFile(pah, data, optopt) → {Object}

Description:
  • 後端nodejs建立檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsCreateFile'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/abc/${fn}`

    let b1 = fsIsFile(fp)
    console.log('fsCreateFile(before)', b1)
    ms.push({ 'fsCreateFile(before)': b1 })

    let b2 = fsCreateFile(fp, 'abc', { encoding: 'utf8' })
    console.log('fsCreateFile', b2)
    ms.push({ 'fsCreateFile': b2 })

    let b3 = fsIsFile(fp)
    console.log('fsCreateFile(after)', b3)
    ms.push({ 'fsCreateFile(after)': b3 })

    let c = fs.readFileSync(fp, 'utf8')
    console.log('readFileSync', c)
    ms.push({ 'readFileSync': c })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsCreateFile(before) false
// fsCreateFile { success: 'done: ./_test_fsCreateFile/abc/t1.txt' }
// fsCreateFile(after) true
// readFileSync abc
// ms [
//   { 'fsCreateFile(before)': false },
//   { fsCreateFile: { success: 'done: ./_test_fsCreateFile/abc/t1.txt' } },
//   { 'fsCreateFile(after)': true },
//   { readFileSync: 'abc' }
// ]
Parameters:
Name Type Attributes Default Description
pah String

輸入檔案路徑字串

data Buffer | TypedArray | DataView

輸入檔案內容資料

opt Object | String <optional>
{}

輸入寫入設定物件或字串,可給予writeFileSync設定物件,或是編碼字串(例如'utf8'),預設{}

Properties
Name Type Attributes Default Description
encoding String <optional>
'utf8'

輸入編碼字串,預設'utf8'

mode Integer <optional>
0o666

輸入寫入模式數字,預設0o666

flag String <optional>
'w'

輸入寫入標誌字串,預設'w'

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsCreateFileCore(pah, dataopt, optopt) → {Object}

Description:
  • 後端nodejs建立檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsCreateFile
Parameters:
Name Type Attributes Default Description
pah String

輸入檔案路徑字串

data Buffer | TypedArray | DataView | String <optional>
''

輸入檔案內容資料,預設''

opt Object | String <optional>
{}

輸入寫入設定物件或字串,可給予writeFileSync設定物件,或是編碼字串(例如'utf8'),預設{}

Properties
Name Type Attributes Default Description
encoding String <optional>
'utf8'

輸入編碼字串,預設'utf8'

mode Integer <optional>
0o666

輸入寫入模式數字,預設0o666

flag String <optional>
'w'

輸入寫入標誌字串,預設'w'

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsCreateFolder(pah) → {Object}

Description:
  • 後端nodejs建立資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsCreateFolder'

    let b1 = fsIsFolder(fdt)
    console.log('fsCreateFolder(before)', b1)
    ms.push({ 'fsCreateFolder(before)': b1 })

    fsCreateFolder(fdt) //創建臨時任務資料夾

    let b2 = fsIsFolder(fdt)
    console.log('fsCreateFolder(after)', b2)
    ms.push({ 'fsCreateFolder(after)': b2 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsCreateFolder(before) false
// fsCreateFolder(after) true
// ms [
//   { 'fsCreateFolder(before)': false },
//   { 'fsCreateFolder(after)': true }
// ]
Parameters:
Name Type Description
pah String

輸入資料夾路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsCreateFolderCore(pah) → {Object}

Description:
  • 後端nodejs建立資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsCreateFolder
Parameters:
Name Type Description
pah String

輸入資料夾路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsDeleteFile(pah) → {Object}

Description:
  • 後端nodejs刪除檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsDeleteFile'

    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 'abc.txt'
    let fp = `${fdt}/${fn}`

    fsCreateFile(fp, 'abc', { encoding: 'utf8' })

    let b1 = fsIsFile(fp)
    console.log('fsDeleteFile(before)', b1)
    ms.push({ 'fsDeleteFile(before)': b1 })

    fsDeleteFile(fp)

    let b2 = fsIsFile(fp)
    console.log('fsDeleteFile(after)', b2)
    ms.push({ 'fsDeleteFile(after)': b2 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsDeleteFile(before) true
// fsDeleteFile(after) false
// ms [ { 'fsDeleteFile(before)': true }, { 'fsDeleteFile(after)': false } ]
Parameters:
Name Type Description
pah String

輸入路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsDeleteFileCore(pah) → {Object}

Description:
  • 後端nodejs刪除檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsDeleteFile
Parameters:
Name Type Description
pah String

輸入路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(async, static) fsDeleteFileSafe(pah, optopt) → {Promise}

Description:
  • 後端nodejs刪除檔案,支援失敗重試機制

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = async () => {

    let ms = []

    let fdt = './_test_fsDeleteFileSafe'

    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 'abc.txt'
    let fp = `${fdt}/${fn}`

    fsCreateFile(fp, 'abc', { encoding: 'utf8' })

    let b1 = fsIsFile(fp)
    console.log('fsDeleteFileSafe(before)', b1)
    ms.push({ 'fsDeleteFileSafe(before)': b1 })

    await fsDeleteFileSafe(fp)

    let b2 = fsIsFile(fp)
    console.log('fsDeleteFileSafe(after)', b2)
    ms.push({ 'fsDeleteFileSafe(after)': b2 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsDeleteFileSafe(before) true
// fsDeleteFileSafe(after) false
// ms [ { 'fsDeleteFileSafe(before)': true }, { 'fsDeleteFileSafe(after)': false } ]
Parameters:
Name Type Attributes Default Description
pah String

輸入路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fs Object <optional>
fs

輸入fs物件,須提供statSync、lstatSync、unlinkSync,預設為Node內建fs(供測試注入假fs);給null或缺少statSync時reject字串opt.fs is invalid: ...(不視為標的不存在),缺少其他函數時其呼叫之TypeError以字串reject

numRetry Integer <optional>
5

輸入嘗試刪除次數整數,預設5;遇EBUSY才重試,每次重試前等待300ms,最後一次嘗試失敗後不再等待而直接reject

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'attempt'時為即將呼叫unlinkSync嘗試刪除(含n為由1起算之嘗試序號),'retry'時為第n次嘗試遇EBUSY而即將等待ms毫秒(固定300)(含n、code為'EBUSY'、ms;僅在之後還有下一次嘗試時通知,故n小於numRetry),'done'時為第n次嘗試刪除成功而即將resolve(含n),'skip'時為不需(再)刪除而即將resolve或reject(含n與reason;reason為'not-exist'代表前置檢查標的不存在而resolve、'not-file'代表前置檢查標的非檔案而reject,此二者n為0;reason為'vanished'代表第n次嘗試得ENOENT且標的確已不存在(重試期間被他方刪除)而resolve),'giveup'時為重試耗盡仍遇EBUSY而即將reject(含n為numRetry、code為'EBUSY';於最後一次嘗試失敗當下通知,不再等待),'fail'時為前置檢查拋錯或opt.fs不合(n為0,opt.fs不合時code為null)或第n次嘗試遇不可重試之錯誤而即將reject(含n、code為該錯誤之code字串,無則null);事件皆於resolve或reject之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響刪除,預設null

Returns:

回傳Promise,resolve回傳成功訊息,reject回傳錯誤訊息字串(含重試耗盡仍被占用)

Type
Promise

(async, static) fsDeleteFileSafeCore(pah, optopt) → {Promise}

Description:
  • 後端nodejs刪除檔案,支援失敗重試機制

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsDeleteFileSafe
Parameters:
Name Type Attributes Default Description
pah String

輸入路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fs Object <optional>
null

輸入fs物件,須提供statSync、lstatSync、unlinkSync,預設null;Core由呼叫端傳入(對外函數fsDeleteFileSafe預設傳入內建fs,呼叫端可再以opt.fs覆蓋);null或缺少statSync時reject字串opt.fs is invalid: ...(不視為標的不存在),缺少其他函數時其呼叫之TypeError以字串reject

numRetry Integer <optional>
5

輸入嘗試刪除次數整數,預設5;遇EBUSY才重試,每次重試前等待300ms,最後一次嘗試失敗後不再等待而直接reject

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'attempt'時為即將呼叫unlinkSync嘗試刪除(含n為由1起算之嘗試序號),'retry'時為第n次嘗試遇EBUSY而即將等待ms毫秒(固定300)(含n、code為'EBUSY'、ms;僅在之後還有下一次嘗試時通知,故n小於numRetry),'done'時為第n次嘗試刪除成功而即將resolve(含n),'skip'時為不需(再)刪除而即將resolve或reject(含n與reason;reason為'not-exist'代表前置檢查標的不存在而resolve、'not-file'代表前置檢查標的非檔案而reject,此二者n為0;reason為'vanished'代表第n次嘗試得ENOENT且標的確已不存在(重試期間被他方刪除)而resolve),'giveup'時為重試耗盡仍遇EBUSY而即將reject(含n為numRetry、code為'EBUSY';於最後一次嘗試失敗當下通知,不再等待),'fail'時為前置檢查拋錯或opt.fs不合(n為0,opt.fs不合時code為null)或第n次嘗試遇不可重試之錯誤而即將reject(含n、code為該錯誤之code字串,無則null);事件皆於resolve或reject之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響刪除,預設null

Returns:

回傳Promise,resolve回傳成功訊息,reject回傳錯誤訊息字串(含重試耗盡仍被占用)

Type
Promise

(static) fsDeleteFolder(pah) → {Object}

Description:
  • 後端nodejs刪除資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsDeleteFolder'

    fsCreateFolder(fdt) //創建臨時任務資料夾

    let rfl = fsCreateFile(`${fdt}/abc/abc.txt`, 'abc', { encoding: 'utf8' })
    console.log('rfl', rfl)
    ms.push({ 'fsCreateFile(before)': rfl })

    let rfd = fsCreateFolder(`${fdt}/def/mno`)
    console.log('rfd', rfd)
    ms.push({ 'fsCreateFolder(before)': rfd })

    let b1 = fsIsFile(`${fdt}/abc/abc.txt`)
    console.log('fsIsFile(before)', b1)
    ms.push({ 'fsIsFile(before)': b1 })

    let b2 = fsIsFolder(`${fdt}/def/mno`)
    console.log('fsIsFolder(before)', b2)
    ms.push({ 'fsIsFolder(before)': b2 })

    fsDeleteFolder(fdt)

    let b3 = fsIsFile(`${fdt}/abc/abc.txt`)
    console.log('fsIsFile(after)', b3)
    ms.push({ 'fsIsFile(after)': b3 })

    let b4 = fsIsFolder(`${fdt}/def/mno`)
    console.log('fsIsFolder(after)', b4)
    ms.push({ 'fsIsFolder(after)': b4 })

    console.log('ms', ms)
    return ms
}
test()
// rfl { success: 'done: ./_test_fsDeleteFolder/abc/abc.txt' }
// rfd { success: 'done: ./_test_fsDeleteFolder/def/mno' }
// fsIsFile(before) true
// fsIsFolder(before) true
// fsIsFile(after) false
// fsIsFolder(after) false
// ms [
//   {
//     'fsCreateFile(before)': { success: 'done: ./_test_fsDeleteFolder/abc/abc.txt' }
//   },
//   {
//     'fsCreateFolder(before)': { success: 'done: ./_test_fsDeleteFolder/def/mno' }
//   },
//   { 'fsIsFile(before)': true },
//   { 'fsIsFolder(before)': true },
//   { 'fsIsFile(after)': false },
//   { 'fsIsFolder(after)': false }
// ]
Parameters:
Name Type Description
pah String

輸入欲刪除資料夾路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsDeleteFolderCore(pah) → {Object}

Description:
  • 後端nodejs刪除資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsDeleteFolder
Parameters:
Name Type Description
pah String

輸入欲刪除資料夾路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(async, static) fsDeleteFolderSafe(pah, optopt) → {Promise}

Description:
  • 後端nodejs刪除資料夾,支援失敗重試機制

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = async () => {

    let ms = []

    let fdt = './_test_fsDeleteFolderSafe'

    fsCreateFolder(fdt) //創建臨時任務資料夾

    let rfl = fsCreateFile(`${fdt}/abc/abc.txt`, 'abc', { encoding: 'utf8' })
    console.log('rfl', rfl)
    ms.push({ 'fsCreateFile(before)': rfl })

    let rfd = fsCreateFolder(`${fdt}/def/mno`)
    console.log('rfd', rfd)
    ms.push({ 'fsCreateFolder(before)': rfd })

    let b1 = fsIsFile(`${fdt}/abc/abc.txt`)
    console.log('fsIsFile(before)', b1)
    ms.push({ 'fsIsFile(before)': b1 })

    let b2 = fsIsFolder(`${fdt}/def/mno`)
    console.log('fsIsFolder(before)', b2)
    ms.push({ 'fsIsFolder(before)': b2 })

    await fsDeleteFolderSafe(fdt)

    let b3 = fsIsFile(`${fdt}/abc/abc.txt`)
    console.log('fsIsFile(after)', b3)
    ms.push({ 'fsIsFile(after)': b3 })

    let b4 = fsIsFolder(`${fdt}/def/mno`)
    console.log('fsIsFolder(after)', b4)
    ms.push({ 'fsIsFolder(after)': b4 })

    console.log('ms', ms)
    return ms
}
test()
// rfl { success: 'done: ./_test_fsDeleteFolderSafe/abc/abc.txt' }
// rfd { success: 'done: ./_test_fsDeleteFolderSafe/def/mno' }
// fsIsFile(before) true
// fsIsFolder(before) true
// fsIsFile(after) false
// fsIsFolder(after) false
// ms [
//   {
//     'fsCreateFile(before)': { success: 'done: ./_test_fsDeleteFolderSafe/abc/abc.txt' }
//   },
//   {
//     'fsCreateFolder(before)': { success: 'done: ./_test_fsDeleteFolderSafe/def/mno' }
//   },
//   { 'fsIsFile(before)': true },
//   { 'fsIsFolder(before)': true },
//   { 'fsIsFile(after)': false },
//   { 'fsIsFolder(after)': false }
// ]
Parameters:
Name Type Attributes Default Description
pah String

輸入欲刪除資料夾路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fs Object <optional>
fs

輸入fs物件,須提供statSync、lstatSync、rmSync,預設為Node內建fs(供測試注入假fs);給null或缺少statSync時reject字串opt.fs is invalid: ...(不視為標的不存在),缺少其他函數時其呼叫之TypeError以字串reject

numRetry Integer <optional>
5

輸入嘗試刪除次數整數,預設5;遇EBUSY才重試,每次重試前等待300ms,最後一次嘗試失敗後不再等待而直接reject

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'attempt'時為即將呼叫rmSync嘗試刪除(含n為由1起算之嘗試序號),'retry'時為第n次嘗試遇EBUSY而即將等待ms毫秒(固定300)(含n、code為'EBUSY'、ms;僅在之後還有下一次嘗試時通知,故n小於numRetry),'done'時為第n次嘗試刪除成功而即將resolve(含n),'skip'時為前置檢查即結束而即將resolve或reject(含n為0與reason;reason為'not-exist'代表標的不存在而resolve、'not-folder'代表標的非資料夾而reject),'giveup'時為重試耗盡仍遇EBUSY而即將reject(含n為numRetry、code為'EBUSY';於最後一次嘗試失敗當下通知,不再等待),'fail'時為前置檢查拋錯或opt.fs不合(n為0,opt.fs不合時code為null)或第n次嘗試遇不可重試之錯誤而即將reject(含n、code為該錯誤之code字串,無則null);事件皆於resolve或reject之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響刪除,預設null

Returns:

回傳Promise,resolve回傳成功訊息,reject回傳錯誤訊息字串(含重試耗盡仍被占用)

Type
Promise

(async, static) fsDeleteFolderSafeCore(pah, optopt) → {Promise}

Description:
  • 後端nodejs刪除資料夾,支援失敗重試機制

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsDeleteFolderSafe
Parameters:
Name Type Attributes Default Description
pah String

輸入欲刪除資料夾路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fs Object <optional>
null

輸入fs物件,須提供statSync、lstatSync、rmSync,預設null;Core由呼叫端傳入(對外函數fsDeleteFolderSafe預設傳入內建fs,呼叫端可再以opt.fs覆蓋);null或缺少statSync時reject字串opt.fs is invalid: ...(不視為標的不存在),缺少其他函數時其呼叫之TypeError以字串reject

numRetry Integer <optional>
5

輸入嘗試刪除次數整數,預設5;遇EBUSY才重試,每次重試前等待300ms,最後一次嘗試失敗後不再等待而直接reject

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'attempt'時為即將呼叫rmSync嘗試刪除(含n為由1起算之嘗試序號),'retry'時為第n次嘗試遇EBUSY而即將等待ms毫秒(固定300)(含n、code為'EBUSY'、ms;僅在之後還有下一次嘗試時通知,故n小於numRetry),'done'時為第n次嘗試刪除成功而即將resolve(含n),'skip'時為前置檢查即結束而即將resolve或reject(含n為0與reason;reason為'not-exist'代表標的不存在而resolve、'not-folder'代表標的非資料夾而reject),'giveup'時為重試耗盡仍遇EBUSY而即將reject(含n為numRetry、code為'EBUSY';於最後一次嘗試失敗當下通知,不再等待),'fail'時為前置檢查拋錯或opt.fs不合(n為0,opt.fs不合時code為null)或第n次嘗試遇不可重試之錯誤而即將reject(含n、code為該錯誤之code字串,無則null);事件皆於resolve或reject之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響刪除,預設null

Returns:

回傳Promise,resolve回傳成功訊息,reject回傳錯誤訊息字串(含重試耗盡仍被占用)

Type
Promise

(async, static) fsDownloadFile(urlIn, fpOut) → {Promise}

Description:
  • 後端nodejs下載網址成為檔案

    下載內容先寫入暫存檔${fpOut}.download,傳輸完成後才改名為fpOut,故中途失敗時fpOut不會殘留截斷檔,下次呼叫自然重新下載。回應帶content-length且未經內容編碼(gzip等會自動解壓,該標頭為壓縮後大小故不核對)時,改名前會核對暫存檔大小。任何失敗(非2xx回應、連線被拒、下載中途斷線、大小不符、寫檔錯誤)皆reject字串failed to download url[urlIn] to file[fpOut]: 原因,並清除暫存檔;輸入檢查不通過(urlIn、fpOut非有效字串)時reject說明字串。同一fpOut之併發下載由呼叫端自行避免

    Unit Test: Github

Source:
Example
//need test in nodejs

let test = async () => {
    let ms = []

    let fdt = './_test_fsDownloadFile'
    fsCreateFolder(fdt) //創建任務資料夾

    let urlIn = `https://raw.githubusercontent.com/yuda-lyu/w-demores/refs/heads/master/res/img/avatar/100.svg`
    let fpOut = `${fdt}/100.svg`
    await fsDownloadFile(urlIn, fpOut)
        .then((res) => {
            console.log('res', res)
            ms.push({ fp: res })
        })
        .catch((err) => {
            console.log('err', err)
        })

    let c = fs.readFileSync(fpOut, 'utf8')
    console.log('c', c)
    ms.push({ content: c })

    fsDeleteFolder(fdt) //最終階段清除任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// ms [
//   { fp: './_test_fsDownloadFile/100.svg' },
//   {
//     content: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 231 231"><path d="M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z" style="fill:#bcf700;"/><path d="m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z" style="fill:#b96438;"/><path d="m141.75 195a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.79 114.79 0 0 1 38-16.5l15.71 15.75h21z" style="fill:#304267;"/><path d="m115.45 211.34-10.55 10.54a2.51 2.51 0 0 1-3.5599 0 2 2 0 0 1-0.26999-0.30994l-18.48-25.4 5.8901-5.8899a2.52 2.52 0 0 1 3.5199-0.0791l23.49 21.14z" style="fill:#aab0b1;"/><path d="m115.45 211.34
// 10.55 10.54a2.51 2.51 0 0 0 3.5599 0 2 2 0 0 0 0.26999-0.30994l18.48-25.4-5.8901-5.8899a2.52 2.52 0 0 0-3.4699-0.089l-23.49 21.14z" style="fill:#aab0b1;"/><path d="m158.41 199.58-10.11-3.2401v29.93q5.1601-1.5299 10.11-3.51zm-75.82 26.66v-29.9l-10.1 3.2401v23.14c3.2901 1.3199 6.67 2.4999 10.1 3.5199z" style="fill:#aab0b1;"/><path d="m157.79 67.5a61.31 61.31 0 0 1-42.79 17.43h-55.7c18.16-37.74 68.27-46.85 98.49-17.43z" style="fill:#531148;"/><path d="m122.93 7.0078c-10.503-0.15729-21.09 1.6448-29.545 5.4316-17.141 7.8999-32.169 23.297-43.973 38.779-5.1703 6.8631-8.7779 13.46-8.1855 18.395 0.93114 12.312 10.372 26.483 11.068 36.9 15.663-72.081 105.99-70.452 124.91-7.0525l4e-3 0.0156c5.616-10.926 8.0682-20.188 8.352-27.653 0.43654-15.607-7.8088-21.149-21.735-28.249 1.7934-3.7704 1.7273-7.5023
// 2.0625-10.154-0.79964-7.8568-3.6796-13.51-10.43-17.758-5.9434-3.7404-13.06-6.0867-18.463-7.2266-4.5319-0.87895-9.2901-1.3562-14.064-1.4277z" style="fill:#531148;"/><path d="m42.426 75.338c0.52158 18.689 10.557 74.338-18.115 101.25 12.38 10.603 28.352 19.061 46.025 24.594 11.032-4.6874 22.88-7.4147 34.817-8.5046l0.0633-14.477c-22.49-4.3813-40.766-18.898-48.862-39.967-8.096-21.07-4.7931-44.72 9.2478-62.393zm124.67 2.7207c7.8997 10.886 11.743 24.64 11.787 37.441-0.36632 30.178-22.389 57.576-53.12 62.708l0.0238 14.471c12.282 1.1216 24.518 3.9888 35.825 8.9128 15.488-5.1448 30.007-13.325 42.396-25.043-13.136-22.051-23.282-63.045-18.694-101.55z" style="fill:#531148;"/><path d="m143.61 46.383c-11.639 0.12482-20.998 1.8906-20.998 1.8906l-9 3.5059c0.63003-0.0191 1.2603-0.0289 1.8906-0.0293h0.0996c35.169 0.055 60.959 27.235 63.283 63.383 7.4e-4 31.157-22.742 57.213-53.106 63.079l-0.0216 14.498c11.567 1.0563 23.154 3.6067 33.887 8.0463 35.952-15.315 55.082-52.303 36.709-68.279-5.018-7.9035-10.44-15.409-9.5544-23.03 5.0545-50.452 0.39626-63.561-43.189-63.064zm-69.966 21.09c-15.286 3.244-17.096 3.73-31.734 6.6953 3.0304 13.081 3.0583 22.274 1.2085 30.012-3.8004 11.361-8.9712 19.787-12.286 28.764-6.8823 22.459-2.9157 31.982 12.093 46.165 8.6595 8.0693 19.861 16.209 30.939 20.647 2.669-1.0316 5.3729-1.9628 8.106-2.792 7.4979-2.275 15.388-3.6535 23.206-4.3673l0.0433-14.393c-23.933-4.5937-44.283-21.98-50.77-45.817-6.3319-23.265 0.51104-48.752 19.195-64.914z" style="fill:none;"/><path d="m172.7 90.75h-6.54c-0.14-0.1-0.26-0.22-0.4-0.3-4.48-2.76-22.75-2.11-33.71 1.2-1 0.3-1.91 0.61-2.75 0.94-1.8937 0.79244-3.8739 1.3597-5.9 1.69-5.5051 0.79002-10.403 0.79002-15.908 0-2.0261-0.33034-4.0063-0.89756-5.9-1.69-0.84-0.33-1.76-0.64-2.75-0.94-11-3.31-29.23-4-33.71-1.2-0.13832 0.08869-0.2688 0.18906-0.39 0.3h-6.55c-1.1046 0-2 0.89543-2 2v4.66c-0.0013 0.98185 0.49088 1.8986 1.31 2.44l1.9 1.27c0.59238 0.38889 0.93475
// 1.0622 0.9 1.77-0.14175 5.4854 0.88072 10.939 3 16 3.58 8.38 16 10.9 24.93 10.9 2.6976 0.0771 5.3921-0.2361 8-0.93 4.35-1.43 8.24-7.36 10.45-12.42 1.7607-3.8506 2.7493-8.009 2.91-12.24 7.3e-4 -0.7138 0.38183-1.3731 1-1.73 3.2281-1.951 6.5798-1.951 9.8079 0 0.61817 0.3569 0.99927 1.0162 1 1.73 0.16067 4.231 1.1493 8.3894 2.91 12.24 2.21 5.06 6.1 11 10.45 12.42 2.6079 0.6939 5.3024 1.0071 8 0.93 8.92 0 21.35-2.52 24.93-10.9 2.1193-5.0614 3.1418-10.515 3-16-0.0348-0.70778 0.30762-1.3811 0.9-1.77l1.9-1.27c0.81913-0.54136 1.3113-1.4582 1.31-2.44v-4.6c0.0336-1.1048-0.83521-2.0274-1.94-2.06z" style="fill:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.5;stroke:none;"/><polygon points="121.61 160.74 109.39 160.74 115.5 171.31" style="fill:#666;"/><path d="m132.64 144.06a34.42 34.42
// 0 0 1-34.24 0" style="fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:5.9998px;stroke:#000;"/></svg>'
//   }
// ]
Parameters:
Name Type Description
urlIn String

輸入下載網址字串

fpOut String

輸入儲存檔案路徑字串

Returns:

回傳Promise,resolve回傳下載後檔案路徑,reject回傳錯誤訊息字串

Type
Promise

(async, static) fsDownloadFileCore(urlIn, fpOut, optopt) → {Promise}

Description:
  • 後端nodejs下載網址成為檔案

    下載內容先寫入暫存檔${fpOut}.download,傳輸完成後才改名為fpOut,故中途失敗時fpOut不會殘留截斷檔。回應帶content-length且未經內容編碼與分塊傳輸時,改名前核對暫存檔大小。任何失敗(非2xx回應、連線被拒、下載中途斷線、大小不符、寫檔錯誤)皆reject字串failed to download url[urlIn] to file[fpOut]: 原因並清除暫存檔;輸入檢查不通過(urlIn、fpOut非有效字串、未給opt.pipeline)時reject說明字串。同一fpOut之併發下載由呼叫端自行避免

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsDownloadFile
Parameters:
Name Type Attributes Default Description
urlIn String

輸入下載網址字串

fpOut String

輸入儲存檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fs Object <optional>
null

輸入fs,預設null

Readable function <optional>
null

輸入stream之Readable,預設null

pipeline function <optional>
null

輸入stream/promises之pipeline,預設null

Returns:

回傳Promise,resolve回傳下載後檔案路徑,reject回傳錯誤訊息字串

Type
Promise

(async, static) fsDownloadFileVerify(urlIn, fpOut, expected, optopt) → {Promise}

Description:
  • 後端nodejs下載網址成為檔案並驗證完整性(SHA-256與大小)

    用於下載執行檔或二進位資產: 內容先串流寫入同資料夾之暫存檔<fpOut之絕對路徑>.<pid>.<亂數>.part(以排他方式同步建立, 不覆寫亦不刪除既有同名檔), 同時計算SHA-256與大小(驗證fetch解碼content-encoding後之內容, fetch不支援之編碼則為原始內容), 與期望值相符且重讀暫存檔仍相符才更名為fpOut(覆蓋舊檔)。任何失敗路徑皆不在fpOut留下未驗證或不完整之檔, 舊檔原樣保留; 每次嘗試後清除本次建立之暫存檔, 刪不掉時如實回報(不稱已清除)。

    fpOut於呼叫當下即依當時之工作路徑解析為絕對路徑(其所在資料夾不存在時先建立; Windows之磁碟相對路徑如'C:a.bin'依該磁碟之目前資料夾), 之後之檔案操作皆用該絕對路徑; 回傳之fp為呼叫端所給之字串。fpOut之檔名不得為空、'.'或'..'(E_ARG), Windows下檔名內不得有冒號(替代資料流, 或只有磁碟代號而無檔名, E_ARG); Windows以/與\為路徑分隔符, 其他系統只有/(反斜線為合法之檔名字元); 暫存檔名為正式檔名加上約30字元之後綴, 檔名逼近系統上限時建立暫存檔失敗(E_FS)。

    期望值expected為物件{ sha256, size }(sha256為64碼十六進位、大小寫不拘, size為位元組數、選填, undefined或null視為未給), 或為函數(可為async), 於每次嘗試前呼叫並回傳該物件(供每次重取清單之用法), 函數收到{ attempt, signal }, signal於整體時間用盡時中止; 函數拋錯時錯誤代碼為E_EXPECTED, 所拋錯誤帶retryable為true者重試, 否則不重試。

    給size時: 回應之content-length(未經內容編碼與分塊傳輸者)與size不符即不下載, 內容超過size即中止, 正常結束而短少亦失敗(E_SIZE_MISMATCH); 傳輸中斷造成之短少為E_NETWORK。

    可重試之錯誤: 連線錯誤(E_NETWORK)、HTTP 5xx、408、429(E_HTTP)、停滯(E_TIMEOUT)、雜湊或大小不符(E_HASH_MISMATCH、E_SIZE_MISMATCH, opt.retryOnMismatch為false時不重試); 不可重試: 其他HTTP狀態、參數錯誤(E_ARG, 使用全域fetch時網址須為可解析之http或https)、磁碟錯誤(E_FS)、更名失敗(E_RENAME)、整體時間用盡(E_TIMEOUT_TOTAL)。第n次重試前等待retryDelayMs*n毫秒, 等待會超過整體時間上限時即停止。整體時間上限以單調時計(performance.now)計算, 約束取期望值、網路、寫入暫存檔與重讀核對階段(各等待點皆以此為上限, 不依賴fetch或串流是否響應中止); 驗證通過後之更名與清理不計入: 其退避另加至多約3秒, 更名失敗時比對fpOut內容之讀取每次至多stallMs。

    更名失敗(EPERM、EBUSY、EACCES, Windows下常為防毒掃描占用)時最多共嘗試5次; 更名失敗但fpOut已是期望內容(他方已完成)視為成功。

    同程序內對同一fpOut(以其絕對路徑判定, Windows不分大小寫)、同一網址、同一期望值(物件比對sha256與size, 函數比對是否同一函數)、同一所傳入之模組(opt.fs等)與opt.fetch、同樣之maxAttempts、retryOnMismatch與useSkipIfMatch之併發呼叫共用同一次下載, 共用者收到同一個結果物件或錯誤物件(其中fp為最先呼叫者所給之字串), 等待以各自之totalMs為上限(逾時者E_TIMEOUT_TOTAL, step為'share', 共用之下載續行且其後仍可能更新fpOut); 其餘選項(stallMs、retryDelayMs、onRetry)依最先呼叫者。不符上述條件者各自下載, 同一fpOut之期望值不同時以最後完成更名者為最終內容。opt.useShare為false時各自下載。

    網址與請求: 未給opt.fetch而使用全域fetch時, 網址須為可解析之http或https且不得帶帳密(user:password@, E_ARG), 需要認證時請傳入自訂之opt.fetch; 錯誤訊息與錯誤物件之url會遮蔽網址內之帳密。傳入之opt.fetch須回傳本體(body)為WHATWG ReadableStream之Response(如undici之fetch), 回傳Node之Readable本體者不支援(E_ARG, step為download, 不重試); opt.fetch給了卻不是函數者為E_ARG(只有undefined或null才使用全域fetch)。傳入之函數(期望值函數、fetch、onRetry)皆不帶接收者呼叫(this為undefined)。共用之下載以最先呼叫者之totalMs計時, totalMs較長之後到者亦收到該次下載因其totalMs用盡之失敗。本體寫完後等待暫存檔寫入串流關閉至多5秒(同時輪詢串流之closed屬性), 失敗清理時不重複等待; 該等待不受totalMs約束。本函數只複製opt自身可列舉之屬性(不含原型鏈上者)。

    參數之包裝物件(new String、new Number、new Boolean)一律先解包為原始值再檢查與使用(不呼叫其可被覆寫之方法), 檢查之值即使用之值。size為非負安全整數, 或十進位整數字串(前後空白可); 小數、指數、十六進位、帶正負號、超過安全整數之字串為E_ARG。本函數自建之錯誤以身分判定(傳入元件所拋值即使帶同名code, 亦不視為本函數之錯誤)。錯誤訊息內任何出現處之網址帳密(含底層錯誤文字帶回者)皆遮蔽, 但cause仍為原始錯誤物件。

    下載執行檔或二進位資產一律經本函數驗證(規則帳本R-DL-VERIFY-01); 不需驗證之一般下載用fsDownloadFile。

    Unit Test: Github

Source:
Example
//need test in nodejs

import fs from 'fs'
import http from 'http'
import crypto from 'crypto'

let test = async () => {
    let ms = []

    let fdt = './_test_fsDownloadFileVerify'
    fsCreateFolder(fdt) //創建任務資料夾

    //本機HTTP服務, 提供一個檔案
    let body = Buffer.from('abc中文')
    let server = http.createServer((req, res) => {
        res.writeHead(200, { 'content-length': String(body.length) })
        res.end(body)
    })
    await new Promise((resolve) => {
        server.listen(8802, '127.0.0.1', resolve)
    })
    let urlIn = 'http://127.0.0.1:8802/a.bin'
    let sha256 = crypto.createHash('sha256').update(body).digest('hex')

    //期望值相符, 下載後更名為正式檔名
    let r1 = await fsDownloadFileVerify(urlIn, `${fdt}/a.bin`, { sha256, size: body.length })
    console.log('r1', r1)
    ms.push({ fp: r1.fp, sha256: r1.sha256, size: r1.size, attempts: r1.attempts, downloaded: r1.downloaded, content: fs.readFileSync(r1.fp, 'utf8') })

    //期望值不符(且不重試), 正式檔名不會出現
    await fsDownloadFileVerify(urlIn, `${fdt}/b.bin`, { sha256: '0'.repeat(64) }, { retryOnMismatch: false })
        .catch((err) => {
            console.log('err', err.message)
            ms.push({ code: err.code, step: err.step, attempts: err.attempts, exists: fs.existsSync(`${fdt}/b.bin`) })
        })

    //期望值由函數取得(每次嘗試前呼叫, 例如每次重取清單)
    let r3 = await fsDownloadFileVerify(urlIn, `${fdt}/c.bin`, async ({ attempt }) => {
        return { sha256: sha256.toUpperCase(), size: String(body.length) }
    })
    console.log('r3', r3)
    ms.push({ sha256: r3.sha256, attempts: r3.attempts })

    await new Promise((resolve) => {
        server.close(resolve)
    })
    fsDeleteFolder(fdt) //最終階段清除任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// r1 {
//   fp: './_test_fsDownloadFileVerify/a.bin',
//   sha256: 'ac462cf17176818361b7ec29c9c399b71ce29c6708580b1851e602341630f137',
//   size: 9,
//   attempts: 1,
//   downloaded: true,
//   leftovers: []
// }
// err failed to download url[http://127.0.0.1:8802/a.bin] to file[./_test_fsDownloadFileVerify/b.bin] (code E_HASH_MISMATCH, step verify, attempt 1 of 3): sha256 mismatch: expected 0000000000000000... (size not given), received ac462cf171768183... (9 bytes)
// hint: the file on the server is not the expected one; a cache may serve an older or newer file for a few minutes after an update, or the content was altered in transit; retry later, check the expected value, proxy and antivirus
// note: file[./_test_fsDownloadFileVerify/b.bin] was not modified by this call
// r3 {
//   fp: './_test_fsDownloadFileVerify/c.bin',
//   sha256: 'ac462cf17176818361b7ec29c9c399b71ce29c6708580b1851e602341630f137',
//   size: 9,
//   attempts: 1,
//   downloaded: true,
//   leftovers: []
// }
// ms [
//   {
//     fp: './_test_fsDownloadFileVerify/a.bin',
//     sha256: 'ac462cf17176818361b7ec29c9c399b71ce29c6708580b1851e602341630f137',
//     size: 9,
//     attempts: 1,
//     downloaded: true,
//     content: 'abc中文'
//   },
//   {
//     code: 'E_HASH_MISMATCH',
//     step: 'verify',
//     attempts: 1,
//     exists: false
//   },
//   {
//     sha256: 'ac462cf17176818361b7ec29c9c399b71ce29c6708580b1851e602341630f137',
//     attempts: 1
//   }
// ]
Parameters:
Name Type Attributes Default Description
urlIn String

輸入下載網址字串

fpOut String

輸入儲存檔案路徑字串, 所在資料夾不存在時自動建立

expected Object | function

輸入期望值物件{ sha256, size }, 或回傳該物件之函數(可為async, 每次嘗試前呼叫, 收到{ attempt, signal })

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fs Object <optional>
fs

輸入fs,供測試傳入,須有mkdirSync、realpathSync、openSync、closeSync、createWriteStream、createReadStream、renameSync、unlinkSync、statSync,預設為nodejs之fs

fetch function <optional>
null

輸入fetch函數,未給時於每次請求時取用全域fetch,預設null

maxAttempts Integer <optional>
3

輸入最多嘗試次數,正整數(可為數字字串),無效時使用預設值,預設3

retryDelayMs Number <optional>
1000

輸入重試間隔基數毫秒,第n次重試前等待retryDelayMs*n,亦為更名與刪除暫存檔重試間隔之基數(上限200),非負數,無效時使用預設值,預設1000

stallMs Number <optional>
30000

輸入連續沒收到資料(含等待回應標頭)視為停滯之毫秒數,正數,無效時使用預設值,預設30000

totalMs Number <optional>
600000

輸入整體時間上限毫秒數,正數,無效時使用預設值,超過計時器上限(2147483647)時夾至上限,預設600000

retryOnMismatch Boolean <optional>
true

輸入雜湊或大小不符時是否重試,預設true

useShare Boolean <optional>
true

輸入同程序內併發呼叫是否共用同一次下載,預設true

useSkipIfMatch Boolean <optional>
false

輸入每次嘗試取得期望值後,fpOut已是期望內容時是否不下載直接成功(回傳downloaded為false),既有檔無法讀取視為不符,預設false

onRetry function <optional>
null

輸入重試前之通知函數,收到{ attempt, maxAttempts, delayMs, error },其拋錯或回傳之rejection皆忽略,預設null

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷,與給使用者之進度通知opt.onRetry是兩回事(互不影響);n為由1起算之嘗試序號(共用等待者與參數通過後之準備失敗為0);type為'attempt'時為第n次嘗試開始(含n、max為maxAttempts),'skip'時為opt.useSkipIfMatch成立而fpOut已是期望內容故不下載(含n、size),'request'時為即將送出請求且整體時限計時器與停滯計時器已設定(含n、stallMs、remainMs為整體時限剩餘毫秒數),'touch'時為停滯計時器剛重設(自此起連續stallMs沒有資料即停滯;含n、from為'request'|'headers'|'data'、ms為stallMs),'headers'時為回應標頭已到(含n、ok、status為數字或null),'data'時為本體又收到一塊資料(含n、bytes為本次嘗試累計位元組數;其後隨即重設停滯計時器),'temp'時為暫存檔建立(act為'create')或清除(act為'remove';含n、act、ok為是否成功),'verify'時為校驗結果(phase為'stream'代表本體讀完之大小與SHA-256比對、'disk'代表重讀暫存檔核對;含n、phase、size、sizeOk、hashOk,皆為布林或數字,不含雜湊內容),'rename'時為第i次更名嘗試(含n、i、ok),'timeout'時為時限計時器觸發(kind為'stall'代表停滯、'total'代表整體時限;where為'request'代表請求階段之計時器、'expected'代表取期望值階段、'read'代表重讀檔案比對階段、'share'代表共用等待者逾時、'budget'代表檢查時整體時限已用盡;含n、kind、where、ms),'abort'時為請求之中止實際執行(why為中止原因代碼'E_TIMEOUT'|'E_TIMEOUT_TOTAL'、'fallback'代表一端先出錯後5秒仍未落定而放手、'close'代表本次嘗試結束時釋放未讀完之本體與連線;含n、why),'retry'時為第n次嘗試失敗而即將等待ms毫秒後重試(含n、ms;與opt.onRetry同一時點但不論有無給onRetry皆通知),'stop'時為不再重試(why為'fatal'代表不可重試之錯誤、'max'代表次數用盡、'budget'代表等待會超過整體時限;含n、why、code為該錯誤代碼),'share'時為同程序共用之登記(act為'own'代表本次為進行中之工作、'join'代表加入進行中之同一工作、'off'代表opt.useShare為false而各自下載;含act),'settle'時為本次呼叫即將resolve(ok為true)或reject(含ok、code為錯誤代碼或null、attempts);入口之參數錯誤(E_ARG)不通知任何事件;事件皆於resolve或reject之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響下載,本函數以展開opt原樣轉傳給fsDownloadFileVerifyCore,預設null

Returns:

回傳Promise,resolve回傳結果物件{ fp, sha256, size, attempts, downloaded, leftovers },sha256為小寫,leftovers為先前嘗試刪不掉之暫存檔陣列[{ fp, error }];reject回傳Error物件,其code為'E_ARG'、'E_EXPECTED'、'E_HTTP'、'E_NETWORK'、'E_TIMEOUT'、'E_TIMEOUT_TOTAL'、'E_SIZE_MISMATCH'、'E_HASH_MISMATCH'、'E_FS'、'E_RENAME'之一,另帶step('argument'、'prepare'、'expected'、'request'、'download'、'verify'、'rename'、'share')、attempts(已嘗試次數,參數錯誤、建立資料夾錯誤與共用等待逾時為0);E_ARG以外另帶url、fp、expected(最後使用之期望值)、actual(已計算之實際值或null)、leftovers、cleanupError(有刪不掉之暫存檔時為第一個之錯誤)、status(HTTP錯誤時)、cause,message含失敗步驟、預期與實際(雜湊截短為16碼)、建議與刪不掉之暫存檔;E_ARG(入口之參數錯誤)只另帶cause(有時); 下載途中之E_ARG(回應本體不是WHATWG ReadableStream)與其他代碼同帶上述欄位

Type
Promise

(async, static) fsDownloadFileVerifyCore(urlIn, fpOut, expected, optopt) → {Promise}

Description:
  • 後端nodejs下載網址成為檔案並驗證完整性(SHA-256與大小), 可傳入fs與fetch

    用於下載執行檔或二進位資產: 內容先串流寫入同資料夾之暫存檔<fpOut之絕對路徑>.<pid>.<亂數>.part(以排他方式同步建立, 不覆寫亦不刪除既有同名檔), 同時計算SHA-256與大小(驗證fetch解碼content-encoding後之內容, fetch不支援之編碼則為原始內容), 與期望值相符且重讀暫存檔仍相符才更名為fpOut(覆蓋舊檔)。任何失敗路徑皆不在fpOut留下未驗證或不完整之檔, 舊檔原樣保留; 每次嘗試後清除本次建立之暫存檔, 刪不掉時如實回報(不稱已清除)。

    fpOut於呼叫當下即依當時之工作路徑解析為絕對路徑(其所在資料夾不存在時先建立; Windows之磁碟相對路徑如'C:a.bin'依該磁碟之目前資料夾), 之後之檔案操作皆用該絕對路徑; 回傳之fp為呼叫端所給之字串。fpOut之檔名不得為空、'.'或'..'(E_ARG), Windows下檔名內不得有冒號(替代資料流, 或只有磁碟代號而無檔名, E_ARG); Windows以/與\為路徑分隔符, 其他系統只有/(反斜線為合法之檔名字元); 暫存檔名為正式檔名加上約30字元之後綴, 檔名逼近系統上限時建立暫存檔失敗(E_FS)。

    期望值expected為物件{ sha256, size }(sha256為64碼十六進位、大小寫不拘, size為位元組數、選填, undefined或null視為未給), 或為函數(可為async), 於每次嘗試前呼叫並回傳該物件(供每次重取清單之用法), 函數收到{ attempt, signal }, signal於整體時間用盡時中止; 函數拋錯時錯誤代碼為E_EXPECTED, 所拋錯誤帶retryable為true者重試, 否則不重試。

    給size時: 回應之content-length(未經內容編碼與分塊傳輸者)與size不符即不下載, 內容超過size即中止, 正常結束而短少亦失敗(E_SIZE_MISMATCH); 傳輸中斷造成之短少為E_NETWORK。

    可重試之錯誤: 連線錯誤(E_NETWORK)、HTTP 5xx、408、429(E_HTTP)、停滯(E_TIMEOUT)、雜湊或大小不符(E_HASH_MISMATCH、E_SIZE_MISMATCH, opt.retryOnMismatch為false時不重試); 不可重試: 其他HTTP狀態、參數錯誤(E_ARG, 使用全域fetch時網址須為可解析之http或https)、磁碟錯誤(E_FS)、更名失敗(E_RENAME)、整體時間用盡(E_TIMEOUT_TOTAL)。第n次重試前等待retryDelayMs*n毫秒, 等待會超過整體時間上限時即停止。整體時間上限以單調時計(performance.now)計算, 約束取期望值、網路、寫入暫存檔與重讀核對階段(各等待點皆以此為上限, 不依賴fetch或串流是否響應中止); 驗證通過後之更名與清理不計入: 其退避另加至多約3秒, 更名失敗時比對fpOut內容之讀取每次至多stallMs。

    更名失敗(EPERM、EBUSY、EACCES, Windows下常為防毒掃描占用)時最多共嘗試5次; 更名失敗但fpOut已是期望內容(他方已完成)視為成功。

    同程序內對同一fpOut(以其絕對路徑判定, Windows不分大小寫)、同一網址、同一期望值(物件比對sha256與size, 函數比對是否同一函數)、同一opt.fs、opt.crypto、opt.Readable、opt.pipeline與opt.fetch、同樣之maxAttempts、retryOnMismatch與useSkipIfMatch之併發呼叫共用同一次下載, 共用者收到同一個結果物件或錯誤物件(其中fp為最先呼叫者所給之字串), 等待以各自之totalMs為上限(逾時者E_TIMEOUT_TOTAL, step為'share', 共用之下載續行且其後仍可能更新fpOut); 其餘選項(stallMs、retryDelayMs、onRetry)依最先呼叫者。不符上述條件者各自下載, 同一fpOut之期望值不同時以最後完成更名者為最終內容。opt.useShare為false時各自下載。

    網址與請求: 未給opt.fetch而使用全域fetch時, 網址須為可解析之http或https且不得帶帳密(user:password@, E_ARG), 需要認證時請傳入自訂之opt.fetch; 錯誤訊息與錯誤物件之url會遮蔽網址內之帳密。傳入之opt.fetch須回傳本體(body)為WHATWG ReadableStream之Response(如undici之fetch), 回傳Node之Readable本體者不支援(E_ARG, step為download, 不重試); opt.fetch給了卻不是函數者為E_ARG(只有undefined或null才使用全域fetch)。傳入之函數(期望值函數、fetch、pipeline、onRetry)皆不帶接收者呼叫(this為undefined)。共用之下載以最先呼叫者之totalMs計時, totalMs較長之後到者亦收到該次下載因其totalMs用盡之失敗。本體寫完後等待暫存檔寫入串流關閉至多5秒(同時輪詢串流之closed屬性, 以emitClose:false建立而不發出close事件者亦可判定), 失敗清理時不重複等待; 該等待不受totalMs約束。

    參數之包裝物件(new String、new Number、new Boolean)一律先解包為原始值再檢查與使用(不呼叫其可被覆寫之方法), 檢查之值即使用之值。size為非負安全整數, 或十進位整數字串(前後空白可); 小數、指數、十六進位、帶正負號、超過安全整數之字串為E_ARG。本函數自建之錯誤以身分判定(傳入元件所拋值即使帶同名code, 亦不視為本函數之錯誤: 傳入fs之getter拋出者為E_ARG, 傳入fetch拋出者為E_NETWORK, 先出錯之寫入端為E_FS)。錯誤訊息內任何出現處之網址帳密(含底層錯誤文字帶回者)皆遮蔽, 但cause仍為原始錯誤物件。

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsDownloadFileVerify
Parameters:
Name Type Attributes Default Description
urlIn String

輸入下載網址字串

fpOut String

輸入儲存檔案路徑字串, 所在資料夾不存在時自動建立

expected Object | function

輸入期望值物件{ sha256, size }, 或回傳該物件之函數(可為async, 每次嘗試前呼叫, 收到{ attempt, signal })

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fs Object <optional>
null

輸入fs,須有mkdirSync、realpathSync、openSync、closeSync、createWriteStream、createReadStream、renameSync、unlinkSync、statSync,預設null

crypto Object <optional>
null

輸入crypto,須有createHash,預設null

Readable function <optional>
null

輸入stream之Readable,須有fromWeb、from,預設null

pipeline function <optional>
null

輸入stream/promises之pipeline,預設null

fetch function <optional>
null

輸入fetch函數,未給時於每次請求時取用全域fetch,預設null

maxAttempts Integer <optional>
3

輸入最多嘗試次數,正整數(可為數字字串),無效時使用預設值,預設3

retryDelayMs Number <optional>
1000

輸入重試間隔基數毫秒,第n次重試前等待retryDelayMs*n,亦為更名與刪除暫存檔重試間隔之基數(上限200),非負數,無效時使用預設值,預設1000

stallMs Number <optional>
30000

輸入連續沒收到資料(含等待回應標頭)視為停滯之毫秒數,正數,無效時使用預設值,預設30000

totalMs Number <optional>
600000

輸入整體時間上限毫秒數,正數,無效時使用預設值,超過計時器上限(2147483647)時夾至上限,預設600000

retryOnMismatch Boolean <optional>
true

輸入雜湊或大小不符時是否重試,預設true

useShare Boolean <optional>
true

輸入同程序內併發呼叫是否共用同一次下載,預設true

useSkipIfMatch Boolean <optional>
false

輸入每次嘗試取得期望值後,fpOut已是期望內容時是否不下載直接成功(回傳downloaded為false),既有檔無法讀取視為不符,預設false

onRetry function <optional>
null

輸入重試前之通知函數,收到{ attempt, maxAttempts, delayMs, error },其拋錯或回傳之rejection皆忽略,預設null

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷,與給使用者之進度通知opt.onRetry是兩回事(互不影響);n為由1起算之嘗試序號(共用等待者與參數通過後之準備失敗為0);type為'attempt'時為第n次嘗試開始(含n、max為maxAttempts),'skip'時為opt.useSkipIfMatch成立而fpOut已是期望內容故不下載(含n、size),'request'時為即將送出請求且整體時限計時器與停滯計時器已設定(含n、stallMs、remainMs為整體時限剩餘毫秒數),'touch'時為停滯計時器剛重設(自此起連續stallMs沒有資料即停滯;含n、from為'request'|'headers'|'data'、ms為stallMs),'headers'時為回應標頭已到(含n、ok、status為數字或null),'data'時為本體又收到一塊資料(含n、bytes為本次嘗試累計位元組數;其後隨即重設停滯計時器),'temp'時為暫存檔建立(act為'create')或清除(act為'remove';含n、act、ok為是否成功),'verify'時為校驗結果(phase為'stream'代表本體讀完之大小與SHA-256比對、'disk'代表重讀暫存檔核對;含n、phase、size、sizeOk、hashOk,皆為布林或數字,不含雜湊內容),'rename'時為第i次更名嘗試(含n、i、ok),'timeout'時為時限計時器觸發(kind為'stall'代表停滯、'total'代表整體時限;where為'request'代表請求階段之計時器、'expected'代表取期望值階段、'read'代表重讀檔案比對階段、'share'代表共用等待者逾時、'budget'代表檢查時整體時限已用盡;含n、kind、where、ms),'abort'時為請求之中止實際執行(why為中止原因代碼'E_TIMEOUT'|'E_TIMEOUT_TOTAL'、'fallback'代表一端先出錯後5秒仍未落定而放手、'close'代表本次嘗試結束時釋放未讀完之本體與連線;含n、why),'retry'時為第n次嘗試失敗而即將等待ms毫秒後重試(含n、ms;與opt.onRetry同一時點但不論有無給onRetry皆通知),'stop'時為不再重試(why為'fatal'代表不可重試之錯誤、'max'代表次數用盡、'budget'代表等待會超過整體時限;含n、why、code為該錯誤代碼),'share'時為同程序共用之登記(act為'own'代表本次為進行中之工作、'join'代表加入進行中之同一工作、'off'代表opt.useShare為false而各自下載;含act),'settle'時為本次呼叫即將resolve(ok為true)或reject(含ok、code為錯誤代碼或null、attempts);入口之參數錯誤(E_ARG)不通知任何事件;事件皆於resolve或reject之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響下載,預設null

Returns:

回傳Promise,resolve回傳結果物件{ fp, sha256, size, attempts, downloaded, leftovers },sha256為小寫,leftovers為先前嘗試刪不掉之暫存檔陣列[{ fp, error }];reject回傳Error物件,其code為'E_ARG'、'E_EXPECTED'、'E_HTTP'、'E_NETWORK'、'E_TIMEOUT'、'E_TIMEOUT_TOTAL'、'E_SIZE_MISMATCH'、'E_HASH_MISMATCH'、'E_FS'、'E_RENAME'之一,另帶step('argument'、'prepare'、'expected'、'request'、'download'、'verify'、'rename'、'share')、attempts(已嘗試次數,參數錯誤、建立資料夾錯誤與共用等待逾時為0);E_ARG以外另帶url、fp、expected(最後使用之期望值)、actual(已計算之實際值或null)、leftovers、cleanupError(有刪不掉之暫存檔時為第一個之錯誤)、status(HTTP錯誤時)、cause,message含失敗步驟、預期與實際(雜湊截短為16碼)、建議與刪不掉之暫存檔;E_ARG(入口之參數錯誤)只另帶cause(有時); 下載途中之E_ARG(回應本體不是WHATWG ReadableStream)與其他代碼同帶上述欄位

Type
Promise

(async, static) fsEnsureBins(manifest, opt) → {Promise}

Description:
  • 後端nodejs依清單取得外部執行檔(快取、鏡像、GitHub)並以清單之SHA-256與大小校驗, 回傳各檔之絕對路徑

    規格: 外部執行檔之存放下載校驗與離線(制式規格, opensrcOper之dspec), 本函數為其§6之共用實作(規則帳本R-BIN-ENSURE-01)。含外部執行檔之套件只做三件事: ①提供清單(bin/manifest.json, 由toolg/genBinManifest.mjs以fsGetBinManifestEntry依實檔產生); ②呼叫本函數取得路徑後啟動; ③匯出ensureBins()(轉呼叫本函數)。不得另寫下載、快取、鏡像、校驗。首次使用時才下載(不在postinstall下載)。

    清單manifest(schema 1)為物件或JSON檔路徑(相對路徑以opt.pkgRoot為基準): { schema: 1, pkg, mode: 'pinned'|'floating', repo: '/', commit(40碼小寫十六進位, 釘選模式有path之檔時必填), manifestPath(主幹模式之清單於repo內之路徑, 預設'bin/manifest.json'), files: [...], note, version }。files之每項: name(檔名, 英數與._+-, 不分大小寫不得重複), 來源為path(repo內之相對路徑, 自有檔, 網址為https://raw.githubusercontent.com//<commit或master>/)、url(廠商檔, 須https、不得帶帳密、不得為releases/latest)或parts(分割壓縮檔, 每項{ path或url, sha256, size, name(選填, 預設為path或url之最後一段) }, 須另給assembled: { type: 'zip', sha256, size }為組合後之校驗值), sha256(64碼十六進位)與size(位元組數)為該來源檔之校驗值(parts者不給), extract: { type: 'zip', member(壓縮檔內之相對路徑), sha256, size }為解出之檔之校驗值, note(選填)。不認得之欄位、型別不符、名稱或路徑不合白名單皆為E_MANIFEST。

    取得順序: ①快取: 位置為<快取>//(內容定址, sha256為最終檔之值, 壓縮檔者為extract之值), 快取資料夾為opt.cacheDir, 否則環境變數W_BIN_CACHE時為<W_BIN_CACHE>/, 否則<opt.pkgRoot>/bin-cache; 命中亦校驗大小與SHA-256, 本行程內已校驗且檔案識別(裝置、inode、大小、修改與變更時間)未變者不重複雜湊, 不符者刪除(識別未變才刪)後重新取得。②鏡像: opt.mirror, 否則環境變數W_BIN_MIRROR, 為本地資料夾或http(s)基底網址, 結構為<鏡像>///; 壓縮檔者先找解出之最終檔, 再找壓縮檔(為其path或url之最後一段)或各分割檔; 鏡像之檔不符清單即拒用(不留檔)並續找下一來源。③GitHub或廠商網址(離線時不連)。任何來源皆經fsDownloadFileVerify之暫存檔、校驗、原子更名寫入, 不讀到半檔。

    離線: opt.offline, 否則環境變數W_BIN_OFFLINE為1或true時, 只用快取與鏡像, 缺檔時reject E_OFFLINE_MISSING, 訊息列出每個缺檔之名稱、sha256、大小、應放之快取與鏡像完整路徑、來源網址與準備方法(連網機器設W_BIN_CACHE為一資料夾後呼叫ensureBins(), 再把該資料夾帶入設為W_BIN_MIRROR), 錯誤物件之missing為[{ name, sha256, size, cachePath, mirrorPath, url, member, archive, parts, reasons }]。

    主幹模式(mode為floating): 先取主幹清單(opt.floatingManifestUrl, 否則https://raw.githubusercontent.com//master/, 上限1MB, 時限同opt.stallMs), 同樣檢核schema且須為同一pkg與repo; 成功時其副本存於<快取>/manifest-last.json(紀錄, 不作為校驗依據); 失敗(含離線、無效)時用傳入之基準清單。主幹清單之信任錨為GitHub主幹(規格M5), 故opt.floatingManifestUrl只能是https://raw.githubusercontent.com//之下(同一repo, 可改分支或路徑, 否則E_ARG), 套件之ensureBins不應轉傳使用者之值。

    壓縮檔: 於快取夾內本次專用之暫存夾(.work--<亂數>)取得壓縮檔(或各分割檔各自校驗後依序組合並校驗assembled), 以tar解出成員(Windows為\System32\tar.exe即bsdtar, 其他系統為bsdtar或tar, 須支援zip, 參數皆為暫存夾內之相對路徑, 成員前加--), 解出之檔須為一般檔案且符合extract之大小與SHA-256, 才原子移入快取; 暫存夾於結束時刪除, 刪不掉者(含下載核心刪不掉之暫存檔、主幹清單副本之暫存檔)列於結果之leftovers(不可列舉之屬性; 失敗時為錯誤物件之leftovers; 共用同一次取得者皆得到)。解壓時限同opt.totalMs, 逾時終止後等其結束至多5秒, 仍未結束則保留暫存夾並列入leftovers。解壓失敗為E_EXTRACT。

    清單與選項之字串一律須為原始字串(包裝物件new String與new Boolean不接受), 檢查之值即使用之值。離線時http(s)鏡像之重導不追隨(視為拒用)。

    併發: 同程序內同一最終檔且來源設定相同之併發呼叫共用同一次取得(其onProgress只通知最先呼叫者); 跨程序以暫存檔與原子更名保證不讀到半檔, 更名失敗而最終檔已是期望內容視為成功。各檔依清單順序逐一取得。

    錯誤: reject帶code之Error: 'E_ARG'(參數)、'E_MANIFEST'(清單, 帶field)、'E_OFFLINE_MISSING'(離線缺檔, 帶missing)、'E_EXTRACT'(解壓)、'E_RENAME'(移入快取), 以及fsDownloadFileVerify之code('E_HTTP'、'E_NETWORK'、'E_TIMEOUT'、'E_TIMEOUT_TOTAL'、'E_SIZE_MISMATCH'、'E_HASH_MISMATCH'、'E_FS'等, 訊息前綴fsEnsureBins[] file[] from <github|url>); 皆帶step, 屬某檔者另帶pkg與bin(檔名)。訊息與欄位內之網址帳密一律遮蔽(鏡像網址帶帳密者須傳入opt.fetch處理認證, 否則E_ARG)。

    環境變數(本函數統一讀取process.env): W_BIN_CACHE(共用快取資料夾)、W_BIN_MIRROR(鏡像)、W_BIN_OFFLINE(1或true為離線)。

    Unit Test: Github

Source:
Example
//need test in nodejs

import fs from 'fs'
import http from 'http'
import crypto from 'crypto'

let test = async () => {
    let ms = []

    let fdt = './_test_fsEnsureBins'
    fsCreateFolder(fdt) //創建任務資料夾(作為套件根目錄)

    //本機HTTP服務扮GitHub, 提供repo內之bin/tool.exe
    let body = Buffer.from('MZ-tool')
    let hits = 0
    let server = http.createServer((req, res) => {
        hits++
        res.writeHead(200, { 'content-length': String(body.length) })
        res.end(body)
    })
    await new Promise((resolve) => {
        server.listen(8812, '127.0.0.1', resolve)
    })
    let fetch = (url, init) => {
        return globalThis.fetch(url.replace('https://raw.githubusercontent.com', 'http://127.0.0.1:8812'), init)
    }

    //清單(實務上為bin/manifest.json)
    let manifest = {
        schema: 1,
        pkg: 'w-example',
        mode: 'pinned',
        repo: 'yuda-lyu/w-example',
        commit: '0123456789abcdef0123456789abcdef01234567',
        files: [
            { name: 'tool.exe', path: 'bin/tool.exe', sha256: crypto.createHash('sha256').update(body).digest('hex'), size: body.length },
        ],
    }

    //首次: 下載、校驗後放入快取<pkgRoot>/bin-cache/<sha256>/tool.exe
    let r1 = await fsEnsureBins(manifest, { pkgRoot: fdt, fetch, mirror: '', offline: false })
    ms.push({ name: Object.keys(r1)[0], inCache: r1['tool.exe'].includes('bin-cache'), content: fs.readFileSync(r1['tool.exe'], 'utf8'), hits })

    //第二次: 快取命中(校驗), 不連網
    let r2 = await fsEnsureBins(manifest, { pkgRoot: fdt, fetch, mirror: '', offline: false })
    ms.push({ same: r2['tool.exe'] === r1['tool.exe'], hits })

    //離線且快取另放他處(無檔): E_OFFLINE_MISSING, 訊息含檔名、sha256、應放之路徑
    await fsEnsureBins(manifest, { pkgRoot: fdt, cacheDir: `${fdt}/other`, mirror: '', offline: true })
        .catch((err) => {
            console.log(err.message)
            ms.push({ code: err.code, name: err.missing[0].name, size: err.missing[0].size })
        })

    await new Promise((resolve) => {
        server.close(resolve)
    })
    fsDeleteFolder(fdt) //最終階段清除任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// fsEnsureBins[w-example]: offline (W_BIN_OFFLINE or opt.offline), 1 external executable file(s) are not in the cache or the mirror:
// - tool.exe: sha256 40f576db36632cf550d7d99bd6599c88a131ba725f46b29a95ae2d5359bb9913, 7 bytes
// ...
// ms [
//   { name: 'tool.exe', inCache: true, content: 'MZ-tool', hits: 1 },
//   { same: true, hits: 1 },
//   { code: 'E_OFFLINE_MISSING', name: 'tool.exe', size: 7 }
// ]
Parameters:
Name Type Description
manifest Object | String

輸入清單物件(schema 1), 或其JSON檔路徑(相對路徑以opt.pkgRoot為基準)

opt Object

輸入設定物件

Properties
Name Type Attributes Default Description
pkgRoot String

輸入套件根目錄, 必填

names String | Array <optional>
null

輸入只取部分檔之名稱或其陣列, 預設null為全部

cacheDir String <optional>
''

輸入快取資料夾(其下為/), 優先於W_BIN_CACHE, ''為預設位置, 預設''

mirror String <optional>

輸入鏡像(本地資料夾或http(s)基底網址), 給了即優先於W_BIN_MIRROR(空字串為不使用鏡像)

offline Boolean <optional>

輸入是否離線, 給了即優先於W_BIN_OFFLINE

fetch function <optional>
null

輸入fetch函數(測試注入或處理認證), 未給時用全域fetch

onProgress function <optional>
null

輸入進度通知函數, 收到{ pkg, step('manifest'、'cache'、'mirror'、'download'、'extract'、'ready'、'cleanup'), name, ... }, 其拋錯皆忽略

onTrace function <optional>
null

輸入觀察回調函數, 於內部事件發生當下同步呼叫, 傳入一個凍結之事件物件{ type, ... }, 供測試以事件(而非等待時間)同步與診斷; 與onProgress(給使用者之進度)分開, 不轉傳給內部之下載核心。type與其欄位為: 'manifest'(本次採用之清單已確定, 含source為'base'[釘選模式之傳入清單]、'remote'[取得主幹清單]、'baseline'[主幹模式退回傳入之基準清單], 與files為該清單之檔案數), 'trunk'(主幹清單, 含phase為'fetch'[開始取, 時限計時器已建立]、'ok'[取得且檢核通過]、'fail'[失敗, 逾opt.stallMs而放棄者code為'E_TIMEOUT'], 與code為失敗之錯誤碼或null), 'cancel'(已取消主幹清單回應之本體, 含why為'unread'[讀取中途被中止]、'late'[放棄等待後才落定之回應]、'settled'[逾時勝出時已落定而未交出之回應]、'status'[非2xx之回應]), 'last'(主幹清單副本寫入manifest-last.json, 含ok), 'job'(各檔取得之登記, 含name與mode為'new'[新開一次取得]、'join'[共用同程序已在進行之同一次取得, 該次取得之其餘事件只通知最先呼叫者]、'solo'[選項無法序列化而不共用]), 'cache'(快取校驗結果, 含name與hit), 'mirror'(鏡像嘗試, 含name、file與result為'ok'、'miss'、'rejected'), 'offline'(離線缺檔之判定, 含name), 'work'(已建本次專用暫存夾, 含name), 'download'(開始自GitHub或廠商網址下載, 含name與file), 'downloaded'(下載結束, 含name、file、ok與code), 'extract'(開始解壓, 含name), 'kill'(解壓逾opt.totalMs而即將終止解壓程式, 含name), 'extracted'(解壓程式已結束或放棄等待, 含name、result為'exit'、'timeout'、'error', code為離開碼或null, exited為是否確認已結束或null), 'place'(解出檔已移入快取, 含name), 'cleanup'(暫存夾刪除結果, 含name與ok; 刪不掉或因解壓程式未結束而保留者ok為false), 'ready'(該檔已就緒, 含name), 'fail'(該檔失敗[離線缺檔除外], 含name與code), 'tidy'(離線缺檔reject前清除本次新建之空資料夾, 含n為清除個數), 'settle'(整體結果, 含ok與code; ok為false時code為錯誤碼或null)。事件皆於對應動作之後、resolve或reject之前通知, 回調不帶接收者呼叫, 其拋錯與回傳值皆忽略, 不影響取得, 預設null

floatingManifestUrl String <optional>
null

輸入主幹模式之清單網址, 須在https://raw.githubusercontent.com//之下, 預設由清單之repo與manifestPath組成

maxAttempts Integer <optional>
3

輸入下載之最多嘗試次數(交fsDownloadFileVerify)

retryDelayMs Number <optional>
1000

輸入下載之重試間隔基數毫秒(交fsDownloadFileVerify)

stallMs Number <optional>
30000

輸入下載之停滯毫秒數(交fsDownloadFileVerify)

totalMs Number <optional>
600000

輸入每個檔下載之整體時間上限毫秒數(交fsDownloadFileVerify)

Returns:

回傳Promise, resolve回傳物件{ [name]: 絕對路徑 }(另有不可列舉之leftovers: 刪不掉之暫存夾[{ name, fp, error }]), reject回傳帶code之Error

Type
Promise

(async, static) fsEnsureBinsCore(manifest, opt) → {Promise}

Description:
  • 後端nodejs依清單取得外部執行檔(快取、鏡像、GitHub)並以清單之SHA-256與大小校驗, 回傳各檔之絕對路徑, 可傳入fs、fetch、spawn與環境變數

    規格: 外部執行檔之存放下載校驗與離線(制式規格, opensrcOper之dspec), 本函數為其§6之共用實作; 規則帳本R-BIN-ENSURE-01。各套件只提供清單、呼叫本函數取得路徑、匯出ensureBins(), 不得自寫下載、快取、鏡像、校驗。

    清單manifest(schema 1)為物件或JSON檔路徑(相對路徑以opt.pkgRoot為基準): { schema: 1, pkg, mode: 'pinned'|'floating', repo: '/', commit(40碼小寫十六進位, 釘選模式有path之檔時必填), manifestPath(主幹模式之清單於repo內之路徑, 預設'bin/manifest.json'), files: [...], note, version }。files之每項: name(檔名, 英數與._+-, 不分大小寫不得重複), 來源為path(repo內之相對路徑, 自有檔, 網址為https://raw.githubusercontent.com//<commit或master>/)、url(廠商檔, 須https、不得帶帳密、不得為releases/latest)或parts(分割壓縮檔, 每項{ path或url, sha256, size, name(選填, 預設為path或url之最後一段) }, 須另給assembled: { type: 'zip', sha256, size }為組合後之校驗值), sha256(64碼十六進位)與size(位元組數)為該來源檔之校驗值(parts者不給), extract: { type: 'zip', member(壓縮檔內之相對路徑), sha256, size }為解出之檔之校驗值, note(選填)。不認得之欄位、型別不符、名稱或路徑不合白名單皆為E_MANIFEST。

    取得順序: ①快取: 位置為<快取>//(內容定址, sha256為最終檔之值, 壓縮檔者為extract之值), 快取資料夾為opt.cacheDir, 否則環境變數W_BIN_CACHE時為<W_BIN_CACHE>/, 否則<opt.pkgRoot>/bin-cache; 命中亦校驗大小與SHA-256, 本行程內已校驗且檔案識別(裝置、inode、大小、修改與變更時間)未變者不重複雜湊, 不符者刪除(識別未變才刪)後重新取得。②鏡像: opt.mirror, 否則環境變數W_BIN_MIRROR, 為本地資料夾或http(s)基底網址, 結構為<鏡像>///; 壓縮檔者先找解出之最終檔, 再找壓縮檔(為其path或url之最後一段)或各分割檔; 鏡像之檔不符清單即拒用(不留檔)並續找下一來源。③GitHub或廠商網址(離線時不連)。任何來源皆經fsDownloadFileVerify之暫存檔、校驗、原子更名寫入, 不讀到半檔。

    離線: opt.offline, 否則環境變數W_BIN_OFFLINE為1或true時, 只用快取與鏡像, 缺檔時reject E_OFFLINE_MISSING, 訊息列出每個缺檔之名稱、sha256、大小、應放之快取與鏡像完整路徑、來源網址與準備方法, 錯誤物件之missing為[{ name, sha256, size, cachePath, mirrorPath, url, member, archive, parts, reasons }]。

    主幹模式(mode為floating): 先取主幹清單(opt.floatingManifestUrl, 否則https://raw.githubusercontent.com//master/, 上限1MB, 時限同opt.stallMs), 同樣檢核schema且須為同一pkg與repo; 成功時其副本存於<快取>/manifest-last.json(紀錄, 不作為校驗依據); 失敗(含離線、無效)時用傳入之基準清單。主幹清單之信任錨為GitHub主幹(規格M5), 故opt.floatingManifestUrl只能是https://raw.githubusercontent.com//之下(同一repo, 可改分支或路徑, 否則E_ARG), 套件之ensureBins不應轉傳使用者之值。

    壓縮檔: 於快取夾內本次專用之暫存夾(.work--<亂數>)取得壓縮檔(或各分割檔各自校驗後依序組合並校驗assembled), 以tar解出成員(Windows為\System32\tar.exe即bsdtar, 其他系統為bsdtar或tar, 須支援zip, 參數皆為暫存夾內之相對路徑, 成員前加--), 解出之檔須為一般檔案且符合extract之大小與SHA-256, 才原子移入快取; 暫存夾於結束時刪除, 刪不掉者(含下載核心刪不掉之暫存檔、主幹清單副本之暫存檔)列於結果之leftovers(不可列舉之屬性; 失敗時為錯誤物件之leftovers; 共用同一次取得者皆得到)。解壓時限同opt.totalMs, 逾時終止後等其結束至多5秒, 仍未結束則保留暫存夾並列入leftovers。解壓失敗為E_EXTRACT。

    清單與選項之字串一律須為原始字串(包裝物件new String與new Boolean不接受), 檢查之值即使用之值。離線時http(s)鏡像之重導不追隨(視為拒用)。

    併發: 同程序內同一最終檔且來源設定相同之併發呼叫共用同一次取得(其onProgress只通知最先呼叫者); 跨程序以暫存檔與原子更名保證不讀到半檔, 更名失敗而最終檔已是期望內容視為成功。各檔依清單順序逐一取得。

    錯誤: reject帶code之Error: 'E_ARG'(參數)、'E_MANIFEST'(清單, 帶field)、'E_OFFLINE_MISSING'(離線缺檔, 帶missing)、'E_EXTRACT'(解壓)、'E_RENAME'(移入快取), 以及fsDownloadFileVerify之code('E_HTTP'、'E_NETWORK'、'E_TIMEOUT'、'E_TIMEOUT_TOTAL'、'E_SIZE_MISMATCH'、'E_HASH_MISMATCH'、'E_FS'等, 訊息前綴fsEnsureBins[] file[] from <github|url>); 皆帶step, 屬某檔者另帶pkg與bin(檔名)。訊息與欄位內之網址帳密一律遮蔽(鏡像網址帶帳密者須傳入opt.fetch處理認證, 否則E_ARG)。

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsEnsureBins
Parameters:
Name Type Description
manifest Object | String

輸入清單物件(schema 1), 或其JSON檔路徑(相對路徑以opt.pkgRoot為基準)

opt Object

輸入設定物件

Properties
Name Type Attributes Default Description
pkgRoot String

輸入套件根目錄, 必填

names String | Array <optional>
null

輸入只取部分檔之名稱或其陣列, 預設null為全部

cacheDir String <optional>
''

輸入快取資料夾(其下為/), 優先於W_BIN_CACHE, ''為預設位置, 預設''

mirror String <optional>

輸入鏡像(本地資料夾或http(s)基底網址), 給了即優先於W_BIN_MIRROR(空字串為不使用鏡像)

offline Boolean <optional>

輸入是否離線, 給了即優先於W_BIN_OFFLINE

fetch function <optional>
null

輸入fetch函數(測試注入或處理認證), 未給時用全域fetch

onProgress function <optional>
null

輸入進度通知函數, 收到{ pkg, step('manifest'、'cache'、'mirror'、'download'、'extract'、'ready'、'cleanup'), name, ... }, 其拋錯皆忽略

onTrace function <optional>
null

輸入觀察回調函數, 於內部事件發生當下同步呼叫, 傳入一個凍結之事件物件{ type, ... }, 供測試以事件(而非等待時間)同步與診斷; 與onProgress(給使用者之進度)分開, 不轉傳給內部之下載核心。type與其欄位為: 'manifest'(本次採用之清單已確定, 含source為'base'[釘選模式之傳入清單]、'remote'[取得主幹清單]、'baseline'[主幹模式退回傳入之基準清單], 與files為該清單之檔案數), 'trunk'(主幹清單, 含phase為'fetch'[開始取, 時限計時器已建立]、'ok'[取得且檢核通過]、'fail'[失敗, 逾opt.stallMs而放棄者code為'E_TIMEOUT'], 與code為失敗之錯誤碼或null), 'cancel'(已取消主幹清單回應之本體, 含why為'unread'[讀取中途被中止]、'late'[放棄等待後才落定之回應]、'settled'[逾時勝出時已落定而未交出之回應]、'status'[非2xx之回應]), 'last'(主幹清單副本寫入manifest-last.json, 含ok), 'job'(各檔取得之登記, 含name與mode為'new'[新開一次取得]、'join'[共用同程序已在進行之同一次取得, 該次取得之其餘事件只通知最先呼叫者]、'solo'[選項無法序列化而不共用]), 'cache'(快取校驗結果, 含name與hit), 'mirror'(鏡像嘗試, 含name、file與result為'ok'、'miss'、'rejected'), 'offline'(離線缺檔之判定, 含name), 'work'(已建本次專用暫存夾, 含name), 'download'(開始自GitHub或廠商網址下載, 含name與file), 'downloaded'(下載結束, 含name、file、ok與code), 'extract'(開始解壓, 含name), 'kill'(解壓逾opt.totalMs而即將終止解壓程式, 含name), 'extracted'(解壓程式已結束或放棄等待, 含name、result為'exit'、'timeout'、'error', code為離開碼或null, exited為是否確認已結束或null), 'place'(解出檔已移入快取, 含name), 'cleanup'(暫存夾刪除結果, 含name與ok; 刪不掉或因解壓程式未結束而保留者ok為false), 'ready'(該檔已就緒, 含name), 'fail'(該檔失敗[離線缺檔除外], 含name與code), 'tidy'(離線缺檔reject前清除本次新建之空資料夾, 含n為清除個數), 'settle'(整體結果, 含ok與code; ok為false時code為錯誤碼或null)。事件皆於對應動作之後、resolve或reject之前通知, 回調不帶接收者呼叫, 其拋錯與回傳值皆忽略, 不影響取得, 預設null

floatingManifestUrl String <optional>
null

輸入主幹模式之清單網址, 須在https://raw.githubusercontent.com//之下, 預設由清單之repo與manifestPath組成

maxAttempts Integer <optional>
3

輸入下載之最多嘗試次數(交fsDownloadFileVerify)

retryDelayMs Number <optional>
1000

輸入下載之重試間隔基數毫秒(交fsDownloadFileVerify)

stallMs Number <optional>
30000

輸入下載之停滯毫秒數(交fsDownloadFileVerify)

totalMs Number <optional>
600000

輸入每個檔下載之整體時間上限毫秒數(交fsDownloadFileVerify)

fs Object

輸入fs, 須有mkdirSync、lstatSync、openSync、closeSync、writeSync、readFileSync、createReadStream、renameSync、unlinkSync、rmSync及fsDownloadFileVerifyCore所需者

path Object

輸入path, 須有resolve、join

crypto Object

輸入crypto, 須有createHash

Readable function

輸入stream之Readable, 須有toWeb、fromWeb、from

pipeline function

輸入stream/promises之pipeline

spawn function

輸入child_process之spawn(解壓用)

env Object <optional>
{}

輸入環境變數物件(讀W_BIN_CACHE、W_BIN_MIRROR、W_BIN_OFFLINE、SystemRoot)

Returns:

回傳Promise, resolve回傳物件{ [name]: 絕對路徑 }(另有不可列舉之leftovers: 刪不掉之暫存夾[{ name, fp, error }]), reject回傳帶code之Error

Type
Promise

(static) fsEvem(optopt) → {Object}

Description:
  • 後端nodejs跨程序事件發布與訂閱,主要為通過檔案內容變更與監測進行傳遞事件

    因使用chokidar,變更至少要3秒才能監測,不適用於頻繁觸發事件之工作

    事件名稱即事件檔案在事件資料夾下之檔名,故須為單一安全檔名(非空字串、不超過200位元組、不含路徑分隔字元與<>:"|?*及控制字元、不為.或..、不以.或空白結尾、不為Windows裝置名稱如CON與NUL),含.之名稱(如'user.login')原樣送達;不分大小寫之檔案系統上大小寫不同之名稱視為同一事件檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

import fsDeleteFolder from './src/fsDeleteFolder.mjs'
import fsEvem from './src/fsEvem.mjs'

let test = async () => {
    return new Promise((resolve, reject) => {
        let ms = []

        fsDeleteFolder('./_evps')

        let ev = fsEvem()

        ev.on('conut', (msg) => {
            console.log('conut', msg)
            ms.push(msg)
        })

        let n = 0
        let t = setInterval(() => {
            n++
            ev.emit('conut', n)
            if (n >= 3) {
                clearTimeout(t)
            }
        }, 3000)

        setTimeout(() => {
            ev.clear()
            console.log('ms', ms)
            resolve(ms)
        }, 12000)

    })
}
await test()
    .catch((err) => {
        console.log(err)
    })
// conut 1
// conut 2
// conut 3
// ms [ 1, 2, 3 ]
Parameters:
Name Type Attributes Default Description
opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fd String <optional>
'./_evps'

輸入建置事件檔案所在資料夾路徑字串,預設'./_evps'

polling Boolean <optional>
false

輸入是否使用輪循布林值,代表chokidar的usePolling,預設為false

timeInterval Integer <optional>
100

輸入當polling為true時偵測檔案變更間隔時間整數,代表chokidar開啟polling時的interval,單位為毫秒ms,預設為100

timeBinaryInterval Integer <optional>
300

輸入當polling為true時偵測二進位檔案變更間隔時間整數,代表chokidar開啟polling時的binaryInterval,單位為毫秒ms,預設為300

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'write'時為emit已寫出事件檔案(含evName為事件名稱,即檔名),'ready'時為內部監看事件檔案資料夾之watcher已就緒(初始掃描完成,此後寫入之事件檔案才經穩定期後確實被接收;僅傳了onTrace才監聽,不影響原行為,且不轉傳內部fsWatchFolder之事件流),'skip'時為監看到檔案事件但略過(含reason為略過原因,'kind'代表事件種類非add與change如資料夾本身之addDir或事件檔之unlink,'content'代表檔案內容無法解析為非空物件;kind為監看事件之種類;evName為由檔名推得之事件名稱),'emit'時為即將對本程序之監聽器派發一個事件(含evName為事件名稱;不含事件內容),'clear'時為clear被呼叫(僅首次);clear後不再通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響事件傳遞,預設null

Returns:

回傳事件物件,包含on、emit、clear函數,on可進行監聽指定事件,emit為跨程序發布事件(寫入事件檔案後由各程序之watcher接收再於本地派發),clear為停止全部監聽,不須輸入。事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯故其不會使行程崩潰;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection,監聽器出錯會改於本程序派發error事件{ fun: 'listener', name, msg, args }(僅本地不寫檔廣播),無error監聽者則console.error;故事件名稱請避免使用'error'。emit之事件名稱不合法(見上,如非字串、空字串、含路徑分隔字元、'..')時不寫出任何檔案並同步拋出Error物件,寫入事件檔案失敗(如資料夾無法寫入)時亦同步拋出Error物件,呼叫端須自行try

Type
Object

(static) fsExists(pah) → {Boolean}

Description:
  • 後端nodejs判斷是否為檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsExists'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn
    let fp

    fn = 't1.txt'
    fp = `${fdt}/${fn}`

    let b1 = fsExists(fp)
    console.log('fsExists file(before)', b1)
    ms.push({ 'fsExists file(before)': b1 })

    fsCreateFile(fp, 'abc', { encoding: 'utf8' })

    let b2 = fsExists(fp)
    console.log('fsExists file(after)', b2)
    ms.push({ 'fsExists file(after)': b2 })

    fn = 't2'
    fp = `${fdt}/${fn}`

    let b3 = fsExists(fp)
    console.log('fsExists folder(before)', b3)
    ms.push({ 'fsExists folder(before)': b3 })

    fsCreateFolder(fp)

    let b4 = fsExists(fp)
    console.log('fsExists folder(after)', b4)
    ms.push({ 'fsExists folder(after)': b4 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsExists file(before) false
// fsExists file(after) true
// fsExists folder(before) false
// fsExists folder(after) true
// ms [
//   { 'fsExists file(before)': false },
//   { 'fsExists file(after)': true },
//   { 'fsExists folder(before)': false },
//   { 'fsExists folder(after)': true }
// ]
Parameters:
Name Type Description
pah String

輸入路徑字串

Returns:

回傳是否布林值

Type
Boolean

(static) fsExistsCore(pah) → {Boolean}

Description:
  • 後端nodejs判斷是否為檔案或資料夾或符號連結

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsExists
Parameters:
Name Type Description
pah String

輸入路徑字串

Returns:

回傳是否布林值

Type
Boolean

(async, static) fsGetBinManifestEntry(fp, optopt) → {Promise}

Description:
  • 後端nodejs依實檔產生外部執行檔清單之一項{ name, path或url, sha256, size }, 供各套件之toolg/genBinManifest.mjs產生bin/manifest.json(清單不手寫sha256與大小)

    清單之格式與用法見fsEnsureBins(規格: 外部執行檔之存放下載校驗與離線, 規則帳本R-BIN-ENSURE-01)。name、path與url須合fsEnsureBins之規則(與其共用同一規則: url須https、不得帶帳密、不得為releases/latest或分支), path與url不可同給, 不合者reject。sha256為小寫64碼, size為位元組數; 計算對象為檔案之原始位元組, 自有檔須與GitHub上之內容(git blob)相同, 文字檔請以.gitattributes設為binary(不轉換換行)。

    Unit Test: Github

Source:
Example
//need test in nodejs

import fs from 'fs'

let test = async () => {
    let ms = []

    let fdt = './_test_fsGetBinManifestEntry'
    fsCreateFolder(fdt) //創建任務資料夾

    fs.writeFileSync(`${fdt}/tool.exe`, 'MZ-tool')

    let r1 = await fsGetBinManifestEntry(`${fdt}/tool.exe`, { path: 'bin/tool.exe' })
    console.log('r1', r1)
    ms.push(r1)

    let r2 = await fsGetBinManifestEntry(`${fdt}/tool.exe`)
    console.log('r2', r2)
    ms.push(r2)

    await fsGetBinManifestEntry(`${fdt}/none.exe`)
        .catch((err) => {
            ms.push({ err: typeof err })
        })

    fsDeleteFolder(fdt) //最終階段清除任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// r1 {
//   name: 'tool.exe',
//   path: 'bin/tool.exe',
//   sha256: '40f576db36632cf550d7d99bd6599c88a131ba725f46b29a95ae2d5359bb9913',
//   size: 7
// }
// r2 {
//   name: 'tool.exe',
//   sha256: '40f576db36632cf550d7d99bd6599c88a131ba725f46b29a95ae2d5359bb9913',
//   size: 7
// }
// ms [
//   {
//     name: 'tool.exe',
//     path: 'bin/tool.exe',
//     sha256: '40f576db36632cf550d7d99bd6599c88a131ba725f46b29a95ae2d5359bb9913',
//     size: 7
//   },
//   {
//     name: 'tool.exe',
//     sha256: '40f576db36632cf550d7d99bd6599c88a131ba725f46b29a95ae2d5359bb9913',
//     size: 7
//   },
//   { err: 'string' }
// ]
Parameters:
Name Type Attributes Default Description
fp String

輸入檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Description
name String <optional>

輸入清單項之name,預設為fp之檔名

path String <optional>

輸入清單項之path(repo內之相對路徑, 以/分隔),給了才放入

url String <optional>

輸入清單項之url(廠商檔之下載網址),給了才放入

Returns:

回傳Promise,resolve回傳清單項物件{ name, path, url, sha256, size }(path、url給了才有),reject回傳錯誤訊息字串

Type
Promise

(static) fsGetFileBasicHash(fp, optopt) → {Promise}

Description:
  • 後端nodejs計算檔案HASH值

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = async () => {

    let ms = []

    let fdt = './_test_fsGetFileBasicHash'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/abc/${fn}`

    fsWriteText(fp, 'xyz')

    let h1 = await fsGetFileBasicHash(fp)
    console.log('fsGetFileBasicHash(sha512)', h1)
    ms.push({ 'fsGetFileBasicHash(sha512)': h1 })

    let h2 = await fsGetFileBasicHash(fp, { type: 'sha256' })
    console.log('fsGetFileBasicHash(sha256)', h2)
    ms.push({ 'fsGetFileBasicHash(sha256)': h2 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// fsGetFileBasicHash(sha512) 4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a58e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728
// fsGetFileBasicHash(sha256) 3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282
// ms [
//   {
//     'fsGetFileBasicHash(sha512)': '4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a58e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728'
//   },
//   {
//     'fsGetFileBasicHash(sha256)': '3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282'
//   }
// ]
Parameters:
Name Type Attributes Default Description
fp String

輸入檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'sha512'

輸入計算HASH方法字串,預設'sha512'

Returns:

回傳Promise,resolve回傳檔案HASH值,reject回傳錯誤訊息字串

Type
Promise

(static) fsGetFileBasicHashCore(fp, optopt) → {Promise}

Description:
  • 後端nodejs計算檔案HASH值

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsGetFileBasicHash
Parameters:
Name Type Attributes Default Description
fp String

輸入檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'sha512'

輸入計算HASH方法字串,預設'sha512'

Returns:

回傳Promise,resolve回傳檔案HASH值,reject回傳錯誤訊息字串

Type
Promise

(static) fsGetFileHash(fp, optopt) → {Promise}

Description:
  • 後端nodejs計算檔案HASH值

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = async () => {

    let ms = []

    let fdt = './_test_fsGetFileHash'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/abc/${fn}`

    fsWriteText(fp, 'xyz')

    let h1 = await fsGetFileHash(fp)
    console.log('fsGetFileHash(sha512)', h1)
    ms.push({ 'fsGetFileHash(sha512)': h1 })

    let h2 = await fsGetFileHash(fp, { type: 'sha256' })
    console.log('fsGetFileHash(sha256)', h2)
    ms.push({ 'fsGetFileHash(sha256)': h2 })

    let h3 = await fsGetFileHash(fp, { type: 'xxhash64' })
    console.log('fsGetFileHash(xxhash64)', h3)
    ms.push({ 'fsGetFileHash(xxhash64)': h3 })

    let h4 = await fsGetFileHash(fp, { type: 'xxhash64', chunkSize: 16 * 1024 * 1024 })
    console.log('fsGetFileHash(xxhash64,chunkSize=16mb)', h4)
    ms.push({ 'fsGetFileHash(xxhash64,chunkSize=16mb)': h4 })

    let h5 = await fsGetFileHash(fp, { type: 'xxhash64', chunkSize: 4 * 1024 * 1024 })
    console.log('fsGetFileHash(xxhash64,chunkSize=4mb)', h5)
    ms.push({ 'fsGetFileHash(xxhash64,chunkSize=4mb)': h5 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// fsGetFileHash(sha512) 4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a58e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728
// fsGetFileHash(sha256) 3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282
// fsGetFileHash(xxhash64) feba48465b833ca1
// fsGetFileHash(xxhash64,chunkSize=16mb) feba48465b833ca1
// fsGetFileHash(xxhash64,chunkSize=4mb) feba48465b833ca1
// ms [
//   {
//     'fsGetFileHash(sha512)': '4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a58e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728'
//   },
//   {
//     'fsGetFileHash(sha256)': '3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282'
//   },
//   { 'fsGetFileHash(xxhash64)': 'feba48465b833ca1' },
//   { 'fsGetFileHash(xxhash64,chunkSize=16mb)': 'feba48465b833ca1' },
//   { 'fsGetFileHash(xxhash64,chunkSize=4mb)': 'feba48465b833ca1' }
// ]
Parameters:
Name Type Attributes Default Description
fp String

輸入檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'sha512'

輸入計算HASH方法字串,預設'sha512'

chunkSize Number <optional>
64*1024*1024

輸入計算HASH方法字串為'xxhash64'時,使用切片長度數字,單位位元,上限2^30(102410241024,1gb),非正整數(0、負數、小數、非數字)或超過上限時使用預設值,預設6410241024(64mb)

Returns:

回傳Promise,resolve回傳檔案HASH值,reject回傳錯誤訊息字串

Type
Promise

(static) fsGetFileHashCore(fp, optopt) → {Promise}

Description:
  • 後端nodejs計算檔案HASH值

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsGetFileHash
Parameters:
Name Type Attributes Default Description
fp String

輸入檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'sha512'

輸入計算HASH方法字串,預設'sha512'

chunkSize Number <optional>
64*1024*1024

輸入計算HASH方法字串為'xxhash64'時,使用切片長度數字,單位位元,上限2^30(102410241024,1gb),非正整數(0、負數、小數、非數字)或超過上限時使用預設值,預設6410241024(64mb)

Returns:

回傳Promise,resolve回傳檔案HASH值,reject回傳錯誤訊息字串

Type
Promise

(static) fsGetFileXxHash(fp, optopt) → {Promise}

Description:
  • 後端nodejs計算檔案xxHash64值

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = async () => {

    let ms = []

    let fdt = './_test_fsGetFileXxHash'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/abc/${fn}`

    fsWriteText(fp, 'xyz')

    let h1 = await fsGetFileXxHash(fp)
    console.log('fsGetFileXxHash(64mb)', h1)
    ms.push({ 'fsGetFileXxHash(64mb)': h1 })

    let h2 = await fsGetFileXxHash(fp, { chunkSize: 16 * 1024 * 1024 })
    console.log('fsGetFileXxHash(16mb)', h2)
    ms.push({ 'fsGetFileXxHash(16mb)': h2 })

    let h3 = await fsGetFileXxHash(fp, { chunkSize: 4 * 1024 * 1024 })
    console.log('fsGetFileXxHash(4mb)', h3)
    ms.push({ 'fsGetFileXxHash(4mb)': h3 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// fsGetFileXxHash(64mb) feba48465b833ca1
// fsGetFileXxHash(16mb) feba48465b833ca1
// fsGetFileXxHash(4mb) feba48465b833ca1
// ms [
//   { 'fsGetFileXxHash(64mb)': 'feba48465b833ca1' },
//   { 'fsGetFileXxHash(16mb)': 'feba48465b833ca1' },
//   { 'fsGetFileXxHash(4mb)': 'feba48465b833ca1' }
// ]
Parameters:
Name Type Attributes Default Description
fp String

輸入檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
chunkSize Number <optional>
64*1024*1024

輸入切片長度數字,單位位元,上限2^30(102410241024,1gb),非正整數(0、負數、小數、非數字)或超過上限時使用預設值,預設6410241024(64mb)

Returns:

回傳Promise,resolve回傳檔案HASH值,reject回傳錯誤訊息字串

Type
Promise

(async, static) fsGetFileXxHashCore(fp, optopt) → {Promise}

Description:
  • 後端nodejs計算檔案xxHash64值

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsGetFileXxHash
Parameters:
Name Type Attributes Default Description
fp String

輸入檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
chunkSize Number <optional>
64*1024*1024

輸入切片長度數字,單位位元,上限2^30(102410241024,1gb),非正整數(0、負數、小數、非數字)或超過上限時使用預設值,預設6410241024(64mb)

Returns:

回傳Promise,resolve回傳檔案HASH值,reject回傳錯誤訊息字串

Type
Promise

(static) fsGetFilesInFolder(fd, levelLimitopt) → {Array}

Description:
  • 後端nodejs列舉指定資料夾下的全部檔案

    Unit Test: Github

Source:
Example
//need test in nodejs

let test = () => {

    let ms = []

    let t = '_test_fsGetFilesInFolder'
    let fdt = './_test_fsGetFilesInFolder'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let ftpath = (v) => {
        // console.log(v, 'v.path', v.path)
        let ss = _.split(v.path, t)
        // console.log('ss', ss)
        let p = ss[1]
        p = p.replaceAll('\\', '/')
        p = `.${p}`
        // console.log('p', p)
        v.path = p
        return v
    }

    let ftpaths = (vs) => {
        return _.map(_.cloneDeep(vs), ftpath)
    }

    fsWriteText(`${fdt}/z1.txt`, 'z1')
    fsWriteText(`${fdt}/abc/z2.txt`, 'z2')
    fsWriteText(`${fdt}/def/ijk/z3.txt`, 'z3')
    fsCreateFolder(`${fdt}/mno/pqr`)

    let r1 = fsGetFilesInFolder(fdt, 1)
    console.log('fsGetFilesInFolder(levelLimit=1)', ftpaths(r1))
    ms.push({ 'fsGetFilesInFolder(evelLimit=1)': ftpaths(r1) })

    let rall = fsGetFilesInFolder(fdt, null)
    console.log('fsGetFilesInFolder(levelLimit=null)', ftpaths(rall))
    ms.push({ 'fsGetFilesInFolder(evelLimit=null)': ftpaths(rall) })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', JSON.stringify(ms))
    return ms
}
test()
// fsGetFilesInFolder(levelLimit=1) [ { level: 1, path: './z1.txt', name: 'z1.txt' } ]
// fsGetFilesInFolder(levelLimit=null) [
//   { level: 2, path: './abc/z2.txt', name: 'z2.txt' },
//   { level: 3, path: './def/ijk/z3.txt', name: 'z3.txt' },
//   { level: 1, path: './z1.txt', name: 'z1.txt' }
// ]
// ms [{"fsGetFilesInFolder(evelLimit=1)":[{"level":1,"path":"./z1.txt","name":"z1.txt"}]},{"fsGetFilesInFolder(evelLimit=null)":[{"level":2,"path":"./abc/z2.txt","name":"z2.txt"},{"level":3,"path":"./def/ijk/z3.txt","name":"z3.txt"},{"level":1,"path":"./z1.txt","name":"z1.txt"}]}]
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案,設定null為無窮遍歷所有檔案,預設1

Returns:

回傳列舉檔案陣列,若fd不是資料夾則同步拋出Error(訊息為'fd[...] is not a folder'),列舉時讀取資料夾失敗(如無讀取權限之EPERM)亦同步拋出其Error

Type
Array

(static) fsGetFilesInFolderCore(fd, levelLimitopt) → {Array}

Description:
  • 後端nodejs列舉指定資料夾下的全部檔案

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsGetFilesInFolder
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案,設定null為無窮遍歷所有檔案,預設1

Returns:

回傳列舉檔案陣列,若fd不是資料夾則同步拋出Error(訊息為'fd[...] is not a folder'),列舉時讀取資料夾失敗(如無讀取權限之EPERM)亦同步拋出其Error

Type
Array

(async, static) fsGetFilesWithHashInFolder(fd, levelLimitopt, optopt) → {Promise}

Description:
  • 後端nodejs列舉指定資料夾下的全部檔案與對應HASH值

    Unit Test: Github

Source:
Example
//need test in nodejs

let test = async () => {

    let ms = []

    let t = '_test_fsGetFilesWithHashInFolder'
    let fdt = './_test_fsGetFilesWithHashInFolder'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let ftpath = (v) => {
        // console.log(v, 'v.path', v.path)
        let ss = _.split(v.path, t)
        // console.log('ss', ss)
        let p = ss[1]
        p = p.replaceAll('\\', '/')
        p = `.${p}`
        // console.log('p', p)
        v.path = p
        return v
    }

    let ftpaths = (vs) => {
        return _.map(_.cloneDeep(vs), ftpath)
    }

    fsWriteText(`${fdt}/z1.txt`, 'z1')
    fsWriteText(`${fdt}/abc/z2.txt`, 'z2')
    fsWriteText(`${fdt}/def/ijk/z3.txt`, 'z3')
    fsCreateFolder(`${fdt}/mno/pqr`)

    let r1 = await fsGetFilesWithHashInFolder(fdt, 1)
    console.log('fsGetFilesWithHashInFolder(levelLimit=1)', ftpaths(r1))
    ms.push({ 'fsGetFilesWithHashInFolder(evelLimit=1)': ftpaths(r1) })

    let rall = await fsGetFilesWithHashInFolder(fdt, null)
    console.log('fsGetFilesWithHashInFolder(levelLimit=null)', ftpaths(rall))
    ms.push({ 'fsGetFilesWithHashInFolder(evelLimit=null)': ftpaths(rall) })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', JSON.stringify(ms))
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// fsGetFilesWithHashInFolder(levelLimit=1) [
//   {
//     level: 1,
//     path: './z1.txt',
//     name: 'z1.txt',
//     hashType: 'md5',
//     hash: '3b770ebe9b04f171f0ead0e07d8e2882'
//   }
// ]
// fsGetFilesWithHashInFolder(levelLimit=null) [
//   {
//     level: 2,
//     path: './abc/z2.txt',
//     name: 'z2.txt',
//     hashType: 'md5',
//     hash: '5cb7e380d019de63c643aef55b8534d0'
//   },
//   {
//     level: 3,
//     path: './def/ijk/z3.txt',
//     name: 'z3.txt',
//     hashType: 'md5',
//     hash: 'a61d1457beb4684e254ce60379c8ae7b'
//   },
//   {
//     level: 1,
//     path: './z1.txt',
//     name: 'z1.txt',
//     hashType: 'md5',
//     hash: '3b770ebe9b04f171f0ead0e07d8e2882'
//   }
// ]
// ms [{"fsGetFilesWithHashInFolder(evelLimit=1)":[{"level":1,"path":"./z1.txt","name":"z1.txt","hashType":"md5","hash":"3b770ebe9b04f171f0ead0e07d8e2882"}]},{"fsGetFilesWithHashInFolder(evelLimit=null)":[{"level":2,"path":"./abc/z2.txt","name":"z2.txt","hashType":"md5","hash":"5cb7e380d019de63c643aef55b8534d0"},{"level":3,"path":"./def/ijk/z3.txt","name":"z3.txt","hashType":"md5","hash":"a61d1457beb4684e254ce60379c8ae7b"},{"level":1,"path":"./z1.txt","name":"z1.txt","hashType":"md5","hash":"3b770ebe9b04f171f0ead0e07d8e2882"}]}]
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案,設定null為無窮遍歷所有檔案,預設1

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'md5'

輸入計算HASH方法字串,預設'md5'

Returns:

回傳Promise,resolve回傳列舉檔案陣列,reject回傳錯誤訊息字串

Type
Promise

(async, static) fsGetFilesWithHashInFolderCore(fd, levelLimitopt, optopt) → {Promise}

Description:
  • 後端nodejs列舉指定資料夾下的全部檔案與對應HASH值

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsGetFilesWithHashInFolder
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案,設定null為無窮遍歷所有檔案,預設1

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'md5'

輸入計算HASH方法字串,預設'md5'

Returns:

回傳Promise,resolve回傳列舉檔案陣列,reject回傳錯誤訊息字串

Type
Promise

(static) fsGetFolderBasicHash(fd, optopt) → {Promise}

Description:
  • 後端nodejs計算資料夾HASH值

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = async () => {

    let ms = []

    let fdt = './_test_fsGetFolderBasicHash'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fp

    fp = `${fdt}/abc/t1.txt`
    fsWriteText(fp, 'abc-t1')

    fp = `${fdt}/def/xyz/t2.txt`
    fsWriteText(fp, 'def-xyz-t2')

    let h1 = await fsGetFolderBasicHash(fdt)
    console.log('fsGetFolderBasicHash(md5)', h1)
    ms.push({ 'fsGetFolderBasicHash(md5)': h1 })

    let h2 = await fsGetFolderBasicHash(fdt, { type: 'sha256' })
    console.log('fsGetFolderBasicHash(sha256)', h2)
    ms.push({ 'fsGetFolderBasicHash(sha256)': h2 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// fsGetFolderBasicHash(md5) 45d11ad27b97bc3bb664260189ec0dcc|ac8cf7366d7adf0743d6561133d404b3
// fsGetFolderBasicHash(sha256) 2cc76b7a715d35c3b8f2d2585b144907883ea9167d230afbee4c4a5ab0634152|bf2fa34249f4dbbd72b3a754b56f4beb9b8dede8b64e21ce8b83696e6febb8bb
// ms [
//   {
//     'fsGetFolderBasicHash(md5)': '45d11ad27b97bc3bb664260189ec0dcc|ac8cf7366d7adf0743d6561133d404b3'
//   },
//   {
//     'fsGetFolderBasicHash(sha256)': '2cc76b7a715d35c3b8f2d2585b144907883ea9167d230afbee4c4a5ab0634152|bf2fa34249f4dbbd72b3a754b56f4beb9b8dede8b64e21ce8b83696e6febb8bb'
//   }
// ]
Parameters:
Name Type Attributes Default Description
fd String

輸入資料夾路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'md5'

輸入計算HASH方法字串,預設'md5'

Returns:

回傳Promise,resolve回傳資料夾HASH值,reject回傳錯誤訊息字串

Type
Promise

(async, static) fsGetFolderBasicHashCore(fd, optopt) → {Promise}

Description:
  • 後端nodejs計算資料夾HASH值

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsGetFolderBasicHash
Parameters:
Name Type Attributes Default Description
fd String

輸入資料夾路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'md5'

輸入計算HASH方法字串,預設'md5'

Returns:

回傳Promise,resolve回傳資料夾HASH值,reject回傳錯誤訊息字串

Type
Promise

(static) fsGetFoldersInFolder(fd, levelLimitopt) → {Array}

Description:
  • 後端nodejs列舉指定資料夾下的全部資料夾

    Unit Test: Github

Source:
Example
//need test in nodejs

let test = () => {

    let ms = []

    let t = '_test_fsGetFoldersInFolder'
    let fdt = './_test_fsGetFoldersInFolder'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let ftpath = (v) => {
        // console.log(v, 'v.path', v.path)
        let ss = _.split(v.path, t)
        // console.log('ss', ss)
        let p = ss[1]
        p = p.replaceAll('\\', '/')
        p = `.${p}`
        // console.log('p', p)
        v.path = p
        return v
    }

    let ftpaths = (vs) => {
        return _.map(_.cloneDeep(vs), ftpath)
    }

    fsWriteText(`${fdt}/z1.txt`, 'z1')
    fsWriteText(`${fdt}/abc/z2.txt`, 'z2')
    fsWriteText(`${fdt}/def/ijk/z3.txt`, 'z3')
    fsCreateFolder(`${fdt}/mno/pqr`)

    let r1 = fsGetFoldersInFolder(fdt, 1)
    console.log('fsGetFoldersInFolder(levelLimit=1)', ftpaths(r1))
    ms.push({ 'fsGetFoldersInFolder(evelLimit=1)': ftpaths(r1) })

    let rall = fsGetFoldersInFolder(fdt, null)
    console.log('fsGetFoldersInFolder(levelLimit=null)', ftpaths(rall))
    ms.push({ 'fsGetFoldersInFolder(evelLimit=null)': ftpaths(rall) })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', JSON.stringify(ms))
    return ms
}
test()
// fsGetFoldersInFolder(levelLimit=1) [
//   { level: 1, path: './abc', name: 'abc' },
//   { level: 1, path: './def', name: 'def' },
//   { level: 1, path: './mno', name: 'mno' }
// ]
// fsGetFoldersInFolder(levelLimit=null) [
//   { level: 1, path: './abc', name: 'abc' },
//   { level: 1, path: './def', name: 'def' },
//   { level: 2, path: './def/ijk', name: 'ijk' },
//   { level: 1, path: './mno', name: 'mno' },
//   { level: 2, path: './mno/pqr', name: 'pqr' }
// ]
// ms [{"fsGetFoldersInFolder(evelLimit=1)":[{"level":1,"path":"./abc","name":"abc"},{"level":1,"path":"./def","name":"def"},{"level":1,"path":"./mno","name":"mno"}]},{"fsGetFoldersInFolder(evelLimit=null)":[{"level":1,"path":"./abc","name":"abc"},{"level":1,"path":"./def","name":"def"},{"level":2,"path":"./def/ijk","name":"ijk"},{"level":1,"path":"./mno","name":"mno"},{"level":2,"path":"./mno/pqr","name":"pqr"}]}]
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的資料夾,設定null為無窮遍歷所有資料夾,預設1

Returns:

回傳列舉資料夾陣列,若fd不是資料夾則同步拋出Error(訊息為'fd[...] is not a folder'),列舉時讀取資料夾失敗(如無讀取權限之EPERM)亦同步拋出其Error

Type
Array

(static) fsGetFoldersInFolderCore(fd, levelLimitopt) → {Array}

Description:
  • 後端nodejs列舉指定資料夾下的全部資料夾

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsGetFoldersInFolder
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的資料夾,設定null為無窮遍歷所有資料夾,預設1

Returns:

回傳列舉資料夾陣列,若fd不是資料夾則同步拋出Error(訊息為'fd[...] is not a folder'),列舉時讀取資料夾失敗(如無讀取權限之EPERM)亦同步拋出其Error

Type
Array

(static) fsIsFile(pah) → {Boolean}

Description:
  • 後端nodejs判斷是否為檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsIsFile'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/${fn}`

    let b1 = fsIsFile(fp)
    console.log('fsIsFile(before)', b1)
    ms.push({ 'fsIsFile(before)': b1 })

    fsCreateFile(fp, 'abc', { encoding: 'utf8' })

    let b2 = fsIsFile(fp)
    console.log('fsIsFile(after)', b2)
    ms.push({ 'fsIsFile(after)': b2 })

    fsDeleteFile(fdt)

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsIsFile(before) false
// fsIsFile(after) true
// ms [ { 'fsIsFile(before)': false }, { 'fsIsFile(after)': true } ]
Parameters:
Name Type Description
pah String

輸入路徑字串

Returns:

回傳是否布林值

Type
Boolean

(static) fsIsFileCore(pah) → {Boolean}

Description:
  • 後端nodejs判斷是否為檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsIsFile
Parameters:
Name Type Description
pah String

輸入路徑字串

Returns:

回傳是否布林值

Type
Boolean

(static) fsIsFolder(pah) → {Boolean}

Description:
  • 後端nodejs判斷是否為資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsIsFolder'

    let b1 = fsIsFolder(fdt)
    console.log('fsIsFolder(before)', b1)
    ms.push({ 'fsIsFolder(before)': b1 })

    fsCreateFolder(fdt)

    let b2 = fsIsFolder(fdt)
    console.log('fsIsFolder(after)', b2)
    ms.push({ 'fsIsFolder(after)': b2 })

    fsDeleteFolder(fdt)

    console.log('ms', ms)
    return ms
}
test()
// fsIsFolder(before) false
// fsIsFolder(after) true
// ms [ { 'fsIsFolder(before)': false }, { 'fsIsFolder(after)': true } ]
Parameters:
Name Type Description
pah String

輸入路徑字串

Returns:

回傳是否布林值

Type
Boolean

(static) fsIsFolderCore(pah) → {Boolean}

Description:
  • 後端nodejs判斷是否為資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsIsFolder
Parameters:
Name Type Description
pah String

輸入路徑字串

Returns:

回傳是否布林值

Type
Boolean

(async, static) fsMergeFiles(fpsIn, fpOut, optopt) → {Promise}

Description:
  • 後端nodejs合併多檔案

    Unit Test: Github

Source:
Example
//need test in nodejs

let test = async () => {
    let ms = []

    let fdt = './_test_fsMergeFiles'
    fsCreateFolder(fdt) //創建任務資料夾

    fs.writeFileSync(`${fdt}/t1.txt`, 'abc', 'utf8')
    fs.writeFileSync(`${fdt}/t2.txt`, 'def', 'utf8')
    fs.writeFileSync(`${fdt}/t3.txt`, '中文', 'utf8')
    fs.writeFileSync(`${fdt}/t4.txt`, '測 試', 'utf8')
    fs.writeFileSync(`${fdt}/t5.txt`, '&*#$%', 'utf8')

    let fnOut = '合併檔案.txt'
    let fpsIn = [
        `${fdt}/t1.txt`,
        `${fdt}/t2.txt`,
        `${fdt}/t3.txt`,
        `${fdt}/t4.txt`,
        `${fdt}/t5.txt`,
    ]
    let fpOut = `${fdt}/m.txt`
    await fsMergeFiles(fpsIn, fpOut, { fnOut })
        .then((res) => {
            console.log('res', res)
            ms.push(res)
        })
        .catch((err) => {
            console.log('err', err)
        })

    let c = fs.readFileSync(fpOut, 'utf8')
    console.log('c', c)
    ms.push({ content: c })

    fsDeleteFolder(fdt) //最終階段清除任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// fpIn ./_test_fsMergeFiles/t1.txt
// fpIn ./_test_fsMergeFiles/t2.txt
// fpIn ./_test_fsMergeFiles/t3.txt
// fpIn ./_test_fsMergeFiles/t4.txt
// fpIn ./_test_fsMergeFiles/t5.txt
// res { filename: '合併檔案.txt', path: './_test_fsMergeFiles/m.txt' }
// c abcdef中文測 試&*#$%
// ms [
//   { filename: '合併檔案.txt', path: './_test_fsMergeFiles/m.txt' },
//   { content: 'abcdef中文測 試&*#$%' }
// ]
Parameters:
Name Type Attributes Default Description
fpsIn Array

輸入合併前各切片檔案路徑陣列

fpOut String

輸入合併後檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fnOut String <optional>
getFileName(fpOut)

輸入合併後檔案名稱字串,僅回傳時會使用,預設getFileName(fpOut)

Returns:

回傳Promise,resolve回傳合併後物件,reject回傳錯誤訊息字串

Type
Promise

(async, static) fsMergeFilesCore(fn, fpsIn, fpOut, optopt) → {Promise}

Description:
  • 後端nodejs合併多檔案

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsMergeFiles
Parameters:
Name Type Attributes Default Description
fn String

輸入實際檔名字串

fpsIn Array

輸入合併前各切片檔案路徑陣列

fpOut String

輸入合併後檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fnOut String <optional>
getFileName(fpOut)

輸入合併後檔案名稱字串,僅回傳時會使用,預設getFileName(fpOut)

Returns:

回傳Promise,resolve回傳合併後物件,reject回傳錯誤訊息字串

Type
Promise

(static) fsReadJson(fp) → {Object}

Description:
  • 後端nodejs由檔案讀取JSON格式文字成為物件或陣列

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsReadJson'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.json'
    let fp = `${fdt}/${fn}`

    fsWriteText(fp, JSON.stringify({ str: '測試中文', val: 123.45, int: 5 }))

    let r = fsReadJson(fp)
    console.log('fsReadJson', r)
    ms.push({ 'fsReadJson': JSON.stringify(r) })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsReadJson { success: { str: '測試中文', val: 123.45, int: 5 } }
// ms [ { fsReadJson: '{"success":{"str":"測試中文","val":123.45,"int":5}}' } ]
Parameters:
Name Type Description
fp String

輸入JSON檔案名稱

Returns:

回傳結果物件,提供鍵success代表解析後之物件或陣列,提供鍵error代表錯誤訊息(讀檔或JSON解析之Error物件)

Type
Object

(static) fsReadText(fp) → {Object}

Description:
  • 後端nodejs由檔案讀取utf-8文字

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsReadText'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/abc/${fn}`

    let b1 = fsIsFile(fp)
    console.log('fsIsFile(before)', b1)
    ms.push({ 'fsIsFile(before)': b1 })

    let rw = fsWriteText(fp, 'abc')
    console.log('fsWriteText', rw)
    ms.push({ 'fsWriteText': rw })

    let b2 = fsIsFile(fp)
    console.log('fsIsFile(after)', b2)
    ms.push({ 'fsIsFile(after)': b2 })

    let rr = fsReadText(fp, 'abc')
    console.log('fsReadText', rr)
    ms.push({ 'fsReadText': rr })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsIsFile(before) false
// fsWriteText { success: './_test_fsReadText/abc/t1.txt' }
// fsIsFile(after) true
// fsReadText { success: 'abc' }
// ms [
//   { 'fsIsFile(before)': false },
//   { fsWriteText: { success: './_test_fsReadText/abc/t1.txt' } },
//   { 'fsIsFile(after)': true },
//   { fsReadText: { success: 'abc' } }
// ]
Parameters:
Name Type Description
fp String

輸入檔案名稱

Returns:

回傳結果物件,提供鍵success代表檔案內容utf-8字串,提供鍵error代表錯誤訊息(讀檔之Error物件)

Type
Object

(static) fsReadTextCore(fp) → {Object}

Description:
  • 後端nodejs由檔案讀取utf-8文字

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsReadText
Parameters:
Name Type Description
fp String

輸入檔案名稱

Returns:

回傳結果物件,提供鍵success代表檔案內容utf-8字串,提供鍵error代表錯誤訊息(讀檔之Error物件,注入之fs拋出非Error之值時為其字串)

Type
Object

(static) fsRenameFile(pahOld, pahNew) → {Object}

Description:
  • 後端nodejs重新命名檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsRenameFile'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fpSrc = `${fdt}/abc/${fn}`
    let fpTar = `${fdt}/def/ijk/${fn}`

    fsWriteText(fpSrc, 'xyz')

    let b1 = fsIsFile(fpSrc)
    console.log('fsRenameFile(src)(before)', b1)
    ms.push({ 'fsRenameFile(src)(before)': b1 })

    let b2 = fsIsFile(fpTar)
    console.log('fsRenameFile(tar)(before)', b2)
    ms.push({ 'fsRenameFile(tar)(before)': b2 })

    let r = fsRenameFile(fpSrc, fpTar)
    console.log('fsRenameFile', r)
    ms.push({ 'fsRenameFile': r })

    let b3 = fsIsFile(fpSrc)
    console.log('fsRenameFile(src)(after)', b3)
    ms.push({ 'fsRenameFile(src)(after)': b3 })

    let b4 = fsIsFile(fpTar)
    console.log('fsRenameFile(tar)(after)', b4)
    ms.push({ 'fsRenameFile(tar)(after)': b4 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsRenameFile(src)(before) true
// fsRenameFile(tar)(before) false
// fsRenameFile { success: 'done: ./_test_fsRenameFile/def/ijk/t1.txt' }
// fsRenameFile(src)(after) false
// fsRenameFile(tar)(after) true
// ms [
//   { 'fsRenameFile(src)(before)': true },
//   { 'fsRenameFile(tar)(before)': false },
//   {
//     fsRenameFile: { success: 'done: ./_test_fsRenameFile/def/ijk/t1.txt' }
//   },
//   { 'fsRenameFile(src)(after)': false },
//   { 'fsRenameFile(tar)(after)': true }
// ]
Parameters:
Name Type Description
pahOld String

輸入檔案原本路徑字串

pahNew String

輸入檔案更名路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsRenameFileCore(pahOld, pahNew) → {Object}

Description:
  • 後端nodejs重新命名檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsRenameFile
Parameters:
Name Type Description
pahOld String

輸入檔案原本路徑字串

pahNew String

輸入檔案更名路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsRenameFolder(pahOld, pahNew) → {Object}

Description:
  • 後端nodejs重新命名資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsRenameFolder'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fdSrc = `${fdt}/abc`
    let fpSrc = `${fdt}/abc/${fn}`
    let fdTar = `${fdt}/def/abc`
    let fpTar = `${fdt}/def/abc/${fn}`

    fsWriteText(fpSrc, 'xyz')

    let b1 = fsIsFile(fpSrc)
    console.log('fsIsFile(before)', b1)
    ms.push({ 'fsIsFile(before)': b1 })

    let b2 = fsIsFolder(fdSrc)
    console.log('fsIsFolder(before)', b2)
    ms.push({ 'fsIsFolder(before)': b2 })

    let b3 = fsIsFile(fpTar)
    console.log('fsIsFile(before)', b3)
    ms.push({ 'fsIsFile(before)': b3 })

    let b4 = fsIsFolder(fdTar)
    console.log('fsIsFolder(before)', b4)
    ms.push({ 'fsIsFolder(before)': b4 })

    let r = fsRenameFolder(fdSrc, fdTar)
    console.log('fsRenameFolder', r)
    ms.push({ 'fsRenameFolder': r })

    let b5 = fsIsFile(fpSrc)
    console.log('fsIsFile(after)', b5)
    ms.push({ 'fsIsFile(after)': b5 })

    let b6 = fsIsFolder(fdSrc)
    console.log('fsIsFolder(after)', b6)
    ms.push({ 'fsIsFolder(after)': b6 })

    let b7 = fsIsFile(fpTar)
    console.log('fsIsFile(after)', b7)
    ms.push({ 'fsIsFile(after)': b7 })

    let b8 = fsIsFolder(fdTar)
    console.log('fsIsFolder(after)', b8)
    ms.push({ 'fsIsFolder(after)': b8 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsIsFile(before) true
// fsIsFolder(before) true
// fsIsFile(before) false
// fsIsFolder(before) false
// fsRenameFolder { success: 'done: ./_test_fsRenameFolder/def/abc' }
// fsIsFile(after) false
// fsIsFolder(after) false
// fsIsFile(after) true
// fsIsFolder(after) true
// ms [
//   { 'fsIsFile(before)': true },
//   { 'fsIsFolder(before)': true },
//   { 'fsIsFile(before)': false },
//   { 'fsIsFolder(before)': false },
//   { fsRenameFolder: { success: 'done: ./_test_fsRenameFolder/def/abc' } },
//   { 'fsIsFile(after)': false },
//   { 'fsIsFolder(after)': false },
//   { 'fsIsFile(after)': true },
//   { 'fsIsFolder(after)': true }
// ]
Parameters:
Name Type Description
pahOld String

輸入資料夾原本路徑字串

pahNew String

輸入資料夾更名路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsRenameFolderCore(pahOld, pahNew) → {Object}

Description:
  • 後端nodejs重新命名資料夾

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsRenameFolder
Parameters:
Name Type Description
pahOld String

輸入資料夾原本路徑字串

pahNew String

輸入資料夾更名路徑字串

Returns:

回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件)

Type
Object

(static) fsSrlog(optopt) → {function}

Description:
  • 後端顯示與紀錄log訊息

    Unit Test: Github

Source:
Example
need test in nodejs.

let opt = {
    useTime: false,
}
let srlog = fsSrlog(opt)
srlog('abc', 123, 4.56, { xyz: ['a', 'bc', true, { xy: 'z' }] })
Parameters:
Name Type Attributes Default Description
opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
fdSys String <optional>
path.resolve()

輸入工作路徑字串,若useLocRela=true時取代用,預設path.resolve()

fdLog String <optional>
'./syslog'

輸入儲存log檔案之資料夾位置字串,預設'./syslog'

dlm String <optional>
'ꓹ'

輸入分隔log參數字串,預設'ꓹ'

useTime Boolean <optional>
true

輸入是否給予紀錄時間布林值,預設true

useLoc Boolean <optional>
true

輸入是否紀錄觸發程式碼位置布林值,預設true

useLocRela Boolean <optional>
true

輸入當useLoc=true時,是否紀錄觸發程式碼位置改用相對工作路徑之位置布林值,預設true

Returns:

回傳log函數,比照console.log使用,寫入log檔失敗(如參數含循環參照、資料夾無法寫入)時以console.log記錄不外拋;useLoc=true而呼叫堆疊中無呼叫端之file:位置(如以setTimeout(srlog, 0, ...)直接排程呼叫)時,log函數同步拋出Error物件(can not parse stackLines),呼叫端須自行try或改設useLoc=false

Type
function

(static) fsSyncFolder(fdSrc, fdTar, optopt) → {Promise}

Description:
  • 後端nodejs針對新舊資料夾進行差異同步

    Unit Test: Github

Source:
Example
//need test in nodejs

let test = async () => {

    let ms = []

    let fdt = './_test_fsSyncFolder'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fdSrc = `${fdt}/src`
    let fdTar = `${fdt}/tar`

    let fp
    let stage
    let b
    let bSync

    if (true) {

        fp = `${fdTar}/t0.txt`
        fsWriteText(fp, 't0')

        fp = `${fdTar}/abc/t1.txt`
        fsWriteText(fp, 'abc-t1')

        fp = `${fdTar}/abc/t2.txt`
        fsWriteText(fp, 'abc-t2')

        fp = `${fdTar}/def/xyz/t3.txt`
        fsWriteText(fp, 'def-xyz-t3')

    }

    if (true) {

        //檔案內容變更
        fp = `${fdSrc}/t0.txt`
        fsWriteText(fp, 't0-mod')

        //未變更
        fp = `${fdSrc}/abc/t1.txt`
        fsWriteText(fp, 'abc-t1')

        //檔案新增
        fp = `${fdSrc}/abc/t1-add.txt`
        fsWriteText(fp, 'abc-t1-add')

        //檔案刪除
        // fp = `${fdSrc}/abc/t2.txt`
        // fsWriteText(fp, 'abc-t2')

        //資料夾刪除, 檔案刪除
        // fp = `${fdSrc}/def/xyz/t3.txt`
        // fsWriteText(fp, 'def-xyz-t3')

        //資料夾新增, 檔案新增
        fp = `${fdSrc}/def/xyz-add/t3.txt`
        fsWriteText(fp, 'def-xyz-add-t3')

    }

    stage = 'before'

    fp = `${fdTar}/t0.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b)
    ms.push({ stage, fp, fsIsFile: b })

    fp = `${fdTar}/abc`
    b = fsIsFolder(fp)
    console.log(stage, fp, 'fsIsFolder', b)
    ms.push({ stage, fp, fsIsFolder: b })

    fp = `${fdTar}/abc/t1.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b)
    ms.push({ stage, fp, fsIsFile: b })

    fp = `${fdTar}/abc/t2.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b)
    ms.push({ stage, fp, fsIsFile: b })

    fp = `${fdTar}/def`
    b = fsIsFolder(fp)
    console.log(stage, fp, 'fsIsFolder', b)
    ms.push({ stage, fp, fsIsFolder: b })

    fp = `${fdTar}/def/xyz`
    b = fsIsFolder(fp)
    console.log(stage, fp, 'fsIsFolder', b)
    ms.push({ stage, fp, fsIsFolder: b })

    fp = `${fdTar}/def/xyz/t3.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b)
    ms.push({ stage, fp, fsIsFile: b })

    stage = 'sync'

    bSync = await fsSyncFolder(fdSrc, fdTar)
    console.log(stage, 'bSync', bSync)
    ms.push({ stage, bSync }) //bSync=true, 有差異故需同步

    stage = 'after'

    fp = `${fdTar}/t0.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b)
    ms.push({ stage, fp, fsIsFile: b })

    fp = `${fdTar}/abc`
    b = fsIsFolder(fp)
    console.log(stage, fp, 'fsIsFolder', b)
    ms.push({ stage, fp, fsIsFolder: b })

    fp = `${fdTar}/abc/t1.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b)
    ms.push({ stage, fp, fsIsFile: b })

    fp = `${fdTar}/abc/t1-add.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b)
    ms.push({ stage, fp, fsIsFile: b })

    fp = `${fdTar}/abc/t2.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b) //b=false
    ms.push({ stage, fp, fsIsFile: b })

    fp = `${fdTar}/def`
    b = fsIsFolder(fp)
    console.log(stage, fp, 'fsIsFolder', b)
    ms.push({ stage, fp, fsIsFolder: b })

    fp = `${fdTar}/def/xyz`
    b = fsIsFolder(fp)
    console.log(stage, fp, 'fsIsFolder', b)
    ms.push({ stage, fp, fsIsFolder: b }) //b=false

    fp = `${fdTar}/def/xyz-add`
    b = fsIsFolder(fp)
    console.log(stage, fp, 'fsIsFolder', b)
    ms.push({ stage, fp, fsIsFolder: b })

    fp = `${fdTar}/def/xyz/t3.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b) //b=false
    ms.push({ stage, fp, fsIsFile: b })

    fp = `${fdTar}/def/xyz-add/t3.txt`
    b = fsIsFile(fp)
    console.log(stage, fp, 'fsIsFile', b)
    ms.push({ stage, fp, fsIsFile: b })

    stage = 'sync-again'

    bSync = await fsSyncFolder(fdSrc, fdTar)
    console.log(stage, 'bSync', bSync)
    ms.push({ stage, bSync }) //bSync=false, 已無差異故無須同步

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', JSON.stringify(ms, null, 2))
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// before ./_test_fsSyncFolder/tar/t0.txt fsIsFile true
// before ./_test_fsSyncFolder/tar/abc fsIsFolder true
// before ./_test_fsSyncFolder/tar/abc/t1.txt fsIsFile true
// before ./_test_fsSyncFolder/tar/abc/t2.txt fsIsFile true
// before ./_test_fsSyncFolder/tar/def fsIsFolder true
// before ./_test_fsSyncFolder/tar/def/xyz fsIsFolder true
// before ./_test_fsSyncFolder/tar/def/xyz/t3.txt fsIsFile true
// sync bSync true
// after ./_test_fsSyncFolder/tar/t0.txt fsIsFile true
// after ./_test_fsSyncFolder/tar/abc fsIsFolder true
// after ./_test_fsSyncFolder/tar/abc/t1.txt fsIsFile true
// after ./_test_fsSyncFolder/tar/abc/t1-add.txt fsIsFile true
// after ./_test_fsSyncFolder/tar/abc/t2.txt fsIsFile false
// after ./_test_fsSyncFolder/tar/def fsIsFolder true
// after ./_test_fsSyncFolder/tar/def/xyz fsIsFolder false
// after ./_test_fsSyncFolder/tar/def/xyz-add fsIsFolder true
// after ./_test_fsSyncFolder/tar/def/xyz/t3.txt fsIsFile false
// after ./_test_fsSyncFolder/tar/def/xyz-add/t3.txt fsIsFile true
// sync-again bSync false
// ms [
//   {
//     "stage": "before",
//     "fp": "./_test_fsSyncFolder/tar/t0.txt",
//     "fsIsFile": true
//   },
//   {
//     "stage": "before",
//     "fp": "./_test_fsSyncFolder/tar/abc",
//     "fsIsFolder": true
//   },
//   {
//     "stage": "before",
//     "fp": "./_test_fsSyncFolder/tar/abc/t1.txt",
//     "fsIsFile": true
//   },
//   {
//     "stage": "before",
//     "fp": "./_test_fsSyncFolder/tar/abc/t2.txt",
//     "fsIsFile": true
//   },
//   {
//     "stage": "before",
//     "fp": "./_test_fsSyncFolder/tar/def",
//     "fsIsFolder": true
//   },
//   {
//     "stage": "before",
//     "fp": "./_test_fsSyncFolder/tar/def/xyz",
//     "fsIsFolder": true
//   },
//   {
//     "stage": "before",
//     "fp": "./_test_fsSyncFolder/tar/def/xyz/t3.txt",
//     "fsIsFile": true
//   },
//   {
//     "stage": "sync",
//     "bSync": true
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/t0.txt",
//     "fsIsFile": true
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/abc",
//     "fsIsFolder": true
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/abc/t1.txt",
//     "fsIsFile": true
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/abc/t1-add.txt",
//     "fsIsFile": true
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/abc/t2.txt",
//     "fsIsFile": false
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/def",
//     "fsIsFolder": true
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/def/xyz",
//     "fsIsFolder": false
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/def/xyz-add",
//     "fsIsFolder": true
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/def/xyz/t3.txt",
//     "fsIsFile": false
//   },
//   {
//     "stage": "after",
//     "fp": "./_test_fsSyncFolder/tar/def/xyz-add/t3.txt",
//     "fsIsFile": true
//   },
//   {
//     "stage": "sync-again",
//     "bSync": false
//   }
// ]
Parameters:
Name Type Attributes Default Description
fdSrc String

輸入來源資料夾字串

fdTar String

輸入目標資料夾字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'md5'

輸入計算HASH方法字串,預設'md5'

Returns:

回傳Promise,resolve回傳是否有進行同步的布林值,reject回傳錯誤訊息字串(含複製失敗,與刪除目標內多出之檔案或資料夾失敗)

Type
Promise

(async, static) fsSyncFolderCore(fdSrc, fdTar, optopt) → {Promise}

Description:
  • 後端nodejs針對新舊資料夾進行差異同步

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsSyncFolder
Parameters:
Name Type Attributes Default Description
fdSrc String

輸入來源資料夾字串

fdTar String

輸入目標資料夾字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'md5'

輸入計算HASH方法字串,預設'md5'

Returns:

回傳Promise,resolve回傳是否有進行同步的布林值,reject回傳錯誤訊息字串(含複製失敗,與刪除目標內多出之檔案或資料夾失敗)

Type
Promise

(static) fsTask(fd, optopt) → {Object}

Description:
  • 後端nodejs偵測指定資料夾下之檔案變化

    Unit Test: Github

Source:
Example
//need test in nodejs

import fsDeleteFile from './src/fsDeleteFile.mjs'
import fsCreateFolder from './src/fsCreateFolder.mjs'
import fsDeleteFolder from './src/fsDeleteFolder.mjs'
import fsTask from './src/fsTask.mjs'

let test = async () => {
    return new Promise((resolve, reject) => {
        let ms = []

        let fpc = './_tkfs'
        fsDeleteFolder(fpc) //預先清除fsTask持久化數據資料夾

        let fpt = './_test_fsTask'
        fsDeleteFolder(fpt) //預先清除任務資料夾

        let fpr = './_test_fsTask_result'
        fsCreateFolder(fpr) //創建結果資料夾

        let ev = fsTask(fpt, { timeInterval: 500 })
        ev.on('change', (msg) => {
            console.log(msg.type, msg.fn)

            //content
            let c = ''
            try {
                c = fs.readFileSync(msg.fp, 'utf8')
            }
            catch (err) {}

            if (msg.fn === 'abc.txt') {
                //僅針對abc.txt任務

                if (msg.type === 'add' || msg.type === 'diff') {
                    //針對新增或變更任務

                    console.log(`task[${msg.fn}]`, `content[${c}]`, 'calculating')
                    ms.push({ type: msg.type, fp: msg.fn, content: c, mode: 'calculating' })

                    //模擬計算延遲
                    setTimeout(() => {

                        //模擬計算完儲存結果
                        fs.writeFileSync(`${fpr}/res1.json`, 'res1', 'utf8')
                        fs.writeFileSync(`${fpr}/res2.json`, 'res2', 'utf8')

                        //使用setResult紀錄完成分析後之關聯結果檔
                        ev.setResult(msg.fp, msg.hash, [
                            {
                                type: 'file',
                                path: `${fpr}/res1.json`,
                            },
                            {
                                type: 'file',
                                path: `${fpr}/res2.json`,
                            },
                        ])

                        console.log(`task[${msg.fn}]`, `content[${c}]`, 'save-result')
                        ms.push({ type: msg.type, fp: msg.fn, content: c, mode: 'save-result' })

                        msg.pm.resolve()
                    }, 2000)

                }
                else { //msg.type === 'del'
                    //針對任務刪除

                    console.log(`task[${msg.fn}]`, 'remove-task')
                    ms.push({ type: msg.type, fp: msg.fn, mode: 'remove-task' })

                    //刪除任務時, 自動刪除關聯結果檔
                    let rrs = ev.getAndEliminateResult(msg.fp, msg.hash)
                    // console.log('rrs', rrs)
                    for (let k = 0; k < rrs.length; k++) {
                        fsDeleteFile(rrs[k].path)
                    }

                    console.log(`task[${msg.fn}]`, 'remove-result')
                    ms.push({ type: msg.type, fp: msg.fn, mode: 'remove-result' })

                    msg.pm.resolve()
                }

            }
            else {
                //針對其他任務

                console.log(`task[${msg.fn}]`, `content[${c}]`, 'skip')
                ms.push({ type: msg.type, fp: msg.fn, content: c, mode: 'skip' })
                msg.pm.resolve()
            }

        })

        setTimeout(() => {
            fsCreateFolder(fpt)
        }, 1)

        setTimeout(() => {
            fs.writeFileSync(`${fpt}/abc.txt`, 'abc', 'utf8')
        }, 3000)

        setTimeout(() => {
            fs.writeFileSync(`${fpt}/abc.txt`, 'mnop', 'utf8')
            fs.writeFileSync(`${fpt}/def.txt`, 'def', 'utf8')
        }, 6000)

        setTimeout(() => {
            fsDeleteFile(`${fpt}/abc.txt`)
        }, 9000)

        setTimeout(() => {
            fsDeleteFolder(fpt) //最終階段清除任務資料夾
        }, 12000)

        setTimeout(() => {
            ev.clear() //結束後中止ev
            fsDeleteFolder(fpc) //結束後清除fsTask持久化數據資料夾
            fsDeleteFolder(fpr) //結束後清除結果資料夾
            console.log('ms', ms)
            resolve(ms)
        }, 15000)

    })
}
await test()
    .catch((err) => {
        console.log(err)
    })
// add abc.txt
// task[abc.txt] content[abc] calculating
// task[abc.txt] content[abc] save-result
// add def.txt
// task[def.txt] content[def] skip
// diff abc.txt
// task[abc.txt] content[mnop] calculating
// task[abc.txt] content[mnop] save-result
// del abc.txt
// task[abc.txt] remove-task
// task[abc.txt] remove-result
// del def.txt
// task[def.txt] content[] skip
// ms [
//   { type: 'add', fp: 'abc.txt', content: 'abc', mode: 'calculating' },
//   { type: 'add', fp: 'abc.txt', content: 'abc', mode: 'save-result' },
//   { type: 'add', fp: 'def.txt', content: 'def', mode: 'skip' },
//   { type: 'diff', fp: 'abc.txt', content: 'mnop', mode: 'calculating' },
//   { type: 'diff', fp: 'abc.txt', content: 'mnop', mode: 'save-result' },
//   { type: 'del', fp: 'abc.txt', mode: 'remove-task' },
//   { type: 'del', fp: 'abc.txt', mode: 'remove-result' },
//   { type: 'del', fp: 'def.txt', content: '', mode: 'skip' }
// ]
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案,設定null為無窮遍歷所有檔案,預設1

typeHash String <optional>
'md5'

輸入計算HASH方法字串,如'md5'、'sha1'、'sha256',預設'md5';選項名為typeHash,給opt.type無作用

fdStorage String <optional>
'./_tkfs'

輸入紀錄檔儲存資料夾路徑字串,存放紀錄各檔案雜湊之fpHash.json與任務關聯結果之fpResult.json,預設'./_tkfs'

timeInterval Number <optional>
60000

輸入定時器偵測時長正整數,單位為毫秒ms,預設60000,為1分鐘

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'tick'時為定時器到期一次(含busy為上一輪掃描是否仍在進行,為true時本次到期略過),'skip'時為因上一個change之pm尚未settle(lock)而本輪不掃描(含reason為'lock'),'scan'時為一輪掃描開始或結束(含phase為'start'或'end',結束時另含files為掃描到之檔案數、records為紀錄檔之筆數),'compare'時為新舊比對結果(含add、del、diff、same為各類件數),'emit'時為即將派發一個change事件(含kind為'add'、'del'或'diff',fp為檔案路徑字串),'settle'時為該change之pm已settle而即將據以處理(含kind、fp、ok為任務是否成功),'record'時為任務成功後紀錄檔fpHash.json已更新(含kind、fp),'fail'時為一輪掃描因列舉檔案或計算雜湊失敗而中止,'clear'時為已呼叫clear;clear後不再通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響偵測,預設null

Returns:

回傳事件物件,建立後即以timeInterval定時偵測,on可進行監聽change事件,clear為停止偵測函數,另提供setResult與getAndEliminateResult紀錄與取得任務關聯結果。每輪偵測只派發一個變更檔並等待其msg.pm settle後才進行下一個(嚴格順序),任務失敗(pm被reject)不更新紀錄故下輪會重試同一檔,持續失敗之檔會阻塞其後之檔(既有設計,呼叫端須自行決定重試上限或以resolve略過)。事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯故其不會使行程崩潰;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection。change監聽器同步拋錯時會先reject該次msg.pm(視為任務失敗、不更新紀錄檔並解鎖)再以error事件回報{ fun: 'listener', name, msg, args }

Type
Object

(static) fsTaskCp(fdSrc, fdTar, optopt) → {Object}

Description:
  • 後端nodejs偵測指定資料夾下之檔案變化,分來源與偵測端

    Unit Test: Github

Source:
Example
//need test in nodejs

import fsDeleteFile from './src/fsDeleteFile.mjs'
import fsCreateFolder from './src/fsCreateFolder.mjs'
import fsDeleteFolder from './src/fsDeleteFolder.mjs'
import fsTaskCp from './src/fsTaskCp.mjs'

let test = async () => {
    return new Promise((resolve, reject) => {
        let msSrc = []
        let msTar = []

        let fp = './_taskcp'
        fsDeleteFolder(fp) //預先清除fsTaskCp持久化數據資料夾

        let fpc = './_taskcpSrc'
        fsDeleteFolder(fpc) //預先清除fsTaskCp持久化數據資料夾

        let fpt = './_taskcpTar'
        fsDeleteFolder(fpt) //預先清除fsTaskCp持久化數據資料夾

        let otk = fsTaskCp(fpc, fpt)

        let otkSrc = otk.buildSrc()
        otkSrc.on('set', (msg) => {
            console.log(`src send task...`, msg)
            msSrc.push({ type: 'src-send', msg: JSON.stringify(msg) })
        })
        otkSrc.on('remove', (msg) => {
            console.log(`src send task...`, msg)
            msSrc.push({ type: 'src-send', msg: JSON.stringify(msg) })
        })

        let otkTar = otk.buildTar()
        otkTar.on('change', (msg) => {
            // console.log('otkTar change', msg)

            //接收任務
            console.log(`tar recv task...`, msg.kpCmp)
            msTar.push({ type: 'tar-recv', msg: JSON.stringify(msg.kpCmp) })

            setTimeout(() => {
                console.log(`tar deal task done`)
                msTar.push({ type: 'tar-done', msg: JSON.stringify(msg.kpCmp) })
                msg.pm.resolve()
            }, 1000)

        })

        setTimeout(() => {
            fsCreateFolder(fp)
        }, 1)

        setTimeout(() => {
            fs.writeFileSync(`${fp}/abc.txt`, 'abc', 'utf8')
            otkSrc.set('abc.txt', 'hash_a1')
        }, 3000)

        setTimeout(() => {
            fs.writeFileSync(`${fp}/abc.txt`, 'mnop', 'utf8')
            otkSrc.set('abc.txt', 'hash_a2')
            fs.writeFileSync(`${fp}/def.txt`, 'def', 'utf8')
            otkSrc.set('def.txt', 'hash_b1')
        }, 6000)

        setTimeout(() => {
            fsDeleteFile(`${fp}/abc.txt`)
            otkSrc.remove('abc.txt')
        }, 9000)

        setTimeout(() => {
            otkTar.clear()
        }, 12000)

        setTimeout(() => {
            fsDeleteFolder(fp)
            fsDeleteFolder(fpc)
            fsDeleteFolder(fpt)
            let ms = {
                msSrc,
                msTar,
            }
            console.log('ms', ms)
            resolve(ms)
        }, 15000)

    })
}
await test()
    .catch((err) => {
        console.log(err)
    })
// src send task... { type: 'set', fp: 'abc.txt', hash: 'hash_a1' }
// tar recv task... {
//   del: [],
//   add: [ { fp: 'abc.txt', hash: 'hash_a1' } ],
//   same: [],
//   diff: []
// }
// tar deal task done
// src send task... { type: 'set', fp: 'abc.txt', hash: 'hash_a2' }
// src send task... { type: 'set', fp: 'def.txt', hash: 'hash_b1' }
// tar recv task... {
//   del: [],
//   add: [ { fp: 'def.txt', hash: 'hash_b1' } ],
//   same: [],
//   diff: [ { fp: 'abc.txt', hash: 'hash_a2' } ]
// }
// src send task... { type: 'remove', fp: 'abc.txt' }
// tar deal task done
// tar recv task... {
//   del: [ { fp: 'abc.txt', hash: 'hash_a2' } ],
//   add: [],
//   same: [ { fp: 'def.txt', hash: 'hash_b1' } ],
//   diff: []
// }
// tar deal task done
// ms {
//   msSrc: [
//     {
//       type: 'src-send',
//       msg: '{"type":"set","fp":"abc.txt","hash":"hash_a1"}'
//     },
//     {
//       type: 'src-send',
//       msg: '{"type":"set","fp":"abc.txt","hash":"hash_a2"}'
//     },
//     {
//       type: 'src-send',
//       msg: '{"type":"set","fp":"def.txt","hash":"hash_b1"}'
//     },
//     { type: 'src-send', msg: '{"type":"remove","fp":"abc.txt"}' }
//   ],
//   msTar: [
//     {
//       type: 'tar-recv',
//       msg: '{"del":[],"add":[{"fp":"abc.txt","hash":"hash_a1"}],"same":[],"diff":[]}'
//     },
//     {
//       type: 'tar-done',
//       msg: '{"del":[],"add":[{"fp":"abc.txt","hash":"hash_a1"}],"same":[],"diff":[]}'
//     },
//     {
//       type: 'tar-recv',
//       msg: '{"del":[],"add":[{"fp":"def.txt","hash":"hash_b1"}],"same":[],"diff":[{"fp":"abc.txt","hash":"hash_a2"}]}'
//     },
//     {
//       type: 'tar-done',
//       msg: '{"del":[],"add":[{"fp":"def.txt","hash":"hash_b1"}],"same":[],"diff":[{"fp":"abc.txt","hash":"hash_a2"}]}'
//     },
//     {
//       type: 'tar-recv',
//       msg: '{"del":[{"fp":"abc.txt","hash":"hash_a2"}],"add":[],"same":[{"fp":"def.txt","hash":"hash_b1"}],"diff":[]}'
//     },
//     {
//       type: 'tar-done',
//       msg: '{"del":[{"fp":"abc.txt","hash":"hash_a2"}],"add":[],"same":[{"fp":"def.txt","hash":"hash_b1"}],"diff":[]}'
//     }
//   ]
// }
Parameters:
Name Type Attributes Default Description
fdSrc String

輸入來源端更新紀錄之資料夾路徑字串

fdTar String

輸入偵測端已紀錄之資料夾路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,供buildTar所建立之偵測端使用,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'recv'時為收到來源端紀錄檔之變更(含kind為檔案監看事件種類'add'、'change'或'unlink',隨後排入300ms之debounce),'run'時為debounce到期而開始偵測,'skip'時為來源端無紀錄(紀錄檔不存在、內容為空物件或非物件)而不偵測(含reason為'no src'),'emit'時為即將派發change事件(含add、del、diff、same為kpCmp各類件數),'settle'時為change之pm已settle而即將據以處理(含ok為任務是否成功),'record'時為任務成功後偵測端紀錄檔已更新,'clear'時為已呼叫偵測端之clear;clear後不再通知(已排入debounce者仍會偵測與派發change,但不再通知);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響偵測,預設null

Returns:

回傳物件,包含buildSrc與buildTar函數,buildSrc回傳監聽事件物件ev, 可使用ev.set進行紀錄變更,buildTar回傳監聽事件物件,可監聽change事件,並使用接收事件資訊msg內的pm做為回傳執行成功與否狀態。兩事件物件皆為事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯故其不會使行程崩潰;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection。change監聽器同步拋錯時會先reject該次msg.pm(視為執行失敗、不更新紀錄檔)再以error事件回報{ fun: 'listener', name, msg, args }

Type
Object

(static) fsTreeFolder(fd, levelLimitopt) → {Array}

Description:
  • 後端nodejs列舉指定資料夾下的全部檔案或資料夾

    Unit Test: Github

Source:
Example
//need test in nodejs

let test = () => {

    let ms = []

    let t = '_test_fsTreeFolder'
    let fdt = './_test_fsTreeFolder'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let ftpath = (v) => {
        // console.log(v, 'v.path', v.path)
        let ss = _.split(v.path, t)
        // console.log('ss', ss)
        let p = ss[1]
        p = p.replaceAll('\\', '/')
        p = `.${p}`
        // console.log('p', p)
        v.path = p
        return v
    }

    let ftpaths = (vs) => {
        return _.map(_.cloneDeep(vs), ftpath)
    }

    fsWriteText(`${fdt}/z1.txt`, 'z1')
    fsWriteText(`${fdt}/abc/z2.txt`, 'z2')
    fsWriteText(`${fdt}/def/ijk/z3.txt`, 'z3')
    fsCreateFolder(`${fdt}/mno/pqr`)

    let r1 = fsTreeFolder(fdt, 1)
    console.log('fsTreeFolder(levelLimit=1)', ftpaths(r1))
    ms.push({ 'fsTreeFolder(evelLimit=1)': ftpaths(r1) })

    let rall = fsTreeFolder(fdt, null)
    console.log('fsTreeFolder(levelLimit=null)', ftpaths(rall))
    ms.push({ 'fsTreeFolder(evelLimit=null)': ftpaths(rall) })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', JSON.stringify(ms))
    return ms
}
test()
// fsTreeFolder(levelLimit=1) [
//   { isFolder: true, level: 1, path: './abc', name: 'abc' },
//   { isFolder: true, level: 1, path: './def', name: 'def' },
//   { isFolder: true, level: 1, path: './mno', name: 'mno' },
//   { isFolder: false, level: 1, path: './z1.txt', name: 'z1.txt' }
// ]
// fsTreeFolder(levelLimit=null) [
//   { isFolder: true, level: 1, path: './abc', name: 'abc' },
//   { isFolder: false, level: 2, path: './abc/z2.txt', name: 'z2.txt' },
//   { isFolder: true, level: 1, path: './def', name: 'def' },
//   { isFolder: true, level: 2, path: './def/ijk', name: 'ijk' },
//   {
//     isFolder: false,
//     level: 3,
//     path: './def/ijk/z3.txt',
//     name: 'z3.txt'
//   },
//   { isFolder: true, level: 1, path: './mno', name: 'mno' },
//   { isFolder: true, level: 2, path: './mno/pqr', name: 'pqr' },
//   { isFolder: false, level: 1, path: './z1.txt', name: 'z1.txt' }
// ]
// ms [{"fsTreeFolder(evelLimit=1)":[{"isFolder":true,"level":1,"path":"./abc","name":"abc"},{"isFolder":true,"level":1,"path":"./def","name":"def"},{"isFolder":true,"level":1,"path":"./mno","name":"mno"},{"isFolder":false,"level":1,"path":"./z1.txt","name":"z1.txt"}]},{"fsTreeFolder(evelLimit=null)":[{"isFolder":true,"level":1,"path":"./abc","name":"abc"},{"isFolder":false,"level":2,"path":"./abc/z2.txt","name":"z2.txt"},{"isFolder":true,"level":1,"path":"./def","name":"def"},{"isFolder":true,"level":2,"path":"./def/ijk","name":"ijk"},{"isFolder":false,"level":3,"path":"./def/ijk/z3.txt","name":"z3.txt"},{"isFolder":true,"level":1,"path":"./mno","name":"mno"},{"isFolder":true,"level":2,"path":"./mno/pqr","name":"pqr"},{"isFolder":false,"level":1,"path":"./z1.txt","name":"z1.txt"}]}]
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案或資料夾,設定null為無窮遍歷所有檔案與資料夾,預設1

Returns:

回傳列舉檔案或資料夾陣列,若fd不是資料夾則同步拋出Error(訊息為'fd[...] is not a folder'),列舉時讀取資料夾失敗(如無讀取權限之EPERM)亦同步拋出其Error

Type
Array

(static) fsTreeFolderCore(fd, levelLimitopt) → {Array}

Description:
  • 後端nodejs列舉指定資料夾下的全部檔案或資料夾

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsTreeFolder
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案或資料夾,設定null為無窮遍歷所有檔案與資料夾,預設1

Returns:

回傳列舉檔案或資料夾陣列,若fd不是資料夾則同步拋出Error(訊息為'fd[...] is not a folder'),列舉時讀取資料夾失敗(如無讀取權限之EPERM)亦同步拋出其Error

Type
Array

(static) fsTreeFolderWithHash(fd, levelLimitopt, optopt) → {Promise}

Description:
  • 後端nodejs列舉指定資料夾下的全部檔案或資料夾,且計算各自HASH值

    Unit Test: Github

Source:
Example
//need test in nodejs

let test = async () => {

    let ms = []

    let fdt = './_test_fsTreeFolderWithHash'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fp

    fp = `${fdt}/t0.txt`
    fsWriteText(fp, 't0')

    fp = `${fdt}/abc/t1.txt`
    fsWriteText(fp, 'abc-t1')

    fp = `${fdt}/def/xyz/t2.txt`
    fsWriteText(fp, 'def-xyz-t2')

    let clns = (vfps) => {
        vfps = _.map(vfps, (v) => {
            delete v.path
            return v
        })
        return vfps
    }

    let vfps1 = await fsTreeFolderWithHash(fdt)
    vfps1 = clns(vfps1)
    console.log('fsTreeFolderWithHash(1,file,md5)', vfps1)
    ms.push({ 'fsTreeFolderWithHash(1,file,md5)': vfps1 })

    let vfps2 = await fsTreeFolderWithHash(fdt, null)
    vfps2 = clns(vfps2)
    console.log('fsTreeFolderWithHash(null,file,md5)', vfps2)
    ms.push({ 'fsTreeFolderWithHash(null,file,md5)': vfps2 })

    let vfps3 = await fsTreeFolderWithHash(fdt, 1, { type: 'sha256' })
    vfps3 = clns(vfps3)
    console.log('fsTreeFolderWithHash(1,file,sha256)', vfps3)
    ms.push({ 'fsTreeFolderWithHash(1,file,sha256)': vfps3 })

    let vfps4 = await fsTreeFolderWithHash(fdt, null, { forFile: true, forFolder: true })
    vfps4 = clns(vfps4)
    console.log('fsTreeFolderWithHash(null,file+folder,md5)', vfps4)
    ms.push({ 'fsTreeFolderWithHash(null,file+folder,md5)': vfps4 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', JSON.stringify(ms, null, 2))
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// fsTreeFolderWithHash(1,file,md5) [
//   { isFolder: true, level: 1, name: 'abc', hash: '' },
//   { isFolder: true, level: 1, name: 'def', hash: '' },
//   {
//     isFolder: false,
//     level: 1,
//     name: 't0.txt',
//     hash: '809d4580aaed41565abc38d58f77f840'
//   }
// ]
// fsTreeFolderWithHash(null,file,md5) [
//   { isFolder: true, level: 1, name: 'abc', hash: '' },
//   {
//     isFolder: false,
//     level: 2,
//     name: 't1.txt',
//     hash: '45d11ad27b97bc3bb664260189ec0dcc'
//   },
//   { isFolder: true, level: 1, name: 'def', hash: '' },
//   { isFolder: true, level: 2, name: 'xyz', hash: '' },
//   {
//     isFolder: false,
//     level: 3,
//     name: 't2.txt',
//     hash: 'ac8cf7366d7adf0743d6561133d404b3'
//   },
//   {
//     isFolder: false,
//     level: 1,
//     name: 't0.txt',
//     hash: '809d4580aaed41565abc38d58f77f840'
//   }
// ]
// fsTreeFolderWithHash(1,file,sha256) [
//   { isFolder: true, level: 1, name: 'abc', hash: '' },
//   { isFolder: true, level: 1, name: 'def', hash: '' },
//   {
//     isFolder: false,
//     level: 1,
//     name: 't0.txt',
//     hash: '512f26ada3c3d634ac3c6b12b7b33cb50bb0963c3f6d9924241619c84ec78ff2'
//   }
// ]
// fsTreeFolderWithHash(null,file+folder,md5) [
//   {
//     isFolder: true,
//     level: 1,
//     name: 'abc',
//     hash: '45d11ad27b97bc3bb664260189ec0dcc'
//   },
//   {
//     isFolder: false,
//     level: 2,
//     name: 't1.txt',
//     hash: '45d11ad27b97bc3bb664260189ec0dcc'
//   },
//   {
//     isFolder: true,
//     level: 1,
//     name: 'def',
//     hash: 'ac8cf7366d7adf0743d6561133d404b3'
//   },
//   {
//     isFolder: true,
//     level: 2,
//     name: 'xyz',
//     hash: 'ac8cf7366d7adf0743d6561133d404b3'
//   },
//   {
//     isFolder: false,
//     level: 3,
//     name: 't2.txt',
//     hash: 'ac8cf7366d7adf0743d6561133d404b3'
//   },
//   {
//     isFolder: false,
//     level: 1,
//     name: 't0.txt',
//     hash: '809d4580aaed41565abc38d58f77f840'
//   }
// ]
// ms [
//   {
//     "fsTreeFolderWithHash(1,file,md5)": [
//       {
//         "isFolder": true,
//         "level": 1,
//         "name": "abc",
//         "hash": ""
//       },
//       {
//         "isFolder": true,
//         "level": 1,
//         "name": "def",
//         "hash": ""
//       },
//       {
//         "isFolder": false,
//         "level": 1,
//         "name": "t0.txt",
//         "hash": "809d4580aaed41565abc38d58f77f840"
//       }
//     ]
//   },
//   {
//     "fsTreeFolderWithHash(null,file,md5)": [
//       {
//         "isFolder": true,
//         "level": 1,
//         "name": "abc",
//         "hash": ""
//       },
//       {
//         "isFolder": false,
//         "level": 2,
//         "name": "t1.txt",
//         "hash": "45d11ad27b97bc3bb664260189ec0dcc"
//       },
//       {
//         "isFolder": true,
//         "level": 1,
//         "name": "def",
//         "hash": ""
//       },
//       {
//         "isFolder": true,
//         "level": 2,
//         "name": "xyz",
//         "hash": ""
//       },
//       {
//         "isFolder": false,
//         "level": 3,
//         "name": "t2.txt",
//         "hash": "ac8cf7366d7adf0743d6561133d404b3"
//       },
//       {
//         "isFolder": false,
//         "level": 1,
//         "name": "t0.txt",
//         "hash": "809d4580aaed41565abc38d58f77f840"
//       }
//     ]
//   },
//   {
//     "fsTreeFolderWithHash(1,file,sha256)": [
//       {
//         "isFolder": true,
//         "level": 1,
//         "name": "abc",
//         "hash": ""
//       },
//       {
//         "isFolder": true,
//         "level": 1,
//         "name": "def",
//         "hash": ""
//       },
//       {
//         "isFolder": false,
//         "level": 1,
//         "name": "t0.txt",
//         "hash": "512f26ada3c3d634ac3c6b12b7b33cb50bb0963c3f6d9924241619c84ec78ff2"
//       }
//     ]
//   },
//   {
//     "fsTreeFolderWithHash(null,file+folder,md5)": [
//       {
//         "isFolder": true,
//         "level": 1,
//         "name": "abc",
//         "hash": "45d11ad27b97bc3bb664260189ec0dcc"
//       },
//       {
//         "isFolder": false,
//         "level": 2,
//         "name": "t1.txt",
//         "hash": "45d11ad27b97bc3bb664260189ec0dcc"
//       },
//       {
//         "isFolder": true,
//         "level": 1,
//         "name": "def",
//         "hash": "ac8cf7366d7adf0743d6561133d404b3"
//       },
//       {
//         "isFolder": true,
//         "level": 2,
//         "name": "xyz",
//         "hash": "ac8cf7366d7adf0743d6561133d404b3"
//       },
//       {
//         "isFolder": false,
//         "level": 3,
//         "name": "t2.txt",
//         "hash": "ac8cf7366d7adf0743d6561133d404b3"
//       },
//       {
//         "isFolder": false,
//         "level": 1,
//         "name": "t0.txt",
//         "hash": "809d4580aaed41565abc38d58f77f840"
//       }
//     ]
//   }
// ]
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案或資料夾,設定null為無窮遍歷所有檔案與資料夾,預設1

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'md5'

輸入計算HASH方法字串,預設'md5'

forFile Boolean <optional>
true

輸入是否針對檔案計算HASH值,預設true

forFolder Boolean <optional>
false

輸入是否針對檔案計算HASH值,預設false

Returns:

回傳Promise,resolve回傳檔案或資料夾陣列且含HASH值,reject回傳錯誤訊息字串

Type
Promise

(async, static) fsTreeFolderWithHashCore(fd, levelLimitopt, optopt) → {Promise}

Description:
  • 後端nodejs列舉指定資料夾下的全部檔案或資料夾,且計算各自HASH值

    Unit Test: Github

Source:
Example
//need test in nodejs

//see fsTreeFolderWithHash
Parameters:
Name Type Attributes Default Description
fd String

輸入欲列舉的資料夾字串

levelLimit Integer <optional>
1

輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案或資料夾,設定null為無窮遍歷所有檔案與資料夾,預設1

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'md5'

輸入計算HASH方法字串,預設'md5'

forFile Boolean <optional>
true

輸入是否針對檔案計算HASH值,預設true

forFolder Boolean <optional>
false

輸入是否針對檔案計算HASH值,預設false

Returns:

回傳Promise,resolve回傳檔案或資料夾陣列且含HASH值,reject回傳錯誤訊息字串

Type
Promise

(static) fsWatchFile(fp, optopt) → {Object}

Description:
  • 後端nodejs基於chokidar提供偵測檔案內容變更或出現或消失事件

    因使用chokidar,變更至少要3秒才能監測,不適用於頻繁觸發事件之工作

    Unit Test: Github

Source:
Example
need test in nodejs.

import getFileName from './src/getFileName.mjs'
import fsDeleteFile from './src/fsDeleteFile.mjs'
import fsRenameFile from './src/fsRenameFile.mjs'
import fsWatchFile from './src/fsWatchFile.mjs'

let test = async () => {
    return new Promise((resolve, reject) => {
        let ms = []

        let fp = './_test_fsWatchFile.txt'

        fsDeleteFile(fp)

        let ev = fsWatchFile(fp)
        ev.on('change', (msg) => {
            console.log(msg.type, getFileName(msg.fp))
            ms.push({ type: msg.type, fp: getFileName(msg.fp) })
        })

        setTimeout(() => {
            fs.writeFileSync(fp, 'abc', 'utf8')
        }, 1)

        setTimeout(() => {
            fsRenameFile(fp, fp + '.tmp')
        }, 3000)

        setTimeout(() => {
            fsRenameFile(fp + '.tmp', fp)
        }, 6000)

        setTimeout(() => {
            fs.writeFileSync(fp, 'def', 'utf8')
        }, 9000)

        setTimeout(() => {
            fsDeleteFile(fp)
        }, 12000)

        setTimeout(() => {
            ev.clear()
            console.log('ms', ms)
            resolve(ms)
        }, 15000)

    })
}
await test()
    .catch((err) => {
        console.log(err)
    })
// add _test_fsWatchFile.txt
// unlink _test_fsWatchFile.txt
// add _test_fsWatchFile.txt
// change _test_fsWatchFile.txt
// unlink _test_fsWatchFile.txt
// ms [
//   { type: 'add', fp: '_test_fsWatchFile.txt' },
//   { type: 'unlink', fp: '_test_fsWatchFile.txt' },
//   { type: 'add', fp: '_test_fsWatchFile.txt' },
//   { type: 'change', fp: '_test_fsWatchFile.txt' },
//   { type: 'unlink', fp: '_test_fsWatchFile.txt' }
// ]
Parameters:
Name Type Attributes Default Description
fp String

輸入偵測檔案路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
polling Boolean <optional>
false

輸入是否使用輪循布林值,代表chokidar的usePolling,預設為false

timeInterval Integer <optional>
100

輸入當polling為true時偵測檔案變更間隔時間整數,代表chokidar開啟polling時的interval,單位為毫秒ms,預設為100

timeBinaryInterval Integer <optional>
300

輸入當polling為true時偵測二進位檔案變更間隔時間整數,代表chokidar開啟polling時的binaryInterval,單位為毫秒ms,預設為300

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'tick'時為每次週期輪詢(每timeInterval一次;含watching為是否已有watcher,exists為監看路徑目前是否為檔案,已有watcher時不查故為null),'watch'時為chokidar之watcher剛建立,'ready'時為chokidar初始掃描完成(僅傳了onTrace才監聽chokidar之ready,不影響原行為),'emit'時為即將對change監聽器派發一個事件(含kind為事件種類即change事件之type如add、change、unlink,fp為path.resolve後之路徑字串),'error'時為chokidar之watcher自身出錯(含fun為'watcher',錯誤物件不傳出,請由error事件取得),'unwatch'時為unWatch被呼叫且watcher尚存在(開始關閉;關閉完成前重複呼叫會重複通知),'closed'時為watcher.close()已完成且watcher已設為null,'clear'時為clear被呼叫(僅首次);clear後僅會再通知unwatch與closed(資源釋放之回報),其餘事件不再通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響監看,預設null

Returns:

回傳事件物件,包含on、clear函數,on可進行監聽change、error事件,clear為停止全部監聽,不須輸入。chokidar之watcher自身出錯(如無權限EPERM、EACCES)以error事件回報{ fun: 'watcher', msg }。事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯故其不會使行程崩潰;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection,監聽器出錯會改以error事件回報{ fun: 'listener', name, msg, args },監聽error時請先以fun欄位分流

Type
Object

(static) fsWatchFolder(fd, optopt) → {Object}

Description:
  • 後端nodejs基於chokidar提供偵測資料夾內檔案變更或出現或消失事件

    因使用chokidar,變更至少要3秒才能監測,不適用於頻繁觸發事件之工作

    Unit Test: Github

Source:
Example
need test in nodejs.

import getFileName from './src/getFileName.mjs'
import fsDeleteFile from './src/fsDeleteFile.mjs'
import fsRenameFile from './src/fsRenameFile.mjs'
import fsCreateFolder from './src/fsCreateFolder.mjs'
import fsDeleteFolder from './src/fsDeleteFolder.mjs'
import fsRenameFolder from './src/fsRenameFolder.mjs'
import fsWatchFolder from './src/fsWatchFolder.mjs'

let test = async () => {
    return new Promise((resolve, reject) => {
        let ms = []

        let fp = './_test_fsWatchFolder'

        fsDeleteFolder(fp)

        let ev = fsWatchFolder(fp)
        ev.on('change', (msg) => {
            console.log(msg.type, getFileName(msg.fp))
            ms.push({ type: msg.type, fp: getFileName(msg.fp) })
        })

        setTimeout(() => {
            fsCreateFolder(fp)
        }, 1)

        setTimeout(() => {
            fs.writeFileSync(`${fp}/abc.txt`, 'abc', 'utf8')
        }, 3000)

        setTimeout(() => {
            fsRenameFile(`${fp}/abc.txt`, `${fp}/abc.txt` + '.tmp')
        }, 6000)

        setTimeout(() => {
            fsRenameFile(`${fp}/abc.txt` + '.tmp', `${fp}/abc.txt`)
        }, 9000)

        setTimeout(() => {
            fs.writeFileSync(`${fp}/abc.txt`, 'def', 'utf8')
        }, 12000)

        setTimeout(() => {
            fsCreateFolder(`${fp}/test-fd`)
        }, 15000)

        setTimeout(() => {
            fsRenameFolder(`${fp}/test-fd`, `${fp}/test-fd` + '-tmp')
        }, 18000)

        setTimeout(() => {
            fsRenameFolder(`${fp}/test-fd` + '-tmp', `${fp}/test-fd`)
        }, 21000)

        setTimeout(() => {
            fsDeleteFile(`${fp}/abc.txt`)
        }, 24000)

        setTimeout(() => {
            fsDeleteFolder(`${fp}/test-fd`)
        }, 27000)

        setTimeout(() => {
            fsDeleteFolder(fp)
        }, 30000)

        setTimeout(() => {
            ev.clear()
            console.log('ms', ms)
            resolve(ms)
        }, 33000)

    })
}
await test()
    .catch((err) => {
        console.log(err)
    })
// addDir _test_fsWatchFolder
// add abc.txt
// unlink abc.txt
// add abc.txt.tmp
// unlink abc.txt.tmp
// add abc.txt
// change abc.txt
// addDir test-fd
// unlinkDir test-fd
// addDir test-fd-tmp
// unlinkDir test-fd-tmp
// addDir test-fd
// unlink abc.txt
// unlinkDir test-fd
// unlinkDir _test_fsWatchFolder
// ms [
//   { type: 'addDir', fp: '_test_fsWatchFolder' },
//   { type: 'add', fp: 'abc.txt' },
//   { type: 'unlink', fp: 'abc.txt' },
//   { type: 'add', fp: 'abc.txt.tmp' },
//   { type: 'unlink', fp: 'abc.txt.tmp' },
//   { type: 'add', fp: 'abc.txt' },
//   { type: 'change', fp: 'abc.txt' },
//   { type: 'addDir', fp: 'test-fd' },
//   { type: 'unlinkDir', fp: 'test-fd' },
//   { type: 'addDir', fp: 'test-fd-tmp' },
//   { type: 'unlinkDir', fp: 'test-fd-tmp' },
//   { type: 'addDir', fp: 'test-fd' },
//   { type: 'unlink', fp: 'abc.txt' },
//   { type: 'unlinkDir', fp: 'test-fd' },
//   { type: 'unlinkDir', fp: '_test_fsWatchFolder' }
// ]
Parameters:
Name Type Attributes Default Description
fd String

輸入偵測資料夾路徑字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
polling Boolean <optional>
false

輸入是否使用輪循布林值,代表chokidar的usePolling,預設為false

timeInterval Integer <optional>
100

輸入當polling為true時偵測檔案變更間隔時間整數,代表chokidar開啟polling時的interval,單位為毫秒ms,預設為100

timeBinaryInterval Integer <optional>
300

輸入當polling為true時偵測二進位檔案變更間隔時間整數,代表chokidar開啟polling時的binaryInterval,單位為毫秒ms,預設為300

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'tick'時為每次週期輪詢(每timeInterval一次;含watching為是否已有watcher、exists為監看路徑目前是否為資料夾),'watch'時為chokidar之watcher剛建立,'ready'時為chokidar初始掃描完成(僅傳了onTrace才監聽chokidar之ready,不影響原行為),'emit'時為即將對change監聽器派發一個事件(含kind為事件種類即change事件之type如add、change、unlink、addDir、unlinkDir,fp為path.resolve後之路徑字串,src為事件來源,'watcher'代表chokidar偵測到、'timer'代表週期輪詢發現監看資料夾已消失而合成之unlinkDir),'error'時為chokidar之watcher自身出錯(含fun為'watcher',錯誤物件不傳出,請由error事件取得),'unwatch'時為unWatch被呼叫且watcher尚存在(開始關閉;關閉完成前重複呼叫會重複通知),'closed'時為watcher.close()已完成且watcher已設為null(可再次建立watcher),'clear'時為clear被呼叫(僅首次);clear後僅會再通知unwatch與closed(資源釋放之回報),其餘事件不再通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響監看,預設null

Returns:

回傳事件物件,包含on、clear函數,on可進行監聽change、error事件,clear為停止全部監聽,不須輸入。chokidar之watcher自身出錯(如資料夾內含無權限之子資料夾EPERM、EACCES)以error事件回報{ fun: 'watcher', msg }。事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯故其不會使行程崩潰;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection,監聽器出錯會改以error事件回報{ fun: 'listener', name, msg, args },監聽error時請先以fun欄位分流

Type
Object

(static) fsWriteJson(fp, inp, optopt) → {Object}

Description:
  • 後端nodejs將物件或陣列轉成JSON格式文字再儲存至檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsWriteJson'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let inp = { str: '測試中文', val: 123.45, int: 5 }

    let fn1 = 't1.json'
    let fp1 = `${fdt}/${fn1}`

    let r1 = fsWriteJson(fp1, inp)
    console.log('fsWriteJson', r1)
    ms.push({ 'fsWriteJson': r1 })

    let c1 = fs.readFileSync(fp1, 'utf8')
    console.log('readFileSync', c1)
    ms.push({ 'readFileSync': c1 })

    let fn2 = 't2.json'
    let fp2 = `${fdt}/${fn2}`

    let r2 = fsWriteJson(fp2, inp, { useFormat: true })
    console.log('fsWriteJson', r2)
    ms.push({ 'fsWriteJson': r2 })

    let c2 = fs.readFileSync(fp2, 'utf8')
    console.log('readFileSync', c2)
    ms.push({ 'readFileSync': c2 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsWriteJson { success: './_test_fsWriteJson/t1.json' }
// readFileSync {"str":"測試中文","val":123.45,"int":5}
// fsWriteJson { success: './_test_fsWriteJson/t2.json' }
// readFileSync {
//   "str": "測試中文",
//   "val": 123.45,
//   "int": 5
// }
// ms [
//   { fsWriteJson: { success: './_test_fsWriteJson/t1.json' } },
//   { readFileSync: '{"str":"測試中文","val":123.45,"int":5}' },
//   { fsWriteJson: { success: './_test_fsWriteJson/t2.json' } },
//   {
//     readFileSync: '{\n  "str": "測試中文",\n  "val": 123.45,\n  "int": 5\n}'
//   }
// ]
Parameters:
Name Type Attributes Default Description
fp String

輸入檔案名稱

inp Object | Array

輸入物件或陣列數據

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useFormat Boolean <optional>
false

輸入是否格式化(縮排2空白)布林值,預設false

Returns:

回傳結果物件,提供鍵success代表寫入之檔案路徑字串,提供鍵error代表錯誤訊息(inp非物件或陣列時為說明字串,序列化或寫檔失敗時為Error物件,toJSON拋出非Error之值時為其字串)

Type
Object

(static) fsWriteText(fp, c) → {Object}

Description:
  • 後端nodejs寫入utf-8文字至檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

let test = () => {

    let ms = []

    let fdt = './_test_fsWriteText'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/abc/${fn}`

    let b1 = fsIsFile(fp)
    console.log('fsWriteText(before)', b1)
    ms.push({ 'fsWriteText(before)': b1 })

    let b2 = fsWriteText(fp, 'abc')
    console.log('fsWriteText', b2)
    ms.push({ 'fsWriteText': b2 })

    let b3 = fsIsFile(fp)
    console.log('fsWriteText(after)', b3)
    ms.push({ 'fsWriteText(after)': b3 })

    let c = fs.readFileSync(fp, 'utf8')
    console.log('readFileSync', c)
    ms.push({ 'readFileSync': c })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
test()
// fsWriteText(before) false
// fsWriteText { success: './_test_fsWriteText/abc/t1.txt' }
// fsWriteText(after) true
// readFileSync abc
// ms [
//   { 'fsWriteText(before)': false },
//   { fsWriteText: { success: './_test_fsWriteText/abc/t1.txt' } },
//   { 'fsWriteText(after)': true },
//   { readFileSync: 'abc' }
// ]
Parameters:
Name Type Description
fp String

輸入檔案名稱

c String

輸入utf-8文字數據

Returns:

回傳結果物件,提供鍵success代表寫入之檔案路徑字串,提供鍵error代表錯誤訊息(寫檔之Error物件)

Type
Object

(static) fsWriteTextCore(fp, c) → {Object}

Description:
  • 後端nodejs寫入utf-8文字至檔案

    Unit Test: Github

Source:
Example
need test in nodejs.

//see fsWriteText
Parameters:
Name Type Description
fp String

輸入檔案名稱

c String

輸入檔案內容utf-8字串

Returns:

回傳結果物件,提供鍵success代表寫入之檔案路徑字串,提供鍵error代表錯誤訊息(寫檔或檢查上層資料夾之Error物件,注入之fs拋出非Error之值時為其字串)

Type
Object

(static) genGradientColor(color) → {Object}

Description:
  • 產生highchart直條圖用漸變顏色物件

    Unit Test: Github

Source:
Example
console.log(genGradientColor('#000'))
// => {
//     linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
//     stops: [ [ 0, 'rgba(0, 0, 0, 0.4)' ], [ 1, 'rgba(0, 0, 0, 0.9)' ] ]
//     }

console.log(genGradientColor('hsl (320, 50%, 40%)'))
// => {
//     linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
//     stops: [
//         [ 0, 'rgba(153, 51, 119, 0.4)' ],
//         [ 1, 'rgba(153, 51, 119, 0.9)' ]
//     ]
// }

console.log(genGradientColor('hsva (320, 100%, 50%, 0.1)'))
// => {
//     linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
//     stops: [ [ 0, 'rgba(128, 0, 85, 0.4)' ], [ 1, 'rgba(128, 0, 85, 0.9)' ] ]
// }

console.log(genGradientColor('#6a3'))
// => {
//     linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
//     stops: [
//         [ 0, 'rgba(102, 170, 51, 0.4)' ],
//         [ 1, 'rgba(102, 170, 51, 0.9)' ]
//     ]
// }

console.log(genGradientColor('#6b8e23'))
// => {
//     linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
//     stops: [
//         [ 0, 'rgba(107, 142, 35, 0.4)' ],
//         [ 1, 'rgba(107, 142, 35, 0.9)' ]
//     ]
// }

console.log(genGradientColor('skyblue'))
// => {
//     linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
//     stops: [
//         [ 0, 'rgba(135, 206, 235, 0.4)' ],
//         [ 1, 'rgba(135, 206, 235, 0.9)' ]
//     ]
// }
Parameters:
Name Type Description
color *

輸入color資料

Returns:

回傳漸變顏色物件

Type
Object

(static) genID(lenopt) → {String}

Description:
  • 產生隨機id

    Unit Test: Github

Source:
Example
console.log(genID())
// => Is1NyImU3A9fyqFyYBWuJu4ivXXcGZAb (is random)
Parameters:
Name Type Attributes Default Description
len Integer <optional>
32

輸入uuid長度,為正整數,預設32

Returns:

回傳uuid字串

Type
String

(static) genIDSeq() → {String}

Description:
  • 基於uuidv7產生循序隨機id

    Unit Test: Github

Source:
Example
console.log(genIDSeq())
// => 01980453-935d-700e-a331-633e52a16055 (is random)
Returns:

回傳uuid字串

Type
String

(static) genPm() → {Object}

Description:
  • 產生Promise物件,具備鏈式resolve與reject 主要受jQuery Deferred概念啟發

    Unit Test: Github

Source:
Example
async function topAsync() {

    function test1() {
        return new Promise((resolve, reject) => {
            let ms = []

            let fn = function(name) {
                let pm = genPm()
                setTimeout(function() {
                    ms.push('resolve: ' + name)
                    pm.resolve('resolve: ' + name)
                }, 1)
                return pm
            }

            fn('abc')
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(() => {
                    resolve(ms)
                })

        })
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // t1 then resolve: abc
    // ["resolve: abc","t1 then: resolve: abc"]

    function test2() {
        return new Promise((resolve, reject) => {
            let ms = []

            let fn = function(name) {
                let pm = genPm()
                setTimeout(function() {
                    ms.push('reject: ' + name)
                    pm.reject('reject: ' + name)
                }, 1)
                return pm
            }

            fn('abc')
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(() => {
                    resolve(ms)
                })

        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // t1 catch reject: abc
    // ["reject: abc","t1 catch: reject: abc"]

}
topAsync().catch(() => {})
Returns:

回傳Promise物件

Type
Object

(static) getBufferSize(buf) → {Integer}

Description:
  • 計算Uint8Array(Nodejs,Browser)或Buffer(Nodejs)的大小

    Unit Test: Github

Source:
Example
let u8a = new Uint8Array([1, 2, 3, 123])
console.log(getBufferSize(u8a))
// => 4

let buf = Buffer.alloc(8)
console.log(getBufferSize(buf))
// => 8

console.log(getBufferSize(new Uint8Array(0)))
// => 0
Parameters:
Name Type Description
buf Unit8Array | Buffer

傳入Uint8Array(Nodejs,Browser)或Buffer(Nodejs)

Returns:

回傳Uint8Array(Nodejs,Browser)或Buffer(Nodejs)的大小

Type
Integer

(static) getClearPathName(str) → {string}

Description:
  • 清理檔案或資料夾位置

    Unit Test: Github

Source:
Example
let c

c = 'C:\\temp\\myfile.html'
console.log(getClearPathName(c))
// => { path: 'C:\\temp\\myfile.html', isRoot: false }

c = 'C:\\temp\\myfile.txt.html'
console.log(getClearPathName(c))
// => { path: 'C:\\temp\\myfile.txt.html', isRoot: false }

c = 'C:\\temp\\\\temp\\\\myfile.txt.html'
console.log(getClearPathName(c))
// => { path: 'C:\\temp\\temp\\myfile.txt.html', isRoot: false }

c = 'C:\\temp\\'
console.log(getClearPathName(c))
// => { path: 'C:\\temp', isRoot: false }

c = 'C:\\temp'
console.log(getClearPathName(c))
// => { path: 'C:\\temp', isRoot: false }

c = 'C:\\'
console.log(getClearPathName(c))
// => { path: 'C:\\', isRoot: true }

c = 'C:'
console.log(getClearPathName(c))
// => { path: 'C:\\', isRoot: true }

c = '/tmp/myfile.html'
console.log(getClearPathName(c))
// => { path: '/tmp/myfile.html', isRoot: false }

c = '/tmp/myfile.txt.html'
console.log(getClearPathName(c))
// => { path: '/tmp/myfile.txt.html', isRoot: false }

c = '//tmp////tmp//myfile.txt.html'
console.log(getClearPathName(c))
// => { path: '/tmp/tmp/myfile.txt.html', isRoot: false }

c = '/tmp/'
console.log(getClearPathName(c))
// => { path: '/tmp', isRoot: false }

c = '/tmp'
console.log(getClearPathName(c))
// => { path: '/tmp', isRoot: false }

c = '/'
console.log(getClearPathName(c))
// => { path: '/', isRoot: true }

c = '/foo/bar/baz/asdf/quux.html'
console.log(getClearPathName(c))
// => { path: '/foo/bar/baz/asdf/quux.html', isRoot: false }

c = '/foo/bar/baz/asdf/quux.txt.html'
console.log(getClearPathName(c))
// => { path: '/foo/bar/baz/asdf/quux.txt.html', isRoot: false }

c = ''
console.log(getClearPathName(c))
// => { path: '', isRoot: false }
Parameters:
Name Type Description
str string

輸入檔案或資料夾位置字串

Returns:

清理後檔案或資料夾位置

Type
string

(static) getCsvStrFromData(data, bomopt)

Description:
  • 由陣列數據轉成為Excel(*.csv)的字串數據

    Unit Test: Github

Source:
Example
let data = [
    ['a', '123', 456],
    [null, 'abc123', '', 111.222333],
    [5, '\r\n', true, 'false'],
]
let c = getCsvStrFromData(data)
console.log(c)
// => "a","123",456
//    ,"abc123","",111.222333
//    5,"",true,"false"
fs.writeFileSync('temp.csv', c, 'utf8')
Parameters:
Name Type Attributes Default Description
data Array

輸入內容陣列

bom Boolean <optional>
true

輸入是否添加開頭BOM符號,預設true

(async, static) getDataFromExcelFileU8Arr(u8a, optopt) → {Promise}

Description:
  • 讀取Excel檔,前後端都可用,前端由input file的檔案取得Uint8Array,後端由fs.readFileSync讀取Buffer 若數據格式fmt為csv格式,數據分欄符號為逗號,分行符號為[\r\n],內容開頭無BOM,方便使用者解析

    本函數為async function fmt='ltdt'時以首列為表頭轉物件陣列:空cell為略鍵、空字串cell保留鍵且值為''、全空列跳過、空表頭之欄位跳過、重複表頭後者覆蓋前者 fmt='array'與'csv'時保留全部列(含全空列),空cell一律轉'' valueToString為true時一律提取原值轉字串:數值依原值轉字串(非顯示格式)、布林為'true'或'false'、錯誤格為其文字(如'#N/A') 日期儲存格(數值且為日期或時間格式)之原值為Date,轉字串一律為'YYYY-MM-DDTHH:mm:ss.SSS'(不加時區、至毫秒),即Excel儲存格之年月日時分秒,與執行環境之時區無關; 純時間之儲存格日期部分為1899-12-31(1904日期系統為1904-01-01),經過時間格式([h]:mm等)亦依原值轉為自該日起算之日期時間; valueToString為false時日期回傳Date,其UTC分量(getUTCFullYear、getUTCHours等)即Excel儲存格之年月日時分秒;fmt為'csv'時日期一律為上述字串 表頭一律轉字串,轉換後為''者視為空表頭

    Unit Test: Github

Source:
Example
// test in browser
domShowInputAndGetFilesU8Arrs()
    .then(async function(d) {
        let file = d[0] //get first file
        let u8a = file.u8a
        let dltdt = await getDataFromExcelFileU8Arr(u8a, { fmt: 'ltdt' })
        console.log(dltdt[0].sheetname, dltdt[0].data)
        // => ...
    })

// test in nodejs
let u8a = fs.readFileSync('temp.xlsx')
let dltdt = await getDataFromExcelFileU8Arr(u8a, { fmt: 'ltdt' })
console.log(dltdt[0].sheetname, dltdt[0].data)
// => ...
Parameters:
Name Type Attributes Default Description
u8a Uint8Array

輸入file資料,格式需為Uint8Array

opt Object <optional>
{}

輸入設定物件,預設為{}

Properties
Name Type Attributes Default Description
fmt String <optional>
'ltdt'

輸入數據格式,可有'ltdt','csv','array',預設為'ltdt'

valueToString Boolean <optional>
true

輸入數據是否強制轉字串布林值,預設為true

Returns:

回傳Promise,resolve回傳數據陣列,輸入無效或解析失敗時resolve回傳{ error }物件

Type
Promise

(async, static) getDataFromExcelFileU8ArrDyn(u8a, optopt, pathItemsopt) → {Promise}

Description:
  • 前端讀取Excel檔,前端由input file的檔案取得Uint8Array 若數據格式fmt為csv格式,數據分欄符號為逗號,分行符號為[\r\n],內容開頭無BOM,方便使用者解析

    本函數為相容舊版之保留函數:舊版採動態加載CDN之SheetJS(xlsx),現Excel引擎已改為hucre並直接打包進wsemi,無需再動態加載 內部直接委派getDataFromExcelFileU8Arr,pathItems參數保留簽名相容但已不使用 與getDataFromExcelFileU8Arr之差異僅在錯誤處理:本函數以reject回傳錯誤訊息,非resolve回傳{ error }物件

    Unit Test: Github

Source:
Example
need test in browser
Parameters:
Name Type Attributes Default Description
u8a Uint8Array

輸入file資料,格式需為Uint8Array

opt Object <optional>
{}

輸入設定物件,預設為{}

Properties
Name Type Attributes Default Description
fmt String <optional>
'ltdt'

輸入數據格式,可有'ltdt','csv','array',預設為'ltdt'

valueToString Boolean <optional>
true

輸入數據是否強制轉字串布林值,預設為true

pathItems String | Object | Array <optional>

輸入資源字串、字串陣列、物件、物件陣列,已不使用,僅保留簽名相容

Returns:

回傳Promise,resolve回傳數據陣列,reject回傳錯誤訊息

Type
Promise

(static) getEnv() → {Object}

Description:
  • 取得運行環境

    Unit Test: Github

Source:
Example
console.log(getEnv())
// => { isBrowser: false, isWebWorker: false, isNode: true }
Returns:

回傳運行環境物件

Type
Object

(static) getErrorMessage(err, optopt) → {String}

Description:
  • 提取Error內Message訊息 err接受任意輸入,於任何err下皆不拋出錯誤且回傳值必為字串 此不拋錯之保證不涵蓋opt,因opt係呼叫端自備之設定物件,其讀取比照本套件慣例不另包try

    Unit Test: Github

Source:
Example
try {
    throw new Error('something wrong')
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => something wrong

try {
    throw new Error()
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => ''

try {
    throw new TypeError('wrong type')
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => wrong type

try {
    throw new RangeError('range bad')
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => range bad

try {
    throw new ReferenceError('ref bad')
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => ref bad

try {
    throw new SyntaxError('syntax bad')
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => syntax bad

try {
    throw new URIError('uri bad')
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => uri bad

try {
    throw new AggregateError([new Error('e1'), 'e2'], 'outer')
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => outer

try {
    throw new AggregateError([new Error('e1'), new Error('e2')], 'outer')
}
catch (err) {
    console.log(getErrorMessage(err, { useAggregate: true }))
}
// => outer [e1; e2]

try {
    throw new Error('top', { cause: new Error('root cause') })
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => top

try {
    throw new Error('top', { cause: new Error('root cause') })
}
catch (err) {
    console.log(getErrorMessage(err, { useCause: true }))
}
// => top <- root cause

try {
    throw new Error('', { cause: new Error('root only') })
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => root only

try {
    throw new DOMException('operation was aborted.', 'AbortError')
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => operation was aborted.

try {
    fs.readFileSync('definitely_not_exists_1234567890.txt')
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => ENOENT: no such file or directory, open ...

let o = { a: 1 }
o.self = o
console.log(getErrorMessage(o))
// => {"a":1,"self":"[Circular]"}

console.log(getErrorMessage({ v: 10n }))
// => {"v":"10"}

console.log(getErrorMessage({ message: 123 }))
// => 123

console.log(getErrorMessage(/ab/g))
// => /ab/g

let test1 = async() => {
    return Promise.reject('promise reject')
}
try {
    await test1()
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => promise reject

let test2 = async() => {
    throw new Error('something wrong')
}
try {
    await test2()
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => something wrong

let test3 = async() => {
    throw new Error()
}
try {
    await test3()
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => ''

let test4 = async() => {
    throw new TypeError('wrong type')
}
try {
    await test4()
}
catch (err) {
    console.log(getErrorMessage(err))
}
// => wrong type
Parameters:
Name Type Attributes Default Description
err *

傳入任意錯誤資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useCause Boolean <optional>
false

輸入是否串接cause鏈布林值,若為true則將各層cause之訊息以' <- '串接,預設false。惟外層message為空字串時,不論本項為何皆取cause,因該情形下無其他資訊來源

useAggregate Boolean <optional>
false

輸入是否附加AggregateError之errors各項訊息布林值,若為true則以'; '串接後置於方括號內,預設false

levelCauseMax Integer <optional>
8

輸入cause鏈與errors之最大展開層數正整數,預設8

levelJsonMax Integer <optional>
6

輸入序列化之最大巢狀深度正整數,超過者以'[Deep]'表示,預設6

lengthMessageMax Integer <optional>
0

輸入回傳字串之最大長度正整數,超過者截斷並以'...'標記,該標記計入本上限內,預設0代表不限制長度

Returns:

回傳錯誤訊息字串。無法提取時回傳空字串。序列化時循環參照處以'[Circular]'表示、BigInt轉為字串,此二標記不做跳脫,應用資料內若恰有相同字串將無法分辨

Type
String

(async, static) getExcelU8ArrFromData(data, sheetNameopt) → {Promise}

Description:
  • 由數據陣列轉成為Excel檔案的Uint8Array數據

    數據陣列可為二維陣列(mat)或物件陣列(ltdt),見getExcelWorksheetFromData 本函數為async function,前後端都可用

    Unit Test: Github

Source:
Example
//need test in nodejs

async function test() {

    let data = [
        ['a', '123', 456],
        [null, 'abc123', '', 111.222333],
    ]

    let u8a = await getExcelU8ArrFromData(data, 'mysheet')
    console.log(u8a instanceof Uint8Array, u8a[0], u8a[1])
    // => true 80 75 (xlsx為zip容器, 開頭必為'PK')

    let r = await getDataFromExcelFileU8Arr(u8a, { fmt: 'array' })
    console.log(r[0].sheetname, JSON.stringify(r[0].data))
    // => mysheet [["a","123","456",""],["","abc123","","111.222333"]]

}
test()
    .catch((err) => {
        console.log(err)
    })
Parameters:
Name Type Attributes Default Description
data Array

輸入數據陣列,可為二維陣列(mat)或物件陣列(ltdt)

sheetName String <optional>
'data'

輸入輸出為Excel時所在分頁(sheet)名稱字串,預設為'data'

Returns:

回傳Promise,resolve回傳Excel檔案的Uint8Array數據,輸入無效或轉換失敗時resolve回傳{ error }物件

Type
Promise

(async, static) getExcelU8ArrFromWorkbook(wb) → {Promise}

Description:
  • 由Excel的Workbook物件轉成為Excel檔案的Uint8Array數據

    Workbook物件格式為{ sheets: [{ name, rows }] }(見createExcelWorkbook/getExcelWorkbookFromData) 本函數為async function,前後端都可用

    Unit Test: Github

Source:
Example
//need test in nodejs

async function test() {

    let data = [
        ['a', '123', 456],
        [null, 'abc123', '', 111.222333],
    ]

    let wb = getExcelWorkbookFromData(data)
    let u8a = await getExcelU8ArrFromWorkbook(wb)
    console.log(u8a instanceof Uint8Array, u8a[0], u8a[1])
    // => true 80 75 (xlsx為zip容器, 開頭必為'PK')

}
test()
    .catch((err) => {
        console.log(err)
    })
Parameters:
Name Type Description
wb Object

輸入Excel的Workbook物件

Returns:

回傳Promise,resolve回傳Excel檔案的Uint8Array數據,wb無效或轉換失敗時resolve回傳{ error }物件

Type
Promise

(static) getExcelWorkbookFromData(data, sheetNameopt) → {Object}

Description:
  • 由數據陣列轉成為Excel的Workbook物件

    Workbook物件格式為{ sheets: [{ name, rows }] },數據陣列可為二維陣列(mat)或物件陣列(ltdt),見getExcelWorksheetFromData

    Unit Test: Github

Source:
Example
let data = [
    ['a', '123', 456],
    [null, 'abc123', '', 111.222333],
]

let wb1 = getExcelWorkbookFromData(data)
console.log(wb1.sheets[0].name)
// => data

let wb2 = getExcelWorkbookFromData(data, 'tester')
console.log(wb2.sheets[0].name)
// => tester
Parameters:
Name Type Attributes Default Description
data Array

輸入數據陣列,可為二維陣列(mat)或物件陣列(ltdt)

sheetName String <optional>
'data'

輸入輸出為Excel時所在分頁(sheet)名稱字串,預設為'data'

Returns:

回傳Excel的Workbook物件,data非陣列時回傳{ error }物件

Type
Object

(static) getExcelWorkbookFromWorksheet(sheet, sheetNameopt) → {Object}

Description:
  • 由Excel的Worksheet物件轉為Excel的Workbook物件

    Worksheet物件格式為{ rows }(見getExcelWorksheetFromData),Workbook物件格式為{ sheets: [{ name, rows }] }

    Unit Test: Github

Source:
Example
let data = [
    ['a', '123', 456],
    [null, 'abc123', '', 111.222333],
]

let ws = getExcelWorksheetFromData(data)

let wb1 = getExcelWorkbookFromWorksheet(ws)
console.log(wb1.sheets[0].name)
// => data

let wb2 = getExcelWorkbookFromWorksheet(ws, 'tester')
console.log(wb2.sheets[0].name)
// => tester
Parameters:
Name Type Attributes Default Description
sheet Object

輸入Excel的Worksheet物件,須含rows二維值陣列

sheetName String <optional>
'data'

輸入輸出為Excel時所在分頁(sheet)名稱字串,預設為'data'

Returns:

回傳Excel的Workbook物件,sheet無效時回傳{ error }物件

Type
Object

(static) getExcelWorksheetFromData(data) → {Object}

Description:
  • 由數據陣列轉成為Excel的Worksheet物件

    Worksheet物件格式為{ rows },rows為二維值陣列,不含分頁名稱,名稱由getExcelWorkbookFromWorksheet或addExcelWorksheetFromData給予 數據陣列可為二維陣列(mat)或物件陣列(ltdt),ltdt會以各物件之鍵作為表頭自動轉為mat

    Unit Test: Github

Source:
Example
let mat = [
    ['a', '123', 456],
    [null, 'abc123', '', 111.222333],
]

let ws1 = getExcelWorksheetFromData(mat)
console.log(ws1)
// => {
//   rows: [ [ 'a', '123', 456 ], [ null, 'abc123', '', 111.222333 ] ]
// }

let ltdt = [
    { x: 'a', y: '123', z: 456 },
    { x: null, y: 'abc123', z: '', a: 111.222333 },
]

let ws2 = getExcelWorksheetFromData(ltdt)
console.log(ws2)
// => {
//   rows: [
//     [ 'x', 'y', 'z', 'a' ],
//     [ 'a', '123', 456, '' ],
//     [ 'null', 'abc123', '', 111.222333 ]
//   ]
// }
Parameters:
Name Type Description
data Array

輸入數據陣列,可為二維陣列(mat)或物件陣列(ltdt)

Returns:

回傳Excel的Worksheet物件,data非陣列時回傳{ error }物件

Type
Object

(static) getFileAccept(groupByopt) → {Array}

Description:
  • 取得檔案關聯性資訊

    Unit Test: Github

Source:
Example
console.log(getFileAccept()[2])
// => { name: 'pdf',
//      group: 'docums',
//      acp: 'application/pdf',
//      exec: 'acrobat' }

console.log(getFileAccept('acp')['text/html'])
// => [ { name: 'htm',
//        group: 'docums',
//        acp: 'text/html',
//        exec: 'browser' },
//      { name: 'html',
//        group: 'docums',
//        acp: 'text/html',
//        exec: 'browser' } ]
Parameters:
Name Type Attributes Default Description
groupBy string <optional>
''

輸入群組化關鍵字字串,預設'',可選用'name','group','acp','exec'

Returns:

回傳檔案關聯性資訊陣列,若有給groupBy則自動群組化

Type
Array

(async, static) getFileBasicHash(inp, optopt) → {Promise}

Description:
  • 計算檔案HASH值

    瀏覽器端輸入檔案Blob或File,可支援大檔

    因Node.js亦已支援Blob,可用new Blob([fs.readFileSync(fp)])取得Blob做為輸入

    Unit Test: Github

Source:
Example
let test = async () => {

    let ms = []

    let fdt = './_test_getFileBasicHash'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/abc/${fn}`

    fsWriteText(fp, 'xyz')

    let ab = fs.readFileSync(fp)
    let bb = new Blob([ab])

    let h1 = await getFileBasicHash(bb)
    console.log('getFileBasicHash(sha512)', h1)
    ms.push({ 'getFileBasicHash(sha512)': h1 })

    let h2 = await getFileBasicHash(bb, { type: 'sha256' })
    console.log('getFileBasicHash(sha256)', h2)
    ms.push({ 'getFileBasicHash(sha256)': h2 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// getFileBasicHash(sha512) 4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a58e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728
// getFileBasicHash(sha256) 3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282
// ms [
//   {
//     'getFileBasicHash(sha512)': '4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a58e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728'
//   },
//   {
//     'getFileBasicHash(sha256)': '3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282'
//   }
// ]
Parameters:
Name Type Attributes Default Description
inp Blob | File

輸入檔案Blob或File

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'sha512'

輸入計算HASH方法字串,預設'sha512'

Returns:

回傳Promise,此時若成功則resolve代表檔案HASH值,若失敗則reject錯誤訊息

Type
Promise

(async, static) getFileHash(inp, optopt) → {Promise}

Description:
  • 計算檔案HASH值

    瀏覽器端輸入檔案Blob或File,可支援大檔

    因Node.js亦已支援Blob,可用new Blob([fs.readFileSync(fp)])取得Blob做為輸入

    Unit Test: Github

Source:
Example
let test = async () => {

    let ms = []

    let fdt = './_test_getFileHash'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/abc/${fn}`

    fsWriteText(fp, 'xyz')

    let ab = fs.readFileSync(fp)
    let bb = new Blob([ab])

    let h1 = await getFileHash(bb)
    console.log('getFileHash(sha512)', h1)
    ms.push({ 'getFileHash(sha512)': h1 })

    let h2 = await getFileHash(bb, { type: 'sha256' })
    console.log('getFileHash(sha256)', h2)
    ms.push({ 'getFileHash(sha256)': h2 })

    let h3 = await getFileHash(bb, { type: 'xxhash64' })
    console.log('getFileHash(xxhash64)', h3)
    ms.push({ 'getFileHash(xxhash64)': h3 })

    let h4 = await getFileHash(bb, { type: 'xxhash64', chunkSize: 16 * 1024 * 1024 })
    console.log('getFileHash(xxhash64,chunkSize=16mb)', h4)
    ms.push({ 'getFileHash(xxhash64,chunkSize=16mb)': h4 })

    let h5 = await getFileHash(bb, { type: 'xxhash64', chunkSize: 4 * 1024 * 1024 })
    console.log('getFileHash(xxhash64,chunkSize=4mb)', h5)
    ms.push({ 'getFileHash(xxhash64,chunkSize=4mb)': h5 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// getFileHash(sha512) 4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a58e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728
// getFileHash(sha256) 3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282
// getFileHash(xxhash64) feba48465b833ca1
// getFileHash(xxhash64,chunkSize=16mb) feba48465b833ca1
// getFileHash(xxhash64,chunkSize=4mb) feba48465b833ca1
// ms [
//   {
//     'getFileHash(sha512)': '4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a58e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728'
//   },
//   {
//     'getFileHash(sha256)': '3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282'
//   },
//   { 'getFileHash(xxhash64)': 'feba48465b833ca1' },
//   { 'getFileHash(xxhash64,chunkSize=16mb)': 'feba48465b833ca1' },
//   { 'getFileHash(xxhash64,chunkSize=4mb)': 'feba48465b833ca1' }
// ]
Parameters:
Name Type Attributes Default Description
inp Blob | File

輸入檔案Blob或File

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
type String <optional>
'sha512'

輸入計算HASH方法字串,預設'sha512'

chunkSize Number <optional>
64*1024*1024

輸入計算HASH方法字串為'xxhash64'時,使用切片長度數字,單位位元,預設6410241024(64mb)

Returns:

回傳Promise,此時若成功則resolve代表檔案HASH值,若失敗則reject錯誤訊息

Type
Promise

(static) getFileName(str) → {string}

Description:
  • 由檔案位置取得檔案名稱

    Unit Test: Github

Source:
Example
let c

c = 'C:\\temp\\myfile.html'
console.log(getFileName(c))
// => myfile.html

c = 'C:\\temp\\myfile.txt.html'
console.log(getFileName(c))
// => myfile.txt.html

c = 'C:\\temp\\myfile'
console.log(getFileName(c))
// => myfile

c = 'C:\\temp\\\\temp\\\\myfile.txt.html'
console.log(getFileName(c))
// => myfile.txt.html

c = 'C:\\temp\\'
console.log(getFileName(c))
// => temp

c = 'C:\\temp'
console.log(getFileName(c))
// => temp

c = 'C:\\'
console.log(getFileName(c))
// => C:\

c = 'C:'
console.log(getFileName(c))
// => C:\

c = '/tmp/myfile.html'
console.log(getFileName(c))
// => myfile.html

c = '/tmp/myfile.txt.html'
console.log(getFileName(c))
// => myfile.txt.html

c = '/tmp/myfile'
console.log(getFileName(c))
// => myfile

c = '//tmp////tmp//myfile.txt.html'
console.log(getFileName(c))
// => myfile.txt.html

c = '/tmp/'
console.log(getFileName(c))
// => tmp

c = '/tmp'
console.log(getFileName(c))
// => tmp

c = '/'
console.log(getFileName(c))
// => /

c = '/foo/bar/baz/asdf/quux.html'
console.log(getFileName(c))
// => quux.html

c = '/foo/bar/baz/asdf/quux.txt.html'
console.log(getFileName(c))
// => quux.txt.html

c = '/foo/bar/baz/asdf/quux'
console.log(getFileName(c))
// => quux

c = ''
console.log(getFileName(c))
// => [empty string]
Parameters:
Name Type Description
str string

輸入檔案位置字串

Returns:

檔案名稱

Type
string

(static) getFileNameExt(str) → {string}

Description:
  • 由檔案位置取得檔案的副檔名

    Unit Test: Github

Source:
Example
let c

c = 'C:\\temp\\myfile.html'
console.log(getFileNameExt(c))
// => html

c = 'C:\\temp\\myfile.txt.html'
console.log(getFileNameExt(c))
// => html

c = 'C:\\temp\\myfile'
console.log(getFileNameExt(c))
// => [empty string]

c = 'C:\\temp\\\\temp\\\\myfile.txt.html'
console.log(getFileNameExt(c))
// => html

c = 'C:\\temp\\'
console.log(getFileNameExt(c))
// => [empty string]

c = 'C:\\temp'
console.log(getFileNameExt(c))
// => [empty string]

c = 'C:\\'
console.log(getFileNameExt(c))
// => [empty string]

c = 'C:'
console.log(getFileNameExt(c))
// => [empty string]

c = '/tmp/myfile.html'
console.log(getFileNameExt(c))
// => html

c = '/tmp/myfile.txt.html'
console.log(getFileNameExt(c))
// => html

c = '/tmp/myfile'
console.log(getFileNameExt(c))
// => [empty string]

c = '//tmp////tmp//myfile.txt.html'
console.log(getFileNameExt(c))
// => html

c = '/tmp/'
console.log(getFileNameExt(c))
// => [empty string]

c = '/tmp'
console.log(getFileNameExt(c))
// => [empty string]

c = '/'
console.log(getFileNameExt(c))
// => [empty string]

c = '/foo/bar/baz/asdf/quux.html'
console.log(getFileNameExt(c))
// => html

c = '/foo/bar/baz/asdf/quux.txt.html'
console.log(getFileNameExt(c))
// => html

c = '/foo/bar/baz/asdf/quux'
console.log(getFileNameExt(c))
// => [empty string]

c = ''
console.log(getFileNameExt(c))
// => [empty string]
Parameters:
Name Type Description
str string

輸入檔案位置字串

Returns:

檔案的副檔名

Type
string

(static) getFileTrueName(str) → {string}

Description:
  • 由檔案位置取得檔案的不含副檔名名稱

    Unit Test: Github

Source:
Example
let c

c = 'C:\\temp\\myfile.html'
console.log(getFileTrueName(c))
// => myfile

c = 'C:\\temp\\myfile.txt.html'
console.log(getFileTrueName(c))
// => myfile.txt

c = 'C:\\temp\\myfile'
console.log(getFileTrueName(c))
// => myfile

c = 'C:\\temp\\\\temp\\\\myfile.txt.html'
console.log(getFileTrueName(c))
// => myfile.txt

c = 'C:\\temp\\'
console.log(getFileTrueName(c))
// => temp

c = 'C:\\temp'
console.log(getFileTrueName(c))
// => temp

c = 'C:\\'
console.log(getFileTrueName(c))
// => C:\\

c = 'C:'
console.log(getFileTrueName(c))
// => C:\\

c = '/tmp/myfile.html'
console.log(getFileTrueName(c))
// => myfile

c = '/tmp/myfile.txt.html'
console.log(getFileTrueName(c))
// => myfile.txt

c = '/tmp/myfile'
console.log(getFileTrueName(c))
// => myfile

c = '//tmp////tmp//myfile.txt.html'
console.log(getFileTrueName(c))
// => myfile.txt

c = '/tmp/'
console.log(getFileTrueName(c))
// => tmp

c = '/tmp'
console.log(getFileTrueName(c))
// => tmp

c = '/'
console.log(getFileTrueName(c))
// => /

c = '/foo/bar/baz/asdf/quux.html'
console.log(getFileTrueName(c))
// => quux

c = '/foo/bar/baz/asdf/quux.txt.html'
console.log(getFileTrueName(c))
// => quux.txt

c = '/foo/bar/baz/asdf/quux'
console.log(getFileTrueName(c))
// => quux

c = ''
console.log(getFileTrueName(c))
// => [empty string]
Parameters:
Name Type Description
str string

輸入檔案位置字串

Returns:

檔案不含副檔名的名稱

Type
string

(static) getFileType(nameopt) → {Object|Array|null}

Description:
  • 取得檔案類型資訊 副檔名不含點,比對不分大小寫(回傳列之name維持表內原樣,如'z'回傳name為'Z'之列) 若副檔名多義或無IANA登錄,該列會附msg欄位說明(如gpx、las、obj) heic之acp為image/heic,heif之acp為image/heif

    Unit Test: Github

Source:
Example
let r

r = getFileType('mp3')
console.log('name=mp3', r)
// => name=mp3 { name: 'mp3', group: 'audio', acp: 'audio/mpeg', exec: 'audioplayer' }

r = getFileType('mp4')
console.log('name=mp4', r)
// => name=mp4 { name: 'mp4', group: 'video', acp: 'video/mp4', exec: 'videoplayer' }

r = getFileType('SVG')
console.log('name=SVG', r)
// => name=SVG { name: 'svg', group: 'image', acp: 'image/svg+xml', exec: 'imageviwer' }

r = getFileType('heic')
console.log('name=heic', r)
// => name=heic { name: 'heic', group: 'image', acp: 'image/heic', exec: 'imageviwer' }

r = getFileType('xyz')
console.log('name=xyz', r)
// => name=xyz null
Parameters:
Name Type Attributes Default Description
name string <optional>
''

輸入過濾副檔名字串(不含點,不分大小寫),預設''

Returns:

回傳檔案關聯性資訊物件,未收錄之副檔名回傳null,未給予有效字串則回傳全部資訊陣列

Type
Object | Array | null

(async, static) getFileXxHash(inp, optopt) → {Promise}

Description:
  • 計算檔案xxHash64值

    瀏覽器端輸入檔案Blob或File,可支援大檔

    因Node.js亦已支援Blob,可用new Blob([fs.readFileSync(fp)])取得Blob做為輸入

    Unit Test: Github

Source:
Example
let test = async () => {

    let ms = []

    let fdt = './_test_getFileXxHash'
    fsCreateFolder(fdt) //創建臨時任務資料夾

    let fn = 't1.txt'
    let fp = `${fdt}/abc/${fn}`

    fsWriteText(fp, 'xyz')

    let ab = fs.readFileSync(fp)
    let bb = new Blob([ab])

    let h1 = await getFileXxHash(bb)
    console.log('getFileXxHash', h1)
    ms.push({ 'getFileXxHash': h1 })

    let h2 = await getFileXxHash(bb, { chunkSize: 16 * 1024 * 1024 })
    console.log('getFileXxHash(chunkSize=16mb)', h2)
    ms.push({ 'getFileXxHash(chunkSize=16mb)': h2 })

    let h3 = await getFileXxHash(bb, { chunkSize: 4 * 1024 * 1024 })
    console.log('getFileXxHash(chunkSize=4mb)', h3)
    ms.push({ 'getFileXxHash(chunkSize=4mb)': h3 })

    fsDeleteFolder(fdt) //刪除臨時任務資料夾

    console.log('ms', ms)
    return ms
}
await test()
    .catch((err) => {
        console.log(err)
    })
// getFileXxHash feba48465b833ca1
// getFileXxHash(chunkSize=16mb) feba48465b833ca1
// getFileXxHash(chunkSize=4mb) feba48465b833ca1
// ms [
//   { getFileXxHash: 'feba48465b833ca1' },
//   { 'getFileXxHash(chunkSize=16mb)': 'feba48465b833ca1' },
//   { 'getFileXxHash(chunkSize=4mb)': 'feba48465b833ca1' }
// ]
Parameters:
Name Type Attributes Default Description
inp Blob | File

輸入檔案Blob或File

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
chunkSize Number <optional>
64*1024*1024

輸入切片長度數字,單位位元,預設6410241024(64mb)

Returns:

回傳Promise,此時若成功則resolve代表檔案HASH值,若失敗則reject錯誤訊息

Type
Promise

(static) getGlobal() → {Object}

Description:
  • 取得運行環境中的頂層物件

    Unit Test: Github

Source:
Example
console.log(getGlobal())
// => global object in running environment
Returns:

回傳頂層物件,若無法取得則回傳null

Type
Object

(static) getObjSize(obj) → {Object}

Description:
  • 計算物件記憶體大小

    Unit Test: Github

Source:
Example
console.log(getObjSize('abc123'))
// => 12

console.log(getObjSize({ a: 123, b: 'xyz', c: '45op', d: null }))
// => 22
Parameters:
Name Type Description
obj *

傳入欲計算大小的任意數據

Returns:

回傳記憶體大小物件, int屬性為記憶體所使用的bytes整數, str屬性為自動處理單位後的字串

Type
Object

(static) getPathParent(str) → {string}

Description:
  • 由檔案或資料夾位置取得所在資料夾位置

    Unit Test: Github

Source:
Example
let c

c = 'C:\\temp\\myfile.html'
console.log(getPathParent(c))
// => C:\\temp

c = 'C:\\temp\\myfile.txt.html'
console.log(getPathParent(c))
// => C:\\temp

c = 'C:\\temp\\myfile'
console.log(getPathParent(c))
// => C:\\temp

c = 'C:\\temp\\\\temp\\\\myfile.txt.html'
console.log(getPathParent(c))
// => C:\\temp\\temp

c = 'C:\\temp\\'
console.log(getPathParent(c))
// => C:\\

c = 'C:\\temp'
console.log(getPathParent(c))
// => C:\\

c = 'C:\\'
console.log(getPathParent(c))
// => C:\\

c = 'C:'
console.log(getPathParent(c))
// => C:\\

c = '/tmp/myfile.html'
console.log(getPathParent(c))
// => /tmp

c = '/tmp/myfile.txt.html'
console.log(getPathParent(c))
// => /tmp

c = '/tmp/myfile'
console.log(getPathParent(c))
// => /tmp

c = '//tmp////tmp//myfile.txt.html'
console.log(getPathParent(c))
// => /tmp/tmp

c = '/tmp/'
console.log(getPathParent(c))
// => /

c = '/tmp'
console.log(getPathParent(c))
// => /

c = '/'
console.log(getPathParent(c))
// => /

c = '/foo/bar/baz/asdf/quux.html'
console.log(getPathParent(c))
// => /foo/bar/baz/asdf

c = '/foo/bar/baz/asdf/quux.txt.html'
console.log(getPathParent(c))
// => /foo/bar/baz/asdf

c = '/foo/bar/baz/asdf/quux'
console.log(getPathParent(c))
// => /foo/bar/baz/asdf

c = ''
console.log(getPathParent(c))
// => [empty string]
Parameters:
Name Type Description
str string

輸入檔案位置字串

Returns:

檔案名稱

Type
string

(static) getTimeBetween(tstart, tend, unitopt) → {Array}

Description:
  • 輸入兩時間,單位皆為unit,由兩時間之間回傳以unit分切的時間點,回傳時間單位一樣為unit

    Unit Test: Github

Source:
Example
console.log(getTimeBetween('2017', '2019', 'years'))
// => ['2017', '2018', '2019']

console.log(getTimeBetween('2019-01-01', '2019-01-03', 'days'))
// => ['2019-01-01', '2019-01-02', '2019-01-03']

console.log(getTimeBetween('2019-01-01', '2019-01-03'))
// => ['2019-01-01', '2019-01-02', '2019-01-03']
Parameters:
Name Type Attributes Default Description
tstart String

輸入開始時間字串,不含時區

tend String

輸入結束時間字串,不含時區

unit String <optional>
'days'

輸入切分單位字串,預設為'days'

Returns:

回傳切分後各時間陣列

Type
Array

(static) getTimeFromUnit(unit) → {String}

Description:
  • 輸入時間單位,回傳時間套件所需之解析字串

    Unit Test: Github

Source:
Example
console.log(getTimeFromUnit('years'))
// => 'YYYY'

console.log(getTimeFromUnit('seconds'))
// => 'YYYY-MM-DDTHH:mm:ssZ'
Parameters:
Name Type Description
unit String

輸入時間單位字串

Returns:

回傳時間套件所需之解析字串,當unit='seconds'時回傳不含時區格式

Type
String

(static) getTimeHp() → {Number}

Description:
  • 回傳當前高精度時間

    Unit Test: Github

Source:
Example
let t = getTimeHp()
console.log('t', t)
Returns:

回傳當前時間數字

Type
Number

(static) getTimeObject(t, unitopt) → {Object}

Description:
  • 輸入時間字串,單位為unit,回傳解析後之時間物件

    Unit Test: Github

Source:
Example
console.log(getTimeObject('2019-01-01', 'days'))
// => dayjs('2019-01-01', 'YYYY-MM-DD') //use dayjs or moment

console.log(getTimeObject('2019-01-01T12:34:56', 'seconds'))
// => dayjs('2019-01-01T12:34:56', 'YYYY-MM-DDTHH:mm:ssZ') //use dayjs or moment
Parameters:
Name Type Attributes Default Description
t String

輸入時間字串,不含時區

unit String <optional>
'days'

輸入時間單位字串,預設為'days'

Returns:

回傳時間物件,若非法時間則回傳null

Type
Object

(static) getTimeRange(tstart, tend) → {String}

Description:
  • 起訖時間合併顯示,時間單位皆為秒

    Unit Test: Github

Source:
Example
console.log(getTimeRange('2019-01-01T09:00:00'))
// => '2019-01-01T09:00 至 無'

console.log(getTimeRange('2019-01-01T09:00:00', '2019-01-01T12:34:56'))
// => '2019-01-01T09:00 至 12:34'

console.log(getTimeRange('2019-01-01T09:00:00', '2019-01-03T12:00:00'))
// => '2019-01-01T09:00 至 2019-01-03T12:00'

console.log(getTimeRange('2019-01-01T19:00:00', '2019-01-01T12:00:00'))
// => '2019-01-01T19:00 至 12:00 (起始時間大於結束時間)'

console.log(getTimeRange('2019-01-03T09:00:00', '2019-01-01T12:00:00'))
// => '2019-01-03T09:00 至 2019-01-01T12:00 (起始時間大於結束時間)'
Parameters:
Name Type Description
tstart String

輸入開始秒時間字串,不含時區

tend String

輸入結束秒時間字串,不含時區,若不輸入則等同於開始時間字串

Returns:

回傳合併顯示時間字串

Type
String

(static) getTrace(optopt) → {function|null}

Description:
  • 取設定物件之觀察回調(opt.onTrace),回傳通知函數或null,供計時器相關函數(輪詢、偵測、重試、節流、防抖、排程、逾時、等待)共用同一套規則,不各自手寫

    本函數供wsemi自身與其他自有套件(w-*)之計時器相關函數取得觀察回調,統一以單一擁有者實作,避免各套件自寫等價版本而分歧。觀察回調用於讓測試與診斷以「事件發生」而非「等待毫秒」同步,規則見sgud/rule/rulebook.md之R-TRACE-01

    觀察回調於函數內部之事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... }:type為各函數於JSDoc列舉之固定字串,其餘欄位為原始值(數字、字串、布林或null)之快照,不含內部可變狀態與Error物件

    回調不帶接收者呼叫(this為undefined),回傳值不使用;其同步拋錯與回傳Promise之rejection皆忽略,故不中斷被觀察之函數、不改變其結果,亦不產生未處理之rejection

    opt.onTrace未給或非函數時回傳null,呼叫端以if (trace)略過組事件物件,使未傳回調時之行為、時序與效能同未支援前;函數被釋放(停止、清除)後呼叫端不應再通知

    Unit Test: Github

Source:
Example
//被觀察函數之內部
function work(opt = {}) {
    let trace = getTrace(opt)
    let n = 0
    let t = setInterval(() => {
        n++
        if (trace) {
            trace('tick', { n })
        }
        if (n >= 2) {
            clearInterval(t)
        }
    }, 10)
}

//使用端
work({
    onTrace: (e) => {
        console.log(e)
    },
})
// => { type: 'tick', n: 1 }
// => { type: 'tick', n: 2 }
Parameters:
Name Type Attributes Default Description
opt Object <optional>
{}

輸入設定物件,取其onTrace屬性,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,格式為(e)=>{},e為凍結之事件物件{ type, ... },預設null

Returns:

回傳通知函數,格式為(type, info)=>{},type為事件類型字串,info為事件之其餘欄位物件;opt.onTrace非函數時回傳null

Type
function | null

(static) getUserAgent(vopt) → {Object}

Description:
  • 取得使用者瀏覽器資訊,後端nodejs環境使用需提供user agent字串

    Unit Test: Github

Source:
Example
let ua = 'Mozilla/5.0 (compatible; Konqueror/4.1; OpenBSD) KHTML/4.1.4 (like Gecko)'
console.log(getUserAgent(ua))
// => {
    browsername: 'Konqueror',
    browserversion: '4.1',
    cpuarchitecture: '',
    devicetype: '',
    engineinfor: 'KHTML4.1.4',
    platform: 'OpenBSDundefined'
}
Parameters:
Name Type Attributes Default Description
v String <optional>
null

輸入user agent字串,預設null

Returns:

回傳使用者瀏覽器資訊物件

Type
Object

(async, static) getUserAgentDyn(vopt, pathItems) → {Promise}

Description:
  • 前端取得使用者瀏覽器資訊,於browser中不輸入即可自動偵測,採用動態加載技術

    Unit Test: Github

Source:
Example
need test in browser

let ua = 'Mozilla/5.0 (compatible; Konqueror/4.1; OpenBSD) KHTML/4.1.4 (like Gecko)'
getUserAgentDyn(ua)
    .then(function(r) {
        console.log(r)
        // => {
            browsername: 'Konqueror',
            browserversion: '4.1',
            cpuarchitecture: '',
            devicetype: '',
            engineinfor: 'KHTML4.1.4',
            platform: 'OpenBSDundefined'
        }
    })
Parameters:
Name Type Attributes Default Description
v String <optional>
null

輸入user agent字串,預設null

pathItems String | Object | Array

輸入資源字串、字串陣列、物件、物件陣列

Returns:

回傳Promise,resolve回傳使用者瀏覽器資訊物件,reject回傳錯誤訊息

Type
Promise

(static) getltdtkeys(ltdt) → {Array}

Description:
  • 由ltdt提取不重複keys,各列物件只依自有可列舉鍵(length視為一般鍵)

    Unit Test: Github

Source:
Example
console.log(getltdtkeys([{ a: 123, b: 'xyz', c: '45op', d: null }, { a: 123.456, b: 'xyz', d: '45op', e: '' }]))
// => ['a', 'b', 'c', 'd', 'e']
Parameters:
Name Type Description
ltdt Array

輸入物件陣列

Returns:

回傳不重複keys陣列

Type
Array

(static) haskey(obj, key) → {Boolean}

Description:
  • 判斷物件是否有key屬性

    Unit Test: Github

Source:
Example
console.log(haskey({ a: 123, b: 'xyz', c: '45op', d: null }, 'a'))
// => true
Parameters:
Name Type Description
obj Object

輸入物件

key String

輸入要查找的key字串

Returns:

回傳判斷布林值

Type
Boolean

(async, static) html2str(html) → {Promise}

Description:
  • html轉純文字

    Unit Test: Github

Source:
Example
let h = `
<!DOCTYPE html>
<html>

<body>
    <h1>My First Heading</h1>
    <p>My first paragraph.</p>
</body>

</html>
`

let c = await html2str(h)
console.log(c)
// =>
// MY FIRST HEADING
//
// My first paragraph.
Parameters:
Name Type Description
html String

輸入html字串

Returns:

回傳Promise,resolve回傳純文字字串,reject回傳錯誤訊息

Type
Promise

(static) htmlDecode(str) → {String}

Description:
  • Html特殊字元(Html entities)字串反編碼

    Unit Test: Github

Source:
Example
console.log(htmlDecode('foo&#x26;bar'))
// => foo&bar

console.log(htmlDecode('foo &#xA9; bar &#x2260; baz &#x1D306; qux'))
// => foo © bar ≠ baz 𝌆 qux

console.log(htmlDecode('&#x3C;img src=&#x22;x&#x22;&#x22; onerror=&#x22;prompt(1)&#x22;&#x3E;'))
// => <img src="x"" onerror="prompt(1)">
Parameters:
Name Type Description
str String

輸入編碼後html字串

Returns:

回傳html字串

Type
String

(static) htmlEncode(str) → {String}

Description:
  • Html特殊字元(Html entities)字串編碼

    Unit Test: Github

Source:
Example
console.log(htmlEncode('foo&bar'))
// => oo&#x26;bar

console.log(htmlEncode('foo © bar ≠ baz 𝌆 qux'))
// => foo &#xA9; bar &#x2260; baz &#x1D306; qux

console.log(htmlEncode('<img src="x"" onerror="prompt(1)">'))
// => &#x3C;img src=&#x22;x&#x22;&#x22; onerror=&#x22;prompt(1)&#x22;&#x3E;
Parameters:
Name Type Description
str String

輸入html字串

Returns:

回傳編碼後html字串

Type
String

(static) importResources(pathItems) → {Promise}

Description:
  • 前端動態引入資源如Javascript或CSS檔案

    同一路徑或同一組路徑載入中之呼叫,與首位共用其載入結果(同成敗);載入失敗(script之error事件、pathItem.func拋錯或其Promise被reject、等待func成立逾次)時reject並清除標記,之後之呼叫重新載入;CSS(link)不等待其載入事件,加入後即視為完成

    Unit Test: Github

Source:
Example
//need test in browser

let pathItems

pathItems = [
    'https://cdn.jsdelivr.net/npm/ag-grid-community@22.0.0/dist/ag-grid-community.noStyle.js',
    'https://cdn.jsdelivr.net/npm/ag-grid-vue@22.0.0/dist/ag-grid-vue.umd.js',
    'https://cdn.jsdelivr.net/npm/w-aggrid-vue@1.0.28/dist/w-aggrid-vue.umd.js',
]
importResources(pathItems)
    .then((res)=>{
        console.log(res)
    })

pathItems = [
    {
        path: 'https://cdn.jsdelivr.net/npm/ag-grid-community@22.0.0/dist/ag-grid-community.noStyle.js',
        type: 'js',
    },
    {
        path: 'https://cdn.jsdelivr.net/npm/ag-grid-vue@22.0.0/dist/ag-grid-vue.umd.js',
        type: 'js',
    },
    {
        path: 'https://cdn.jsdelivr.net/npm/w-aggrid-vue@1.0.28/dist/w-aggrid-vue.umd.js',
        type: 'js',
    },
]
importResources(pathItems)
    .then((res)=>{
        console.log(res)
    })
Parameters:
Name Type Description
pathItems String | Object | Array

輸入資源字串、字串陣列、物件、物件陣列

Returns:

回傳Promise,resolve回傳注入成功的HtmlElement陣列(已載入完成或與載入中者共用時為'loaded'),reject回傳載入失敗之script元素(error事件)、func之錯誤或錯誤訊息

Type
Promise

(static) interp1(ps, px, optopt) → {Number|Object}

Description:
  • 一維數據內插

    Unit Test: Github

Source:
Example
let r
let x

let ps = [
    { x: 1, y: 0.2 },
    { x: 3, y: 1.2 },
    { x: 4, y: 2 },
]

let psInv = [
    { x: 4, y: 2 },
    { x: 3, y: 1.2 },
    { x: 1, y: 0.2 },
]

let psErr = [
    { x: 'a', y: 0.2 },
    { x: 'mnop', y: 1.2 },
    { x: 'xyz', y: 2 },
]

let psEmpty = [
]

let psEffOne = [
    { x: 1, y: 0.2 },
    { x: 'mnop', y: 1.2 },
    { x: 'xyz', y: 2 },
]

let psP = [
    { a: 1, b: 0.2 },
    { a: 3, b: 1.2 },
    { a: 4, b: 2 },
]

let px = [0, 1, 2, 2.6, 3, 3.5, 4, 5]

let opt = {
    mode: 'stairs',
}

let optX = {
    mode: 'stairs',
    xMin: 0,
    xMax: 4.5,
}

let optP = {
    keyX: 'a',
    keyY: 'b',
}

x = 0
r = interp1(psErr, x)
console.log(`linear(error data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(error data): x=0 r={"err":"ps(length=0) is not an effective array","ps":[{"x":"a","y":0.2},{"x":"mnop","y":1.2},{"x":"xyz","y":2}],"psEff":[]}

x = 0
r = interp1(psEmpty, x)
console.log(`linear(empty data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(empty data): x=0 r={"err":"ps(length=0) is not an effective array","ps":[],"psEff":[]}

x = 0
r = interp1(psEffOne, x)
console.log(`linear(one point): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(one point): x=0 r={"err":"ps(length=1) is one point only","ps":[{"x":1,"y":0.2},{"x":"mnop","y":1.2},{"x":"xyz","y":2}],"psEff":[{"x":1,"y":0.2}]}

x = 0
r = interp1(ps, x)
console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r))
// => linear: x=0 r={"err":"out of x-range","msg":"x[0] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":0,"xmin":1,"xmax":4}}

x = 1
r = interp1(ps, x)
console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r))
// => linear: x=1 r=0.2

x = 2
r = interp1(ps, x)
console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r))
// => linear: x=2 r=0.7

x = 2.6
r = interp1(ps, x)
console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r))
// => linear: x=2.6 r=1

x = 3
r = interp1(ps, x)
console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r))
// => linear: x=3 r=1.2

x = 3.5
r = interp1(ps, x)
console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r))
// => linear: x=3.5 r=1.6

x = 4
r = interp1(ps, x)
console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r))
// => linear: x=4 r=2

x = 5
r = interp1(ps, x)
console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r))
// => linear: x=5 r={"err":"out of x-range","msg":"x[5] greater than upper limit[4]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":5,"xmin":1,"xmax":4}}

x = 0
r = interp1(psInv, x)
console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(inverse data): x=0 r={"err":"out of x-range","msg":"x[0] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":0,"xmin":1,"xmax":4}}

x = 1
r = interp1(psInv, x)
console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(inverse data): x=1 r=0.2

x = 2
r = interp1(psInv, x)
console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(inverse data): x=2 r=0.7

x = 2.6
r = interp1(psInv, x)
console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(inverse data): x=2.6 r=1

x = 3
r = interp1(psInv, x)
console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(inverse data): x=3 r=1.2

x = 3.5
r = interp1(psInv, x)
console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(inverse data): x=3.5 r=1.6

x = 4
r = interp1(psInv, x)
console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(inverse data): x=4 r=2

x = 5
r = interp1(psInv, x)
console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r))
// => linear(inverse data): x=5 r={"err":"out of x-range","msg":"x[5] greater than upper limit[4]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":5,"xmin":1,"xmax":4}}

x = -1
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=-1 r={"err":"out of x-range","msg":"x[-1] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":-1,"xmin":1,"xmax":4}}

x = 0.51
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=0.51 r={"err":"out of x-range","msg":"x[0.51] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":0.51,"xmin":1,"xmax":4}}

x = 1
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=1 r=0.2

x = 1.9
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=1.9 r=0.2

x = 2
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=2 r=0.2

x = 2.1
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=2.1 r=1.2

x = 2.5
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=2.5 r=1.2

x = 3
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=3 r=1.2

x = 3.49
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=3.49 r=1.2

x = 3.5
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=3.5 r=1.2

x = 3.51
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=3.51 r=2

x = 4
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=4 r=2

x = 4.5
r = interp1(ps, x, opt)
console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs: x=4.5 r={"err":"out of x-range","msg":"x[4.5] greater than upper limit[4]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":4.5,"xmin":1,"xmax":4}}

x = -1
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=-1 r={"err":"out of x-range","msg":"x[-1] less than lower limit[0]","data":{"ps":[{"x":0,"y":0.2},{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2},{"x":4.5,"y":2}],"x":-1,"xmin":0,"xmax":4.5}}

x = 0
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=0 r=0.2

x = 0.49
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=0.49 r=0.2

x = 0.5
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=0.5 r=0.2

x = 0.51
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=0.51 r=0.2

x = 1
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=1 r=0.2

x = 1.9
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=1.9 r=0.2

x = 2
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=2 r=0.2

x = 2.1
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=2.1 r=1.2

x = 2.5
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=2.5 r=1.2

x = 3
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=3 r=1.2

x = 3.49
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=3.49 r=1.2

x = 3.5
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=3.5 r=1.2

x = 3.51
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=3.51 r=2

x = 4
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=4 r=2

x = 4.49
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=4.49 r=2

x = 4.5
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=4.5 r=2

x = 4.51
r = interp1(ps, x, optX)
console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r))
// => stairs with x-limit: x=4.51 r={"err":"out of x-range","msg":"x[4.51] greater than upper limit[4.5]","data":{"ps":[{"x":0,"y":0.2},{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2},{"x":4.5,"y":2}],"x":4.51,"xmin":0,"xmax":4.5}}

x = 0
r = interp1(psP, x, optP)
console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r))
// => linear by keyX & keyY: x=0 r={"err":"out of x-range","msg":"x[0] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":0,"xmin":1,"xmax":4}}

x = 1
r = interp1(psP, x, optP)
console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r))
// => linear by keyX & keyY: x=1 r=0.2

x = 2
r = interp1(psP, x, optP)
console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r))
// => linear by keyX & keyY: x=2 r=0.7

x = 2.6
r = interp1(psP, x, optP)
console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r))
// => linear by keyX & keyY: x=2.6 r=1

x = 3
r = interp1(psP, x, optP)
console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r))
// => linear by keyX & keyY: x=3 r=1.2

x = 3.5
r = interp1(psP, x, optP)
console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r))
// => linear by keyX & keyY: x=3.5 r=1.6

x = 4
r = interp1(psP, x, optP)
console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r))
// => linear by keyX & keyY: x=4 r=2

x = 5
r = interp1(psP, x, optP)
console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r))
// => linear by keyX & keyY: x=5 r={"err":"out of x-range","msg":"x[5] greater than upper limit[4]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":5,"xmin":1,"xmax":4}}

r = interp1(ps, px)
console.log(`linear: px=${JSON.stringify(px)}`, 'r=' + JSON.stringify(r, null, 2))
// => linear: px=[0,1,2,2.6,3,3.5,4,5] r=[
//   {
//     "err": "out of x-range",
//     "msg": "x[0] less than lower limit[1]",
//     "data": {
//       "ps": [
//         {
//           "x": 1,
//           "y": 0.2
//         },
//         {
//           "x": 3,
//           "y": 1.2
//         },
//         {
//           "x": 4,
//           "y": 2
//         }
//       ],
//       "x": 0,
//       "xmin": 1,
//       "xmax": 4
//     }
//   },
//   0.2,
//   0.7,
//   1,
//   1.2,
//   1.6,
//   2,
//   {
//     "err": "out of x-range",
//     "msg": "x[5] greater than upper limit[4]",
//     "data": {
//       "ps": [
//         {
//           "x": 1,
//           "y": 0.2
//         },
//         {
//           "x": 3,
//           "y": 1.2
//         },
//         {
//           "x": 4,
//           "y": 2
//         }
//       ],
//       "x": 5,
//       "xmin": 1,
//       "xmax": 4
//     }
//   }
// ]
Parameters:
Name Type Attributes Default Description
ps Array

輸入一維數據,格式可支援兩種,第一種各點為陣列[[x1,y1],[x2,y2],...],例如[[0.1,5],[0.2,12],...],第二種各點為物件,屬性至少要有x與y,格式為[{x:x1,y:y1},{x:x2,y:y2},...],例如[{x:0.1,y:5},{x:0.2,y:12},...],key值x與y可由opt更換

px Number | Array

輸入要內插點的數值或數值陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
mode String <optional>
''

輸入內插方法,可選'linear'、'stairs'、'blocks',預設'linear'

keyX String <optional>
'x'

輸入若數據為物件陣列,取物件x值時的key字串,預設為'x'

keyY String <optional>
'y'

輸入若數據為物件陣列,取物件y值時的key字串,預設為'y'

xMin Number <optional>

輸入若mode='stairs',更改x範圍下限值,預設為undefined

xMax Number <optional>

輸入若mode='stairs',更改x範圍上限值,預設為undefined

Returns:

回傳內插結果數值,或是無法內插時之錯誤訊息物件

Type
Number | Object

(static) isDate(v, optopt) → {Boolean}

Description:
  • 判斷是否為時間(Date)

    預設可輸入Date、可解析為時間之字串或數值,判斷是否為有效時間 若opt.onlyCheckDateObject為true,則僅以Object.prototype.toString.call(v)判斷是否為Date物件,不解析字串與數值,無效日期(Invalid Date)與跨realm(例如iframe、vm)之Date亦回傳true

    Unit Test: Github

Source:
Example
console.log(isDate('2019-01-01', { onlyCheckDateObject: true }))
// => false

console.log(isDate(new Date('2019-01-01'), { onlyCheckDateObject: true }))
// => true

console.log(isDate(new Date('x'), { onlyCheckDateObject: true }))
// => true (無效日期亦為Date物件)

let c

c = '2019-01-01T12:34:56:7891+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:7891+08:00 false Invalid Date

c = '2019-01-01T12:34:56.7891+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56.7891+08:00 true 2019-01-01T04:34:56.789Z

c = '2019-01-01T12:34:56:789+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:789+08:00 false Invalid Date

c = '2019-01-01T12:34:56.789+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56.789+08:00 true 2019-01-01T04:34:56.789Z

c = '2019-01-01T12:34:56:78+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:78+08:00 false Invalid Date

c = '2019-01-01T12:34:56.78+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56.78+08:00 true 2019-01-01T04:34:56.780Z

c = '2019-01-01T12:34:56+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56+08:00 true 2019-01-01T04:34:56.000Z

c = '2019-01-01T12:34+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34+08:00 true 2019-01-01T04:34:00.000Z

c = '2019-01-01T12+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12+08:00 false Invalid Date

c = '2019-01-01+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01+08:00 false Invalid Date

c = '2019-01+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019-01+08:00 false Invalid Date

c = '2019+08:00'
console.log(c, isDate(c), new Date(c))
// => 2019+08:00 false Invalid Date

c = '2019-01-01T12:34:56:7891Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:7891Z false Invalid Date

c = '2019-01-01T12:34:56.7891Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56.7891Z true 2019-01-01T12:34:56.789Z

c = '2019-01-01T12:34:56:789Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56.789Z true 2019-01-01T12:34:56.789Z

c = '2019-01-01T12:34:56.789Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:789Z false Invalid Date

c = '2019-01-01T12:34:56:78Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:78Z false Invalid Date

c = '2019-01-01T12:34:56.78Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56.78Z true 2019-01-01T12:34:56.780Z

c = '2019-01-01T12:34:56Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56Z true 2019-01-01T12:34:56.000Z

c = '2019-01-01T12:34Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34Z true 2019-01-01T12:34:00.000Z

c = '2019-01-01T12Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12Z false Invalid Date

c = '2019-01-01Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01Z true 2019-01-01T00:00:00.000Z

c = '2019-01Z'
console.log(c, isDate(c), new Date(c))
// => 2019-01Z true 2019-01-01T00:00:00.000Z

c = '2019Z'
console.log(c, isDate(c), new Date(c))
// => 2019Z true 2019-01-01T00:00:00.000Z

c = '2019-01-01T12:34:56:7891'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:7891 false Invalid Date

c = '2019-01-01T12:34:56.7891'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56.7891 true 2019-01-01T04:34:56.789Z

c = '2019-01-01T12:34:56:789'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:789 false Invalid Date

c = '2019-01-01T12:34:56.789'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56.789 true 2019-01-01T04:34:56.789Z

c = '2019-01-01T12:34:56:78'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:78 false Invalid Date

c = '2019-01-01T12:34:56.78'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56.78 true 2019-01-01T04:34:56.780Z

c = '2019-01-01T12:34:56'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56 true 2019-01-01T04:34:56.000Z

c = '2019-01-01T12:34'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34 true 2019-01-01T04:34:00.000Z

c = '2019-01-01T12'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12 false Invalid Date

c = '2019-01-01'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01 true 2019-01-01T00:00:00.000Z

c = '2019-01'
console.log(c, isDate(c), new Date(c))
// => 2019-01 true 2019-01-01T00:00:00.000Z

c = '2019'
console.log(c, isDate(c), new Date(c))
// => 2019 true 2019-01-01T00:00:00.000Z

c = '2019-01-01T12:34:56A'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56A false Invalid Date

c = '1256'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:789+08:00 false Invalid Date

c = '1.25'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:789+08:00 false Invalid Date

c = '-1.25'
console.log(c, isDate(c), new Date(c))
// => 2019-01-01T12:34:56:789+08:00 false Invalid Date
Parameters:
Name Type Attributes Default Description
v *

輸入任意資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onlyCheckDateObject Boolean <optional>
false

輸入是否僅判斷是否為Date物件布林值,若為true則以Object.prototype.toString.call(v)==='[object Date]'判斷,預設false

Returns:

回傳判斷布林值

Type
Boolean

(static) isDev() → {Boolean}

Description:
  • 前端判斷是否為開發階段 主要判斷location.href內是否含有localhost Unit Test: Github

Source:
Example
need test in browser

console.log(isDev())
// => true or false
Returns:

回傳是否為開發階段,非瀏覽器環境(無location)回傳false

Type
Boolean

(static) isEle(ele) → {Boolean}

Description:
  • 前端判斷元素是否存在

    Unit Test: Github

Source:
Example
need test in browser

let ele = document.querySelector('#id')
console.log(isEle(ele))
// => true or false
Parameters:
Name Type Description
ele Element

輸入DOM元素

Returns:

回傳判斷布林值

Type
Boolean

(static) isEleExistByID(id) → {Boolean}

Description:
  • 前端判斷元素id是否存在

    Unit Test: Github

Source:
Example
need test in browser

console.log(isEleExistByID('#id'))
// => true or false
Parameters:
Name Type Description
id String

輸入id字串

Returns:

回傳判斷布林值,非瀏覽器環境(無document)回傳false

Type
Boolean

(static) isEmail(email) → {Boolean}

Description:
  • 判斷是否為email格式

    採ASCII寬鬆判定:@之前為RFC 5322之dot-atom(英數與 !#$%&'*+/=?^_`{|}~- ,點只能當分隔),@之後為一層以上之主機名標籤加頂級網域,頂級網域為2碼以上純字母或xn--開頭之punycode形式,全長至多254碼、@之前至多64碼(RFC 5321)。不支援引號形式之local part、Unicode位址(須先轉punycode)、IP位址與無頂級網域者

    Unit Test: Github

Source:
Example
console.log(isEmail('abc@mail.com'))
// => true

console.log(isEmail('abc+tag@mail.info'))
// => true

console.log(isEmail('abc@a.b'))
// => false

console.log(isEmail('a..b@mail.com'))
// => false
Parameters:
Name Type Description
email String

輸入email字串

Returns:

回傳判斷布林值

Type
Boolean

(static) isErr(v) → {Boolean}

Description:
  • 判斷是否為錯誤

    Unit Test: Github

Source:
Example
console.log(isErr(new Error))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isIE() → {Boolean}

Description:
  • 前端判斷瀏覽器是否為IE

    Unit Test: Github

Source:
Example
console.log(isIE())
// => true
Returns:

回傳判斷布林值,非瀏覽器環境(無navigator)回傳false

Type
Boolean

(static) isNarrow(eleopt) → {Boolean}

Description:
  • 前端判斷是否為窄版裝置 主要判斷瀏覽器視窗寬度是否大於0與小於1024,若有傳DOM元素,則再加判斷該元素寬度是否大於0與小於700

    Unit Test: Github

Source:
Example
need test in browser

console.log(isNarrow())
// => true or false

let ele = document.querySelector('#id')
console.log(isNarrow(ele))
// => true or false
Parameters:
Name Type Attributes Description
ele Element <optional>

輸入為DOM元素,預設為不使用

Returns:

回傳是否為窄版裝置

Type
Boolean

(static) isStrHasCapital(str) → {Boolean}

Description:
  • 字串是否包含大寫英文字元

    Unit Test: Github

Source:
Example
console.log(isStrHasCapital('abc125'))
// => false

console.log(isStrHasCapital('abC125'))
// => true
Parameters:
Name Type Description
str String

輸入欲判斷的字串

Returns:

回傳是否包含大寫英文字元

Type
Boolean

(static) isStrHasLetter(str) → {Boolean}

Description:
  • 字串是否包含英文字母字元(不分大小寫)

    Unit Test: Github

Source:
Example
console.log(isStrHasLetter('12345'))
// => false

console.log(isStrHasLetter('abc125'))
// => true
Parameters:
Name Type Description
str String

輸入欲判斷的字串

Returns:

回傳是否包含英文字母字元

Type
Boolean

(static) isStrHasLowerCase(str) → {Boolean}

Description:
  • 字串是否包含小寫英文字元

    Unit Test: Github

Source:
Example
console.log(isStrHasLowerCase('ABC125'))
// => false

console.log(isStrHasLowerCase('abC125'))
// => true
Parameters:
Name Type Description
str String

輸入欲判斷的字串

Returns:

回傳是否包含小寫英文字元

Type
Boolean

(static) isStrHasNumber(str) → {Boolean}

Description:
  • 字串是否包含數字

    Unit Test: Github

Source:
Example
console.log(isStrHasNumber('abc'))
// => false

console.log(isStrHasNumber('abc125'))
// => true
Parameters:
Name Type Description
str String

輸入欲判斷的字串

Returns:

回傳是否包含數字

Type
Boolean

(static) isStrHasSpace(str) → {Boolean}

Description:
  • 字串是否包含空白字元

    Unit Test: Github

Source:
Example
console.log(isStrHasSpace('abc125'))
// => false

console.log(isStrHasSpace('abc 125'))
// => true
Parameters:
Name Type Description
str String

輸入欲判斷的字串

Returns:

回傳是否包含空白字元

Type
Boolean

(static) isStrHasSymbol(str) → {Boolean}

Description:
  • 字串是否包含特殊符號字元

    Unit Test: Github

Source:
Example
console.log(isStrHasSymbol('abc125'))
// => false

console.log(isStrHasSymbol('abc@125'))
// => true
Parameters:
Name Type Description
str String

輸入欲判斷的字串

Returns:

回傳是否包含特殊符號字元

Type
Boolean

(static) isUserIdentify(v) → {Promise}

Description:
  • 判斷是否為有效中華民國身份證

    Unit Test: Github

Source:
Example
need test in browser

isUserIdentify('A122471399')
    .then(function(){
        console.log('then')
        //code here
    })
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳Promise,resolve回傳為空代表有效,reject回傳錯誤訊息

Type
Promise

(static) isUserName(v) → {Promise}

Description:
  • 判斷是否為有效使用者名稱

    Unit Test: Github

Source:
Example
need test in browser

isUserName('我的名稱')
    .then(function() {
        console.log('then')
        //code here
    })
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳Promise,resolve回傳為空代表有效,reject回傳錯誤訊息

Type
Promise

(static) isUserPw(pw, optopt) → {Boolean}

Description:
  • 判斷是否為有效使用者密碼

    Unit Test: Github

Source:
Example
let r = null

try {
    r = isUserPw('Asdf%1234')
}
catch (err) {
    r = err.message
}
console.log(r)
// => true

try {
    r = isUserPw('246135')
}
catch (err) {
    r = err.message
}
console.log(r)
// => 密碼長度須大於等於8個字元 | 密碼須包含至少一個大寫英文字母 | 密碼須包含至少一個小寫英文字母 | 密碼須包含至少一個特殊符號

try {
    r = isUserPw('24681357')
}
catch (err) {
    r = err.message
}
console.log(r)
// => 密碼須包含至少一個大寫英文字母 | 密碼須包含至少一個小寫英文字母 | 密碼須包含至少一個特殊符號

try {
    r = isUserPw('abcdefgh')
}
catch (err) {
    r = err.message
}
console.log(r)
// => 密碼須包含至少一個大寫英文字母 | 密碼須包含至少一個數字 | 密碼須包含至少一個特殊符號

try {
    r = isUserPw('asdf1234')
}
catch (err) {
    r = err.message
}
console.log(r)
// => 密碼須包含至少一個大寫英文字母 | 密碼須包含至少一個特殊符號

try {
    r = isUserPw('123456789012345678901234567890a')
}
catch (err) {
    r = err.message
}
console.log(r)
// => 密碼長度須小於等於30個字元 | 密碼須包含至少一個大寫英文字母 | 密碼須包含至少一個特殊符號

try {
    r = isUserPw('')
}
catch (err) {
    r = err.message
}
console.log(r)
// => 密碼長度須大於等於8個字元 | 密碼須包含至少一個大寫英文字母 | 密碼須包含至少一個小寫英文字母 | 密碼須包含至少一個數字 | 密碼須包含至少一個特殊符號

try {
    r = isUserPw('abcd@1234', { useKeyForError: true, requireUppercase: false, requireLetter: true, requireDigit: true, requireSpecial: true })
}
catch (err) {
    r = err.message
}
console.log(r)
// => true

try {
    r = isUserPw('1234@5678', { useKeyForError: true, useOnlyOneError: true, requireLetter: true })
}
catch (err) {
    r = err.message
}
console.log(r)
// => keyLimRequireLetter

try {
    r = isUserPw('Abcd@1234', {
        useKeyForError: true,
        useOnlyOneError: true,
        numLenMin: 8,
        numLenMax: 16,
        requireLetter: true,
        requireDigit: true,
        requireSpecial: true,
        noSpace: true,
        forbiddenChars: ['\\'],
        commonPasswordBlacklist: ['1qaz@WSX', 'P@ssw0rd'],
        account: 'ac-admin',
        noConsecutiveCharsFromAccount: true,
        consecutiveCharsMinMatch: 3,
    })
}
catch (err) {
    r = err.message
}
console.log(r)
// => true
Parameters:
Name Type Attributes Default Description
pw String

輸入密碼字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useKeyForError Boolean <optional>
false

輸入是否回傳error key而非中文訊息布林值,預設false

useOnlyOneError Boolean <optional>
false

輸入是否遇到第一個錯誤即throw布林值,預設false

numLenMin Integer <optional>
8

輸入密碼最小長度非負整數,預設8

numLenMax Integer <optional>
30

輸入密碼最大長度非負整數,預設30

noSpace Boolean <optional>
true

輸入是否禁止空白字元布林值,預設true

onlyAscii Boolean <optional>
true

輸入是否限制密碼僅可使用ASCII可印刷字元(英文大小寫、數字、特殊符號)布林值,設為true時會擋掉中文、emoji、Unicode等非ASCII字元,預設true

requireLetter Boolean <optional>
false

輸入是否需包含英文字母(不分大小寫)布林值,預設false

requireUppercase Boolean <optional>
true

輸入是否需包含大寫英文字母布林值,預設true

requireLowercase Boolean <optional>
true

輸入是否需包含小寫英文字母布林值,預設true

requireDigit Boolean <optional>
true

輸入是否需包含數字布林值,預設true

requireSpecial Boolean <optional>
true

輸入是否需包含特殊符號布林值,預設true

forbiddenChars Array <optional>
[]

輸入禁止包含的字元陣列,預設[]

commonPasswordBlacklist Array <optional>
預設100組常見弱密碼

輸入常見弱密碼黑名單字串陣列,比對時不分大小寫,傳入空陣列可關閉,預設內建100組常見弱密碼

account String <optional>
''

輸入使用者帳號字串,用於帳號連續字元比對,預設''

noConsecutiveCharsFromAccount Boolean <optional>
false

輸入是否禁止密碼包含與帳號相同之連續字元布林值,比對時不分大小寫,預設false

consecutiveCharsMinMatch Integer <optional>
3

輸入帳號連續字元最小比對長度正整數,預設3

Returns:

回傳判斷布林值

Type
Boolean

(static) isWindow() → {Boolean}

Description:
  • 判斷是否為瀏覽器環境

    Unit Test: Github

Source:
Example
need test in browser

console.log(isWindow())
// => true or false
Returns:

回傳判斷布林值

Type
Boolean

(static) isab(v) → {Boolean}

Description:
  • 判斷是否為ArrayBuffer

    Unit Test: Github

Source:
Example
console.log(isab(new ArrayBuffer(1)))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isarr(v) → {Boolean}

Description:
  • 判斷是否為陣列

    Unit Test: Github

Source:
Example
console.log(isarr([]))
// => true

console.log(isarr([{}]))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isarr0(v) → {Boolean}

Description:
  • 判斷是否為無內容陣列

    Unit Test: Github

Source:
Example
console.log(isarr([]))
// => true

console.log(isarr([{}]))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isarr1(v) → {Boolean}

Description:
  • 判斷是否為有內容陣列,即長度至少為1之陣列

    與isearr之差異:isearr於長度為1時會額外檢查該元素是否為有效值,故['']、[null]會判為false;本函數只看長度,故皆判為true

    Unit Test: Github

Source:
Example
console.log(isarr1([]))
// => false

console.log(isarr1([{}]))
// => true

console.log(isarr1(['']))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isbigint(v) → {Boolean}

Description:
  • 判斷是否為BigInt

    BigInt為ES2020新增之原生型別,typeof值為'bigint',與Number(typeof 'number')互不相容。 由於BigInt與Number無法直接做混合算術運算(1n + 1、Math.floor(1n) 皆擲TypeError), 故本函式獨立於isnum/isnbr,提供BigInt之專屬判斷。常見場景如金融API回傳之大整數識別碼 (Binance orderId/algoId常超過Number.MAX_SAFE_INTEGER即2^53-1)。

    Unit Test: Github

Source:
Example
console.log(isbigint(123n))
// => true

console.log(isbigint(BigInt(123)))
// => true

console.log(isbigint(BigInt('9007199254740993')))
// => true

console.log(isbigint(123))
// => false

console.log(isbigint('123'))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isblob(v) → {Boolean}

Description:
  • 判斷是否為Blob或其子型別(File)

    File繼承自Blob,具備Blob全部方法,由input type=file取得者皆為File,故一併視為Blob;若須區分File請用isfile。瀏覽器與NodeJS(18以上)皆有Blob,NodeJS 20以上有File

    Unit Test: Github

Source:
Example
let bb = new Blob([new Uint8Array([66, 97, 115])])
console.log(isblob(bb))
// => true

let fl = new File([new Uint8Array([66, 97, 115])], 'a.txt')
console.log(isblob(fl))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isbol(v) → {Boolean}

Description:
  • 判斷是否為boolean

    包裝物件(new String/Number/Boolean)依內部標籤視同其型別;值判定函數(isestr、isstr0、isnan、isnum族)與cbol對包裝物件拆包後判定,其餘函數不保證,請傳入原始值

    Unit Test: Github

Source:
Example
console.log(isbol(false))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isday(v) → {Boolean}

Description:
  • 判斷是否為日時間

    Unit Test: Github

Source:
Example
console.log(isday('2019-01-01'))
// => true
Parameters:
Name Type Description
v String

輸入日時間字串

Returns:

回傳是否為日時間布林值

Type
Boolean

(static) isearr(v) → {Boolean}

Description:
  • 判斷是否為有效陣列,長度至少大於等於1,各元素至少皆為有效

    Unit Test: Github

Source:
Example
console.log(isearr([{}]))
// => false

console.log(isearr([{ a: 123 }]))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) iseobj(v) → {Boolean}

Description:
  • 判斷是否為有效物件

    Unit Test: Github

Source:
Example
console.log(iseobj({}))
// => false

console.log(iseobj({ a: 123 }))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) iser(v) → {Boolean}

Description:
  • 判斷是否為廣義無效

    Unit Test: Github

Source:
Example
console.log(iser('12a5'))
// => false

console.log(iser(''))
// => true

console.log(iser([]))
// => true

console.log(iser([{}]))
// => false

console.log(iser(['']))
// => false

console.log(iser({}))
// => true

console.log(iser(null))
// => true

console.log(iser(undefined))
// => true

console.log(iser(NaN))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isernot(v) → {Boolean}

Description:
  • 判斷是否為廣義有效

    Unit Test: Github

Source:
Example
console.log(isernot('12a5'))
// => true

console.log(isernot(''))
// => false

console.log(isernot([]))
// => false

console.log(isernot([{}]))
// => true

console.log(isernot(['']))
// => true

console.log(isernot({}))
// => false

console.log(isernot(null))
// => false

console.log(isernot(undefined))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isestr(v) → {Boolean}

Description:
  • 判斷是否為有效字串

    Unit Test: Github

Source:
Example
console.log(isestr('1.25'))
// => true

console.log(isestr(125))
// => false

console.log(isestr(''))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值,包裝物件依其原始值判定

Type
Boolean

(static) isfile(v) → {Boolean}

Description:
  • 判斷是否為File,瀏覽器與NodeJS(20以上)皆有File;File為Blob之子型別,isblob對File亦回傳true,須區分兩者時用本函數

    Unit Test: Github

Source:
Example
need test in browser

let fl = 'FILE'
console.log(isfile(fl))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isfun(v) → {Boolean}

Description:
  • 判斷是否為函數

    委派lodash之isFunction,一般函數、箭頭函數、async函數、generator函數與class皆為true;async generator函數(async function*)於lodash 4判為false,屬其已知限制

    Unit Test: Github

Source:
Example
console.log(isfun('1.25'))
// => false

console.log(isfun(function() {}))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isint(v, optopt) → {Boolean}

Description:
  • 判斷是否為整數

    Unit Test: Github

Source:
Example
console.log(isint('1.25'))
// => false

console.log(isint('125'))
// => true

console.log(isint(1.25))
// => false

console.log(isint(125))
// => true

console.log(isint(Infinity))
// => true

console.log(isint(Infinity, { useLimitSafe: true }))
// => false
Parameters:
Name Type Attributes Default Description
v *

輸入任意資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useLimitSafe Boolean <optional>
false

輸入是否限制須為安全整數布林值,若為true則超出安全整數範圍者(即Number.isSafeInteger為false,含Infinity、-Infinity與絕對值大於Number.MAX_SAFE_INTEGER者)判定為false,預設false

Returns:

回傳判斷布林值

Type
Boolean

(static) ismonth(v) → {Boolean}

Description:
  • 判斷是否為月時間

    Unit Test: Github

Source:
Example
console.log(ismonth('2019-01-01'))
// => false

console.log(ismonth('2019-01'))
// => true
Parameters:
Name Type Description
v String

輸入月時間字串

Returns:

回傳是否為月時間布林值

Type
Boolean

(static) isn0int(v) → {Boolean}

Description:
  • 判斷是否為小於等於0整數(非正整數) 非正整數包含0

    Unit Test: Github

Source:
Example
console.log(isn0int(0))
// => true

console.log(isn0int('0'))
// => true

console.log(isn0int(125))
// => false

console.log(isn0int(1.25))
// => false

console.log(isn0int('125'))
// => false

console.log(isn0int('1.25'))
// => false
console.log(isn0int(-125))
// => true

console.log(isn0int(-1.25))
// => false

console.log(isn0int('-125'))
// => true

console.log(isn0int('-1.25'))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isn0num(v) → {Boolean}

Description:
  • 判斷是否為小於等於0浮點數(非正浮點數) 非正浮點數包含0

    Unit Test: Github

Source:
Example
console.log(isn0num(0))
// => true

console.log(isn0num('0'))
// => true

console.log(isn0num(125))
// => false

console.log(isn0num(1.25))
// => false

console.log(isn0num('125'))
// => false

console.log(isn0num('1.25'))
// => false

console.log(isn0num(-125))
// => true

console.log(isn0num(-1.25))
// => true

console.log(isn0num('-125'))
// => true

console.log(isn0num('-1.25'))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isnan(v) → {Boolean}

Description:
  • 判斷是否為NaN

    Unit Test: Github

Source:
Example
console.log(isnan(NaN))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值,包裝物件依其原始值判定

Type
Boolean

(static) isnbr(v) → {Boolean}

Description:
  • 判斷是否為數字

    包裝物件(new String/Number/Boolean)依內部標籤視同其型別;值判定函數(isestr、isstr0、isnan、isnum族)與cbol對包裝物件拆包後判定,其餘函數不保證,請傳入原始值

    Unit Test: Github

Source:
Example
console.log(isnbr(1.25))
// => true

console.log(isnbr('1.25'))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isnint(v) → {Boolean}

Description:
  • 判斷是否為負整數 負整數不包含0,為小於0的整數

    Unit Test: Github

Source:
Example
console.log(isnint(0))
// => false

console.log(isnint('0'))
// => false

console.log(isnint(125))
// => false

console.log(isnint(1.25))
// => false

console.log(isnint('125'))
// => false

console.log(isnint('1.25'))
// => false
console.log(isnint(-125))
// => true

console.log(isnint(-1.25))
// => false

console.log(isnint('-125'))
// => true

console.log(isnint('-1.25'))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isnnum(v) → {Boolean}

Description:
  • 判斷是否為小於0浮點數

    Unit Test: Github

Source:
Example
console.log(isnnum(0))
// => false

console.log(isnnum('0'))
// => false

console.log(isnnum(125))
// => false

console.log(isnnum(1.25))
// => false

console.log(isnnum('125'))
// => false

console.log(isnnum('1.25'))
// => false

console.log(isnnum(-125))
// => true

console.log(isnnum(-1.25))
// => true

console.log(isnnum('-125'))
// => true

console.log(isnnum('-1.25'))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isnull(v) → {Boolean}

Description:
  • 判斷是否為null

    Unit Test: Github

Source:
Example
console.log(isnull(null))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isnum(v) → {Boolean}

Description:
  • 判斷是否為數字

    只有數字(NaN除外)與數字字串回傳true;空字串與純空白字串(含多個空白、tab、換行)回傳false,不因JS之Number(' ')為0而視為數字。數字字串之前後空白可接受(例如' 5 ',網頁輸入常見)

    注意:本函式不支援BigInt,typeof BigInt值為'bigint'而非'number'故isnbr判定為false。 BigInt與Number在JS為互不相容的算術域(1n + 1、Math.floor(1n) 皆擲TypeError), 而isnum的隱含契約是「通過後可做Number算術運算」,wsemi內逾60處callsite依賴此契約 (如arrMax/arrMin/round/randomRange等),若放寬isnum認BigInt將導致這些callsite執行期錯誤。 此設計與lodash _.isNumber 將BigInt排除的處理一致。BigInt請另作獨立判斷。

    Unit Test: Github

Source:
Example
console.log(isnum(0))
// => true

console.log(isnum(1.25))
// => true

console.log(isnum('-125'))
// => true

console.log(isnum(' '))
// => false (純空白字串不為數字)

console.log(isnum(123n))
// => false (BigInt不被視為數字, 詳見上方說明)
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isobj(v) → {Boolean}

Description:
  • 判斷是否為物件

    Unit Test: Github

Source:
Example
console.log(isobj({}))
// => true

console.log(isobj({ a: 123 }))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isobj0(v) → {Boolean}

Description:
  • 判斷是否為空物件

    Unit Test: Github

Source:
Example
console.log(isobj0({}))
// => true

console.log(isobj0({ a: 123 }))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isp0int(v, optopt) → {Boolean}

Description:
  • 判斷是否為大於等於0整數(非負整數) 非負整數包含0

    Unit Test: Github

Source:
Example
console.log(isp0int(0))
// => true

console.log(isp0int('0'))
// => true

console.log(isp0int(125))
// => true

console.log(isp0int(1.25))
// => false

console.log(isp0int('125'))
// => true

console.log(isp0int('1.25'))
// => false
console.log(isp0int(-125))
// => false

console.log(isp0int(-1.25))
// => false

console.log(isp0int('-125'))
// => false

console.log(isp0int('-1.25'))
// => false
Parameters:
Name Type Attributes Default Description
v *

輸入任意資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useLimitSafe Boolean <optional>
false

輸入是否限制須為安全整數布林值,原樣傳遞給isint,若為true則超出安全整數範圍者(即Number.isSafeInteger為false,含Infinity、-Infinity與絕對值大於Number.MAX_SAFE_INTEGER者)判定為false,預設false

Returns:

回傳判斷布林值

Type
Boolean

(static) isp0num(v) → {Boolean}

Description:
  • 判斷是否為大於等於0浮點數(非負浮點數) 非負浮點數包含0

    Unit Test: Github

Source:
Example
console.log(isp0num(0))
// => true

console.log(isp0num('0'))
// => true

console.log(isp0num(125))
// => true

console.log(isp0num(1.25))
// => true

console.log(isp0num('125'))
// => true

console.log(isp0num('1.25'))
// => true
console.log(isp0num(-125))
// => false

console.log(isp0num(-1.25))
// => false

console.log(isp0num('-125'))
// => false

console.log(isp0num('-1.25'))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) ispint(v, optopt) → {Boolean}

Description:
  • 判斷是否為正整數 正整數不包含0,為大於0的整數

    Unit Test: Github

Source:
Example
console.log(ispint(0))
// => false

console.log(ispint('0'))
// => false

console.log(ispint(125))
// => true

console.log(ispint(1.25))
// => false

console.log(ispint('125'))
// => true

console.log(ispint('1.25'))
// => false
console.log(ispint(-125))
// => false

console.log(ispint(-1.25))
// => false

console.log(ispint('-125'))
// => false

console.log(ispint('-1.25'))
// => false
Parameters:
Name Type Attributes Default Description
v *

輸入任意資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useLimitSafe Boolean <optional>
false

輸入是否限制須為安全整數布林值,原樣傳遞給isint,若為true則超出安全整數範圍者(即Number.isSafeInteger為false,含Infinity、-Infinity與絕對值大於Number.MAX_SAFE_INTEGER者)判定為false,預設false

Returns:

回傳判斷布林值

Type
Boolean

(static) ispm(v) → {Boolean}

Description:
  • 判斷是否為Promise

    Unit Test: Github

Source:
Example
console.log(ispm('1.25'))
// => false

console.log(ispm(new Promise(function() {})))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值,任何輸入皆回傳布林值,讀取即拋錯之值(如已撤銷之Proxy、會拋錯之getter)回傳false

Type
Boolean

(static) ispnum(v) → {Boolean}

Description:
  • 判斷是否為大於0浮點數

    Unit Test: Github

Source:
Example
console.log(ispnum(0))
// => false

console.log(ispnum('0'))
// => false

console.log(ispnum(125))
// => true

console.log(ispnum(1.25))
// => true

console.log(ispnum('125'))
// => true

console.log(ispnum('1.25'))
// => true
console.log(ispnum(-125))
// => false

console.log(ispnum(-1.25))
// => false

console.log(ispnum('-125'))
// => false

console.log(ispnum('-1.25'))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isstr(v) → {Boolean}

Description:
  • 判斷是否為字串

    包裝物件(new String/Number/Boolean)依內部標籤視同其型別;值判定函數(isestr、isstr0、isnan、isnum族)與cbol對包裝物件拆包後判定,其餘函數不保證,請傳入原始值

    Unit Test: Github

Source:
Example
console.log(isstr(0))
// => false

console.log(isstr('0'))
// => true

console.log(isstr(''))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isstr0(v) → {Boolean}

Description:
  • 判斷是否為空字串

    Unit Test: Github

Source:
Example
console.log(isstr0(''))
// => true

console.log(isstr0('0'))
// => false

console.log(isstr0('abc125'))
// => false
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值,包裝物件依其原始值判定

Type
Boolean

(static) istime(v) → {Boolean}

Description:
  • 判斷是否為秒時間

    Unit Test: Github

Source:
Example
console.log(istime('2019-01-01T12:34:56:789'))
// => false

console.log(istime('2019-01-01T12:34:56'))
// => true

console.log(istime('2019-01-01'))
// => false
Parameters:
Name Type Description
v String

輸入秒時間字串,不含時區

Returns:

回傳是否為秒時間布林值

Type
Boolean

(static) istimeTZ(v) → {Boolean}

Description:
  • 判斷是否為秒時間,含時區

    Unit Test: Github

Source:
Example
console.log(istimeTZ('2019-01-01T12:34:56:789+08:00'))
// => false

console.log(istimeTZ('2019-01-01T12:34:56+08:00'))
// => true

console.log(istimeTZ('2019-01-01T12:34:56Z'))
// => true

console.log(istimeTZ('2019-01-01T12:34:56A'))
// => false

console.log(istimeTZ('2019-01-01'))
// => false
Parameters:
Name Type Description
v String

輸入秒時間字串,含時區

Returns:

回傳是否為秒時間布林值

Type
Boolean

(static) istimems(v) → {Boolean}

Description:
  • 判斷是否為秒時間,不含時區

    Unit Test: Github

Source:
Example
console.log(istimems('2019-01-01T12:34:56:789.321'))
// => false

console.log(istimems('2019-01-01T12:34:56.321'))
// => true

console.log(istimems('2019-01-01T12:34:56'))
// => false

console.log(istimems('2019-01-01'))
// => false
Parameters:
Name Type Description
v String

輸入毫秒時間字串,不含時區

Returns:

回傳是否為毫秒時間布林值

Type
Boolean

(static) istimemsTZ(v) → {Boolean}

Description:
  • 判斷是否為毫秒時間,含時區

    Unit Test: Github

Source:
Example
console.log(istimemsTZ('2019-01-01T12:34:56.789+08:00'))
// => true

console.log(istimemsTZ('2019-01-01T12:34:56.789Z'))
// => true

console.log(istimemsTZ('2019-01-01T12:34:56:789+08:00'))
// => false

console.log(istimemsTZ('2019-01-01T12:34:56+08:00'))
// => false

console.log(istimemsTZ('2019-01-01'))
// => false
Parameters:
Name Type Description
v String

輸入毫秒時間字串,含時區

Returns:

回傳是否為毫秒時間布林值

Type
Boolean

(static) isu16arr(v) → {Boolean}

Description:
  • 判斷是否為Uint16Array

    Unit Test: Github

Source:
Example
console.log(isu16arr(new Uint16Array(1)))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isu8arr(v) → {Boolean}

Description:
  • 判斷是否為Uint8Array

    Unit Test: Github

Source:
Example
console.log(isu8arr(new Uint8Array(1)))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) isundefined(v) → {Boolean}

Description:
  • 判斷是否為undefined

    Unit Test: Github

Source:
Example
console.log(isundefined(undefined))
// => true
Parameters:
Name Type Description
v *

輸入任意資料

Returns:

回傳判斷布林值

Type
Boolean

(static) j2o(v, optopt) → {*}

Description:
  • json文字轉任意資料

    Unit Test: Github

Source:
Example
console.log(j2o('[1,"3","abc"]'))
// => [1, '3', 'abc']

console.log(j2o('{"a":12.34,"b":"abc"}'))
// => { a: 12.34, b: 'abc' }
Parameters:
Name Type Attributes Default Description
v String

輸入json格式字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳任意資料,輸入非有效字串或解析失敗時回傳空物件;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
*

(static) judge(x1, op, x2, optopt) → {Number}

Description:
  • 使用指定條件判斷兩數字成立與否

    Unit Test: Github

Source:
Example
let x1
let op
let x2
let b

x1 = 0
op = '>='
x2 = 0
b = judge(x1, op, x2, { withPreciseNum: false })
console.log(`${x1}, '${op}', ${x2}`, b)
// => 0, '>=', 0 true

x1 = 1
op = '>='
x2 = 1
b = judge(x1, op, x2, { withPreciseNum: false })
console.log(`${x1}, '${op}', ${x2}`, b)
// => 1, '>=', 1 true

//浮點數誤差
x1 = 0.3
op = '>='
x2 = 0.1 + 0.2
b = judge(x1, op, x2, { withPreciseNum: false })
console.log(`${x1}, '${op}', ${x2}`, b)
// => 0.3, '>=', 0.30000000000000004 false

x1 = 0.3
op = '>='
x2 = 0.1 + 0.2
b = judge(x1, op, x2, { withPreciseNum: true }) //default
console.log(`${x1}, '${op}', ${x2}`, b)
// => 0.3, '>=', 0.30000000000000004 true

x1 = 0.3
op = '>='
x2 = Decimal.sum(0.1, 0.2).toString()
b = judge(x1, op, x2, { withPreciseNum: false })
console.log(`${x1}, '${op}', ${x2}`, b)
// => 0.3, '>=', 0.3 true

x1 = Decimal.sum(0.1, 0.2).toString()
op = '>='
x2 = 0.3
b = judge(x1, op, x2, { withPreciseNum: false })
console.log(`${x1}, '${op}', ${x2}`, b)
// => 0.3, '>=', 0.3 true

//加法順序會導致不同的誤差
x1 = 4.67 + 7.12 + 94.4
op = '>='
x2 = 94.4 + 7.12 + 4.67
b = judge(x1, op, x2, { withPreciseNum: false })
console.log(`${x1}, '${op}', ${x2}`, b)
// => 106.19, '>=', 106.19000000000001 false

x1 = 4.67 + 7.12 + 94.4
op = '>='
x2 = 94.4 + 7.12 + 4.67
b = judge(x1, op, x2, { withPreciseNum: true }) //default
console.log(`${x1}, '${op}', ${x2}`, b)
// => 106.19, '>=', 106.19000000000001 true

x1 = Decimal.sum(4.67, 7.12, 94.4).toString()
op = '>='
x2 = Decimal.sum(94.4, 7.12, 4.67).toString()
b = judge(x1, op, x2, { withPreciseNum: false })
console.log(`${x1}, '${op}', ${x2}`, b)
// => 106.19, '>=', 106.19 true
Parameters:
Name Type Attributes Default Description
x1 String | Number

輸入數字字串或數字,若要精準判識得要用字串

op String

輸入操作符號字串,可使用'==='、'!=='、'>='、'<='、'>'、'<'

x2 NuString | Number

輸入數字字串或數字,若要精準判識得要用字串

opt Object <optional>
{}

輸設定物件,預設{}

Properties
Name Type Attributes Default Description
withPreciseNum Boolean <optional>
true

輸是否自動轉原始精度之數字字串布林值,預設true

Returns:

回傳判斷布林值

Type
Number

(static) keysmat2ltdt(keys, mat, optopt) → {Array|Object}

Description:
  • 由keys與二維陣列mdata轉ltdt

    Unit Test: Github

Source:
Example
console.log(keysmat2ltdt(['a', 'b'], [[12, 34.56], ['123', 'xyz']]))
// => [{ a: 12, b: 34.56 }, { a: '123', b: 'xyz' }]
Parameters:
Name Type Attributes Default Description
keys Array

輸入字串陣列

mat Array

輸入資料陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳物件陣列,keys或mat無效時回傳空陣列;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Array | Object

(static) lowerFind(copy, opt) → {Object|null}

Description:
  • w-orm系列條件(find)之方言中立IR(中間結構)轉換,SQL族後端共用之lowering

    輸入為cvOrmFind回傳之全新null原型深複本,輸出為全新null原型之節點樹(IR),無條件回傳null;本函數只做語意轉譯,不檢核形狀(形狀檢核由cvOrmFind擁有;輸入須為其複本,非複本之輸入不保證節點形狀),方言差異(欄名檢核、綁定參數上限、布林字面值、恆假寫法、參數化與引用方式)由呼叫端以opt或各後端自有之發射器承擔。契約案例表見wsemi/src/_lowerFindCases.mjs,IR快照見wsemi/src/_lowerFindSnapshots.mjs。

    1. IR節點只有六種:and、or(n元,a至少2項,單項已展開、同型已攤平、零項不產生;不因方言上限限制寬度,重新分組由發射器處理);cmp(c, op, v, p,op為eq ne gt gte lt lte,v為字串、有限數字或Date);bool(同cmp但v為布林,語意為SQL三值,NULL列不命中任何布林比較含ne);null(c, not, p,IS [NOT] NULL);in(c, vs, not, p,vs為純量陣列且不含null,not為false且vs為空代表恆假,結果集為空,發射器是否引用欄位自決,不得影響結果集)。c為欄名,p為葉節點之來源路徑(如find.$or[0].age.$gt),僅供診斷與後續映射遍回報invalid find,不參與語意(快取鍵須忽略)。

    2. 語意(SQL族):{ 欄: null }與$eq:null為null節點,$ne:null為null not節點,其餘$ne與$nin為三值邏輯(不命中NULL列),同欄多運算子與多欄鍵為隱含AND,$and/$or為and/or,單項and/or展開為該項。

    3. opt.maxParams必填(不限者傳Infinity,核心不預設任何方言上限),計數契約為cmp計1、in每元素計1、bool與null與空in計0,超過即拋錯,路徑指向越限處;opt.checkColumn(name)為方言專屬欄名檢核,回傳空字串表通過,其餘為拒收原因,僅對欄名鍵呼叫,非函數時視為全過。opt以屬性存取讀取自有可列舉屬性(自有存取器會被呼叫;繼承值與不可列舉屬性不生效),opt為呼叫端之程式設定而非輸入資料,不防護opt自身之存取器。

    4. 所有輸出節點為null原型,陣列以defineProperty附加(不用push與索引指派),不受Array.prototype數字索引setter與Object.prototype汙染影響;純JS,無任何import。

    錯誤一律為同步拋出之Error('invalid find: 原因 at [路徑]'),與cvOrmFind同格式,不回顯值;opt.maxParams缺漏或非法拋Error('lowerFind: opt.maxParams is required (use Infinity for no limit)')。

    Unit Test: Github

Source:
Example
console.log(lowerFind(cvOrmFind({}), { maxParams: 100 }))
// => null

let ir = lowerFind(cvOrmFind({ age: { $gt: 20 }, $or: [{ g: 'A' }, { g: null }] }), { maxParams: 100 })
console.log(JSON.stringify(ir))
// => {"t":"and","a":[{"t":"cmp","c":"age","op":"gt","v":20,"p":"find.age.$gt"},{"t":"or","a":[{"t":"cmp","c":"g","op":"eq","v":"A","p":"find.$or[0].g"},{"t":"null","c":"g","not":false,"p":"find.$or[1].g"}]}]}

console.log(JSON.stringify(lowerFind(cvOrmFind({ n: { $in: [] }, b: true }), { maxParams: 100 })))
// => {"t":"and","a":[{"t":"in","c":"n","vs":[],"not":false,"p":"find.n.$in"},{"t":"bool","c":"b","op":"eq","v":true,"p":"find.b"}]}

try {
    lowerFind(cvOrmFind({ a: { $in: [1, 2, 3] } }), { maxParams: 2 })
}
catch (err) {
    console.log(err.message)
}
// => invalid find: too many bound parameters (> 2) at [find.a.$in[2]]
Parameters:
Name Type Description
copy Object

輸入cvOrmFind回傳之複本物件,無自有鍵代表無條件

opt Object

輸入設定物件

Properties
Name Type Attributes Description
maxParams Number

輸入單一語句綁定參數上限正數,不限者傳Infinity,必填

checkColumn function <optional>

輸入欄名檢核函數,傳入欄名字串,回傳空字串表通過,其餘為拒收原因字串,預設全過

Throws:

越限或欄名不合者拋出Error('invalid find: 原因 at [路徑]'),opt.maxParams缺漏者拋出Error('lowerFind: opt.maxParams is required (use Infinity for no limit)')

Type
Error
Returns:

回傳IR根節點(null原型),無條件回傳null

Type
Object | null

(static) lowerFindEq(c, v) → {Object}

Description:
  • 單欄等值條件之IR節點(主鍵條件用)

    回傳與lowerFind同形之null原型節點:布林值為bool節點(op為eq),其餘為cmp節點(op為eq),p為find.欄名(欄名限30字元並以?取代控制字元)。輸入值不再檢核(含±Infinity,float8主鍵可為±Infinity),須已由呼叫端之主鍵檢核擔保為字串、數字、布林或Date;Date原樣保留(同一實例)。與同一值經lowerFind所得之IR相同,故selectByPk與select可共用快取鍵。

    Unit Test: Github

Source:
Example
console.log(JSON.stringify(lowerFindEq('id', 'k1')))
// => {"t":"cmp","c":"id","op":"eq","v":"k1","p":"find.id"}

console.log(JSON.stringify(lowerFindEq('flag', true)))
// => {"t":"bool","c":"flag","op":"eq","v":true,"p":"find.flag"}
Parameters:
Name Type Description
c String

輸入欄名字串(已檢核)

v String | Number | Boolean | Date

輸入主鍵值(已檢核)

Returns:

回傳IR節點(null原型)

Type
Object

(static) ltdt2vrtsable(ltdt, mergerowkeysopt, optopt) → {Array|Object}

Description:
  • 由物件陣列ltdt轉陣列vrtable(使用value,style,rowspan儲存)

    Unit Test: Github

Source:
Example
console.log(ltdt2vrtsable([{"a":{"value":"123","style":{}},"b":{"value":34.56,"style":{}}},{"a":{"value":"123","style":{}},"b":{"value":"xyz","style":{}}}], ['a']))
// => [{"a":{"rowspan":2,"value":"123","style":{}},"b":{"rowspan":1,"value":34.56,"style":{}}},{"a":{"rowspan":null,"value":"123","style":{}},"b":{"rowspan":1,"value":"xyz","style":{}}}]
Parameters:
Name Type Attributes Default Description
ltdt Array

輸入物件陣列

mergerowkeys Array <optional>
[]

輸入需合併列的關鍵字keys,為字串陣列,預設為空陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳物件陣列,ltdt或mergerowkeys無效、或ltdt任一元素非有效物件時回傳空陣列;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Array | Object

(static) ltdtDiff(ltdtOld, ltdtNew, optopt) → {Object}

Description:
  • 比對新舊物件陣列差異處

    Unit Test: Github

Source:
Example
let r
let ltdtOld
let ltdtNew

if (true) {
    ltdtOld = [
        { 'cn': '1', 'v1': 'a', 'v2': '0.506002098' },
        { 'cn': '2', 'v1': 'b', 'v2': '0.506002098' },
    ]
    ltdtNew = [
        { 'cn': 't1', 'v1': 'ta', 'v2': '0.506002098' },
        { 'cn': 't2', 'v1': 'tb', 'v2': '0.506002098' },
    ]
    r = ltdtDiff(ltdtOld, ltdtNew)
    console.log('diff', r.diff)
    // => diff [
    //   {
    //     count: 2,
    //     added: false,
    //     removed: true,
    //     value: '0∶1⟋1∶a⟋2∶0.506002098⟋\n0∶2⟋1∶b⟋2∶0.506002098⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: true,
    //     removed: false,
    //     value: '0∶t1⟋1∶ta⟋2∶0.506002098⟋\n0∶t2⟋1∶tb⟋2∶0.506002098⟋\n'
    //   }
    // ]
    for (let i = 0; i < r.dfs.length; i++) {
        let df = r.dfs[i]
        console.log(i, 'df', df)
    }
    // => 0 df [
    //   cn: { p: 'modify', k: 'cn', vo: '1', vn: 't1' },
    //   v1: { p: 'modify', k: 'v1', vo: 'a', vn: 'ta' },
    //   v2: { p: '', k: 'v2', vo: '0.506002098', vn: '' }
    // }
    // 1 df {
    //   cn: { p: 'modify', k: 'cn', vo: '2', vn: 't2' },
    //   v1: { p: 'modify', k: 'v1', vo: 'b', vn: 'tb' },
    //   v2: { p: '', k: 'v2', vo: '0.506002098', vn: '' }
    // }
}

if (true) {
    ltdtOld = [
        { 'cn': '1', 'v1': '', 'v3': '0.506002098' },
    ]
    ltdtNew = [
        { 'cn': '1', 'v2': '', 'v3': '0.506002098' },
    ]
    r = ltdtDiff(ltdtOld, ltdtNew)
    console.log('diff', r.diff)
    // => diff [
    //   {
    //     count: 1,
    //     added: false,
    //     removed: true,
    //     value: '0∶1⟋1∶⟋2∶0.506002098⟋3∶∗empty∗⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶1⟋1∶∗empty∗⟋2∶0.506002098⟋3∶⟋\n'
    //   }
    // ]
    for (let i = 0; i < r.dfs.length; i++) {
        let df = r.dfs[i]
        console.log(i, 'df', df)
    }
    // => 0 df [
    //   cn: { p: '', k: 'cn', vo: '1', vn: '' },
    //   v1: { p: 'modify', k: 'v1', vo: '', vn: '∗empty∗' },
    //   v3: { p: '', k: 'v3', vo: '0.506002098', vn: '' },
    //   v2: { p: 'modify', k: 'v2', vo: '∗empty∗', vn: '' }
    // }
}

if (true) {
    ltdtOld = [
        { 'hid': 'BH-01', 'sid': 'S-01', 'depth': '1-2', 'rsat': '18' },
        { 'hid': 'BH-01', 'sid': 'S-02', 'depth': '5-6', 'rsat': '17.5' },
        { 'hid': 'BH-01', 'sid': 'S-03', 'depth': '7-8', 'rsat': '17' },
    ]
    ltdtNew = [
        { 'hid': 'BH-01', 'sid': 'S-01', 'depth': '1-2', 'rsat': '18' },
        { 'hid': 'BH-01', 'sid': 'S-03', 'depth': '7-8', 'rsat': '17' },
        { 'hid': 'BH-01', 'sid': 'S-04', 'depth': '11-12', 'rsat': '19.5' },
    ]
    r = ltdtDiff(ltdtOld, ltdtNew)
    console.log('diff', r.diff)
    // => diff [
    //   {
    //     count: 1,
    //     added: false,
    //     removed: false,
    //     value: '0∶BH-01⟋1∶S-01⟋2∶1-2⟋3∶18⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: true,
    //     value: '0∶BH-01⟋1∶S-02⟋2∶5-6⟋3∶17.5⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: false,
    //     value: '0∶BH-01⟋1∶S-03⟋2∶7-8⟋3∶17⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶BH-01⟋1∶S-04⟋2∶11-12⟋3∶19.5⟋\n'
    //   }
    // ]
    for (let i = 0; i < r.dfs.length; i++) {
        let df = r.dfs[i]
        console.log(i, 'df', df)
    }
    // => 0 df {
    //   hid: { p: '', k: 'hid', vo: 'BH-01', vn: '' },
    //   sid: { p: '', k: 'sid', vo: 'S-01', vn: '' },
    //   depth: { p: '', k: 'depth', vo: '1-2', vn: '' },
    //   rsat: { p: '', k: 'rsat', vo: '18', vn: '' }
    // }
    // 1 df {
    //   hid: { p: 'remove', k: 'hid', vo: 'BH-01', vn: '' },
    //   sid: { p: 'remove', k: 'sid', vo: 'S-02', vn: '' },
    //   depth: { p: 'remove', k: 'depth', vo: '5-6', vn: '' },
    //   rsat: { p: 'remove', k: 'rsat', vo: '17.5', vn: '' }
    // }
    // 2 df {
    //   hid: { p: '', k: 'hid', vo: 'BH-01', vn: '' },
    //   sid: { p: '', k: 'sid', vo: 'S-03', vn: '' },
    //   depth: { p: '', k: 'depth', vo: '7-8', vn: '' },
    //   rsat: { p: '', k: 'rsat', vo: '17', vn: '' }
    // }
    // 3 df {
    //   hid: { p: 'add', k: 'hid', vo: 'BH-01', vn: '' },
    //   sid: { p: 'add', k: 'sid', vo: 'S-04', vn: '' },
    //   depth: { p: 'add', k: 'depth', vo: '11-12', vn: '' },
    //   rsat: { p: 'add', k: 'rsat', vo: '19.5', vn: '' }
    // }
}

if (true) {
    ltdtOld = [
        { 'testId': 'GeneralPhysicalProperties', 'holeId': 'BH-1', 'sampleId': 'S-1', 'depth': '0.9-1.2', 'unitWeight': 3.53, 'waterContent': 76.7, 'n': 7.672198498621583, 'Iv': 7.672198498621583 },
        { 'testId': 'GeneralPhysicalProperties', 'holeId': 'BH-1', 'sampleId': 'S-2', 'depth': '2.1-2.3', 'unitWeight': 3.83, 'waterContent': 91.3, 'n': 9.126731706783175, 'Iv': 9.126731706783175 },
        { 'testId': 'GeneralPhysicalProperties', 'holeId': 'BH-1', 'sampleId': 'S-3', 'depth': '4.4-4.6', 'unitWeight': 2.51, 'waterContent': 25.5, 'n': 2.552068908698857, 'Iv': 2.552068908698857 }
    ]
    ltdtNew = [
        { 'testId': 'GeneralPhysicalProperties', 'holeId': 'BH-1', 'sampleId': 'S-1A', 'depth': '0.9-1.2', 'unitWeight': 3.67, 'waterContent': 83.5, 'n': 8.347251752857119, 'Iv': 8.347251752857119 },
        { 'testId': 'GeneralPhysicalProperties', 'holeId': 'BH-1', 'sampleId': 'S-3', 'depth': '4.4-4.6', 'unitWeight': 2.51, 'waterContent': 25.5, 'n': 2.552068908698857, 'Iv': 2.552068908698857 },
        { 'testId': 'GeneralPhysicalProperties', 'holeId': 'BH-1', 'sampleId': 'S-4', 'depth': '5.1-5.3', 'unitWeight': 3.13, 'waterContent': 56.6, 'n': 5.655150997918099, 'Iv': 5.655150997918099 }
    ]
    r = ltdtDiff(ltdtOld, ltdtNew)
    console.log('diff', r.diff)
    // => diff [
    //   {
    //     count: 2,
    //     added: false,
    //     removed: true,
    //     value: '0∶GeneralPhysicalProperties⟋1∶BH-1⟋2∶S-1⟋3∶0.9-1.2⟋4∶3.53⟋5∶76.7⟋6∶7.672198498621583⟋7∶7.672198498621583⟋\n' +
    //       '0∶GeneralPhysicalProperties⟋1∶BH-1⟋2∶S-2⟋3∶2.1-2.3⟋4∶3.83⟋5∶91.3⟋6∶9.126731706783175⟋7∶9.126731706783175⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶GeneralPhysicalProperties⟋1∶BH-1⟋2∶S-1A⟋3∶0.9-1.2⟋4∶3.67⟋5∶83.5⟋6∶8.347251752857119⟋7∶8.347251752857119⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: false,
    //     value: '0∶GeneralPhysicalProperties⟋1∶BH-1⟋2∶S-3⟋3∶4.4-4.6⟋4∶2.51⟋5∶25.5⟋6∶2.552068908698857⟋7∶2.552068908698857⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶GeneralPhysicalProperties⟋1∶BH-1⟋2∶S-4⟋3∶5.1-5.3⟋4∶3.13⟋5∶56.6⟋6∶5.655150997918099⟋7∶5.655150997918099⟋\n'
    //   }
    // ]
    for (let i = 0; i < r.dfs.length; i++) {
        let df = r.dfs[i]
        console.log(i, 'df', df)
    }
    // => 0 df {
    //   testId: { p: '', k: 'testId', vo: 'GeneralPhysicalProperties', vn: '' },
    //   holeId: { p: '', k: 'holeId', vo: 'BH-1', vn: '' },
    //   sampleId: { p: 'modify', k: 'sampleId', vo: 'S-1', vn: 'S-1A' },
    //   depth: { p: '', k: 'depth', vo: '0.9-1.2', vn: '' },
    //   unitWeight: { p: 'modify', k: 'unitWeight', vo: '3.53', vn: '3.67' },
    //   waterContent: { p: 'modify', k: 'waterContent', vo: '76.7', vn: '83.5' },
    //   n: {
    //     p: 'modify',
    //     k: 'n',
    //     vo: '7.672198498621583',
    //     vn: '8.347251752857119'
    //   },
    //   Iv: {
    //     p: 'modify',
    //     k: 'Iv',
    //     vo: '7.672198498621583',
    //     vn: '8.347251752857119'
    //   }
    // }
    // 1 df {
    //   testId: { p: 'remove', k: 'testId', vo: 'GeneralPhysicalProperties', vn: '' },
    //   holeId: { p: 'remove', k: 'holeId', vo: 'BH-1', vn: '' },
    //   sampleId: { p: 'remove', k: 'sampleId', vo: 'S-2', vn: '' },
    //   depth: { p: 'remove', k: 'depth', vo: '2.1-2.3', vn: '' },
    //   unitWeight: { p: 'remove', k: 'unitWeight', vo: '3.83', vn: '' },
    //   waterContent: { p: 'remove', k: 'waterContent', vo: '91.3', vn: '' },
    //   n: { p: 'remove', k: 'n', vo: '9.126731706783175', vn: '' },
    //   Iv: { p: 'remove', k: 'Iv', vo: '9.126731706783175', vn: '' }
    // }
    // 2 df {
    //   testId: { p: '', k: 'testId', vo: 'GeneralPhysicalProperties', vn: '' },
    //   holeId: { p: '', k: 'holeId', vo: 'BH-1', vn: '' },
    //   sampleId: { p: '', k: 'sampleId', vo: 'S-3', vn: '' },
    //   depth: { p: '', k: 'depth', vo: '4.4-4.6', vn: '' },
    //   unitWeight: { p: '', k: 'unitWeight', vo: '2.51', vn: '' },
    //   waterContent: { p: '', k: 'waterContent', vo: '25.5', vn: '' },
    //   n: { p: '', k: 'n', vo: '2.552068908698857', vn: '' },
    //   Iv: { p: '', k: 'Iv', vo: '2.552068908698857', vn: '' }
    // }
    // 3 df {
    //   testId: { p: 'add', k: 'testId', vo: 'GeneralPhysicalProperties', vn: '' },
    //   holeId: { p: 'add', k: 'holeId', vo: 'BH-1', vn: '' },
    //   sampleId: { p: 'add', k: 'sampleId', vo: 'S-4', vn: '' },
    //   depth: { p: 'add', k: 'depth', vo: '5.1-5.3', vn: '' },
    //   unitWeight: { p: 'add', k: 'unitWeight', vo: '3.13', vn: '' },
    //   waterContent: { p: 'add', k: 'waterContent', vo: '56.6', vn: '' },
    //   n: { p: 'add', k: 'n', vo: '5.655150997918099', vn: '' },
    //   Iv: { p: 'add', k: 'Iv', vo: '5.655150997918099', vn: '' }
    // }
}

if (true) {
    ltdtOld = [
        { 'cn': '1', 'v1': '0.791303871', 'v2': '0.716898185', 'v3': '0.506002098', 'v4': '0.137888903', 'v5': '', 'v6': '0.626724085' },
        { 'cn': '2', 'v1': '0.839882385', 'v2': '0.663059856', 'v3': '0.49047221', 'v4': '0.395763265', 'v5': '0.567412025', 'v6': '0.866151835' },
        { 'cn': '3', 'v1': '0.475514539', 'v2': '0.969205779', 'v3': '0.711250309', 'v4': '0.153847069', 'v5': '0.304927473', 'v6': '0.410092395' },
        { 'cn': '4', 'v1': '0.486179086', 'v2': '0.481023842', 'v3': '0.467410582', 'v4': '0.42602231', 'v5': '', 'v6': '0.849701641' },
        { 'cn': '5', 'v1': '0.697242433', 'v2': '0.67532802', 'v3': '0.174644416', 'v4': '0.045652267', 'v5': '', 'v6': '0.397104668' },
        { 'cn': '6', 'v1': '0.259252779', 'v2': '0.566177431', 'v3': '0.679637706', 'v4': '0.377814487', 'v5': '', 'v6': '0.400248119' },
        { 'cn': '7', 'v1': '0.263793391', 'v2': '0.167895215', 'v3': '0.794808602', 'v4': '0.107070584', 'v5': '', 'v6': '0.011822872' },
        { 'cn': '8', 'v1': '0.360426795', 'v2': '0.014346373', 'v3': '0.000469616', 'v4': '0.4082693', 'v5': '', 'v6': '0.913806611' },
        { 'cn': '9', 'v1': '0.167996664', 'v2': '0.711054429', 'v3': '0.363177921', 'v4': '0.206849994', 'v5': '', 'v6': '0.636855344' },
        { 'cn': '10', 'v1': '0.324665077', 'v2': '0.973218005', 'v3': '0.883927423', 'v4': '0.176906125', 'v5': '', 'v6': '0.20087887' },
        { 'cn': '11', 'v1': '', 'v2': '', 'v3': '', 'v4': '', 'v5': '', 'v6': '' },
        { 'cn': '12', 'v1': '', 'v2': '', 'v3': '', 'v4': '', 'v5': '', 'v6': '' },
        { 'cn': '13', 'v1': '0.984003751', 'v2': '0.32549507', 'v3': '0.987090751', 'v4': '0.192745589', 'v5': '', 'v6': '0.735133561' },
        { 'cn': '14', 'v1': '0.083431884', 'v2': '0.565146092', 'v3': '0.935388666', 'v4': '0.637675154', 'v5': '', 'v6': '0.523815661' }
    ]
    ltdtNew = [
        { 'cn': '1', 'v1': '0.791303871', 'v2': '0.716898185', 'v3': '0.506002098', 'v4': '0.137888903', 'v5': '', 'v6': '0.626724085' },
        { 'cn': '2', 'v1': '0.839882385', 'v2': '0.663059856', 'v3': '0.49047221', 'v4': '0.395763265', 'v5': '', 'v6': '0.866151835' },
        { 'cn': '3', 'v1': '0.475514539', 'v2': '0.969205779', 'v3': '0.711250309', 'v4': '0.153847069', 'v5': '0.304927473', 'v6': '0.410092395' },
        { 'cn': '4', 'v1': '0.486179086', 'v2': '0.481023842', 'v3': '0.467410582', 'v4': '0.42602231', 'v5': '', 'v6': '0.849701641' },
        { 'cn': '5', 'v1': '0.169724243', 'v2': '0.67532802', 'v3': '0.174644416', 'v4': '0.045652267', 'v5': '', 'v6': '0.397104668' },
        { 'cn': '7', 'v1': '0.263793391', 'v2': '0.167895215', 'v3': '0.794808602', 'v4': '0.107070584', 'v5': '', 'v6': '0.011822872' },
        { 'cn': '8', 'v1': '0.360426795', 'v2': '0.014346373', 'v3': '0.000469616', 'v4': '0.4082693', 'v5': '', 'v6': '0.913806611' },
        { 'cn': '9', 'v1': '0.167996664', 'v2': '0.711054429', 'v3': '0.363173478', 'v4': '', 'v5': '0.729361837', 'v6': '0.636855344' },
        { 'cn': '10', 'v1': '0.324665077', 'v2': '0.973218005', 'v3': '0.883927423', 'v4': '0.176906125', 'v5': '0.397795245', 'v6': '0.20087887' },
        { 'cn': '13', 'v1': '0.984003751', 'v2': '0.32549507', 'v3': '0.987090751', 'v4': '0.192745589', 'v5': '', 'v6': '0.735133561' },
        { 'cn': '14', 'v1': '0.083431884', 'v2': '0.565146092', 'v3': '0.935388666', 'v4': '0.637675154', 'v5': '', 'v6': '0.523815661' },
        { 'cn': 'n1', 'v1': '0.89950443', 'v2': '0.182709318', 'v3': '0.892820757', 'v4': '0.709746901', 'v5': '', 'v6': '0.097385354' },
        { 'cn': 'n2', 'v1': '0.061355308', 'v2': '0.314826137', 'v3': '0.855857651', 'v4': '0.653550539', 'v5': '', 'v6': '0.772500773' },
        { 'cn': 'n3', 'v1': '0.085078711', 'v2': '0.844664253', 'v3': '0.21630142', 'v4': '0.912931341', 'v5': '', 'v6': '0.735138313' }
    ]
    r = ltdtDiff(ltdtOld, ltdtNew)
    console.log('diff', r.diff)
    // => diff [
    //   {
    //     count: 1,
    //     added: false,
    //     removed: false,
    //     value: '0∶1⟋1∶0.791303871⟋2∶0.716898185⟋3∶0.506002098⟋4∶0.137888903⟋5∶⟋6∶0.626724085⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: true,
    //     value: '0∶2⟋1∶0.839882385⟋2∶0.663059856⟋3∶0.49047221⟋4∶0.395763265⟋5∶0.567412025⟋6∶0.866151835⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶2⟋1∶0.839882385⟋2∶0.663059856⟋3∶0.49047221⟋4∶0.395763265⟋5∶⟋6∶0.866151835⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: false,
    //     value: '0∶3⟋1∶0.475514539⟋2∶0.969205779⟋3∶0.711250309⟋4∶0.153847069⟋5∶0.304927473⟋6∶0.410092395⟋\n' +
    //       '0∶4⟋1∶0.486179086⟋2∶0.481023842⟋3∶0.467410582⟋4∶0.42602231⟋5∶⟋6∶0.849701641⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: true,
    //     value: '0∶5⟋1∶0.697242433⟋2∶0.67532802⟋3∶0.174644416⟋4∶0.045652267⟋5∶⟋6∶0.397104668⟋\n' +
    //       '0∶6⟋1∶0.259252779⟋2∶0.566177431⟋3∶0.679637706⟋4∶0.377814487⟋5∶⟋6∶0.400248119⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶5⟋1∶0.169724243⟋2∶0.67532802⟋3∶0.174644416⟋4∶0.045652267⟋5∶⟋6∶0.397104668⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: false,
    //     value: '0∶7⟋1∶0.263793391⟋2∶0.167895215⟋3∶0.794808602⟋4∶0.107070584⟋5∶⟋6∶0.011822872⟋\n' +
    //       '0∶8⟋1∶0.360426795⟋2∶0.014346373⟋3∶0.000469616⟋4∶0.4082693⟋5∶⟋6∶0.913806611⟋\n'
    //   },
    //   {
    //     count: 4,
    //     added: false,
    //     removed: true,
    //     value: '0∶9⟋1∶0.167996664⟋2∶0.711054429⟋3∶0.363177921⟋4∶0.206849994⟋5∶⟋6∶0.636855344⟋\n' +
    //       '0∶10⟋1∶0.324665077⟋2∶0.973218005⟋3∶0.883927423⟋4∶0.176906125⟋5∶⟋6∶0.20087887⟋\n' +
    //       '0∶11⟋1∶⟋2∶⟋3∶⟋4∶⟋5∶⟋6∶⟋\n' +
    //       '0∶12⟋1∶⟋2∶⟋3∶⟋4∶⟋5∶⟋6∶⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: true,
    //     removed: false,
    //     value: '0∶9⟋1∶0.167996664⟋2∶0.711054429⟋3∶0.363173478⟋4∶⟋5∶0.729361837⟋6∶0.636855344⟋\n' +
    //       '0∶10⟋1∶0.324665077⟋2∶0.973218005⟋3∶0.883927423⟋4∶0.176906125⟋5∶0.397795245⟋6∶0.20087887⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: false,
    //     value: '0∶13⟋1∶0.984003751⟋2∶0.32549507⟋3∶0.987090751⟋4∶0.192745589⟋5∶⟋6∶0.735133561⟋\n' +
    //       '0∶14⟋1∶0.083431884⟋2∶0.565146092⟋3∶0.935388666⟋4∶0.637675154⟋5∶⟋6∶0.523815661⟋\n'
    //   },
    //   {
    //     count: 3,
    //     added: true,
    //     removed: false,
    //     value: '0∶n1⟋1∶0.89950443⟋2∶0.182709318⟋3∶0.892820757⟋4∶0.709746901⟋5∶⟋6∶0.097385354⟋\n' +
    //       '0∶n2⟋1∶0.061355308⟋2∶0.314826137⟋3∶0.855857651⟋4∶0.653550539⟋5∶⟋6∶0.772500773⟋\n' +
    //       '0∶n3⟋1∶0.085078711⟋2∶0.844664253⟋3∶0.21630142⟋4∶0.912931341⟋5∶⟋6∶0.735138313⟋\n'
    //   }
    // ]
    for (let i = 0; i < r.dfs.length; i++) {
        let df = r.dfs[i]
        console.log(i, 'df', df)
    }
    // => 0 df {
    //   cn: { p: '', k: 'cn', vo: '1', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.791303871', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.716898185', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.506002098', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.137888903', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.626724085', vn: '' }
    // }
    // 1 df {
    //   cn: { p: '', k: 'cn', vo: '2', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.839882385', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.663059856', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.49047221', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.395763265', vn: '' },
    //   v5: { p: 'modify', k: 'v5', vo: '0.567412025', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.866151835', vn: '' }
    // }
    // 2 df {
    //   cn: { p: '', k: 'cn', vo: '3', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.475514539', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.969205779', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.711250309', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.153847069', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '0.304927473', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.410092395', vn: '' }
    // }
    // 3 df {
    //   cn: { p: '', k: 'cn', vo: '4', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.486179086', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.481023842', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.467410582', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.42602231', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.849701641', vn: '' }
    // }
    // 4 df {
    //   cn: { p: '', k: 'cn', vo: '5', vn: '' },
    //   v1: { p: 'modify', k: 'v1', vo: '0.697242433', vn: '0.169724243' },
    //   v2: { p: '', k: 'v2', vo: '0.67532802', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.174644416', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.045652267', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.397104668', vn: '' }
    // }
    // 5 df {
    //   cn: { p: 'remove', k: 'cn', vo: '6', vn: '' },
    //   v1: { p: 'remove', k: 'v1', vo: '0.259252779', vn: '' },
    //   v2: { p: 'remove', k: 'v2', vo: '0.566177431', vn: '' },
    //   v3: { p: 'remove', k: 'v3', vo: '0.679637706', vn: '' },
    //   v4: { p: 'remove', k: 'v4', vo: '0.377814487', vn: '' },
    //   v5: { p: 'remove', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'remove', k: 'v6', vo: '0.400248119', vn: '' }
    // }
    // 6 df {
    //   cn: { p: '', k: 'cn', vo: '7', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.263793391', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.167895215', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.794808602', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.107070584', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.011822872', vn: '' }
    // }
    // 7 df {
    //   cn: { p: '', k: 'cn', vo: '8', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.360426795', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.014346373', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.000469616', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.4082693', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.913806611', vn: '' }
    // }
    // 8 df {
    //   cn: { p: '', k: 'cn', vo: '9', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.167996664', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.711054429', vn: '' },
    //   v3: { p: 'modify', k: 'v3', vo: '0.363177921', vn: '0.363173478' },
    //   v4: { p: 'modify', k: 'v4', vo: '0.206849994', vn: '' },
    //   v5: { p: 'modify', k: 'v5', vo: '', vn: '0.729361837' },
    //   v6: { p: '', k: 'v6', vo: '0.636855344', vn: '' }
    // }
    // 9 df {
    //   cn: { p: '', k: 'cn', vo: '10', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.324665077', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.973218005', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.883927423', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.176906125', vn: '' },
    //   v5: { p: 'modify', k: 'v5', vo: '', vn: '0.397795245' },
    //   v6: { p: '', k: 'v6', vo: '0.20087887', vn: '' }
    // }
    // 10 df {
    //   cn: { p: 'remove', k: 'cn', vo: '11', vn: '' },
    //   v1: { p: 'remove', k: 'v1', vo: '', vn: '' },
    //   v2: { p: 'remove', k: 'v2', vo: '', vn: '' },
    //   v3: { p: 'remove', k: 'v3', vo: '', vn: '' },
    //   v4: { p: 'remove', k: 'v4', vo: '', vn: '' },
    //   v5: { p: 'remove', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'remove', k: 'v6', vo: '', vn: '' }
    // }
    // 11 df {
    //   cn: { p: 'remove', k: 'cn', vo: '12', vn: '' },
    //   v1: { p: 'remove', k: 'v1', vo: '', vn: '' },
    //   v2: { p: 'remove', k: 'v2', vo: '', vn: '' },
    //   v3: { p: 'remove', k: 'v3', vo: '', vn: '' },
    //   v4: { p: 'remove', k: 'v4', vo: '', vn: '' },
    //   v5: { p: 'remove', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'remove', k: 'v6', vo: '', vn: '' }
    // }
    // 12 df {
    //   cn: { p: '', k: 'cn', vo: '13', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.984003751', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.32549507', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.987090751', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.192745589', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.735133561', vn: '' }
    // }
    // 13 df {
    //   cn: { p: '', k: 'cn', vo: '14', vn: '' },
    //   v1: { p: '', k: 'v1', vo: '0.083431884', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.565146092', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.935388666', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.637675154', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.523815661', vn: '' }
    // }
    // 14 df {
    //   cn: { p: 'add', k: 'cn', vo: 'n1', vn: '' },
    //   v1: { p: 'add', k: 'v1', vo: '0.89950443', vn: '' },
    //   v2: { p: 'add', k: 'v2', vo: '0.182709318', vn: '' },
    //   v3: { p: 'add', k: 'v3', vo: '0.892820757', vn: '' },
    //   v4: { p: 'add', k: 'v4', vo: '0.709746901', vn: '' },
    //   v5: { p: 'add', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'add', k: 'v6', vo: '0.097385354', vn: '' }
    // }
    // 15 df {
    //   cn: { p: 'add', k: 'cn', vo: 'n2', vn: '' },
    //   v1: { p: 'add', k: 'v1', vo: '0.061355308', vn: '' },
    //   v2: { p: 'add', k: 'v2', vo: '0.314826137', vn: '' },
    //   v3: { p: 'add', k: 'v3', vo: '0.855857651', vn: '' },
    //   v4: { p: 'add', k: 'v4', vo: '0.653550539', vn: '' },
    //   v5: { p: 'add', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'add', k: 'v6', vo: '0.772500773', vn: '' }
    // }
    // 16 df {
    //   cn: { p: 'add', k: 'cn', vo: 'n3', vn: '' },
    //   v1: { p: 'add', k: 'v1', vo: '0.085078711', vn: '' },
    //   v2: { p: 'add', k: 'v2', vo: '0.844664253', vn: '' },
    //   v3: { p: 'add', k: 'v3', vo: '0.21630142', vn: '' },
    //   v4: { p: 'add', k: 'v4', vo: '0.912931341', vn: '' },
    //   v5: { p: 'add', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'add', k: 'v6', vo: '0.735138313', vn: '' }
    // }
}

if (true) {
    ltdtOld = [
        { 'cn': '1', 'v8': '0.701731713', 'v1': '0.791303871', 'v2': '0.716898185', 'v3': '0.506002098', 'v4': '0.137888903', 'v5': '', 'v6': '0.626724085' },
        { 'cn': '2', 'v8': '0.944311349', 'v1': '0.839882385', 'v2': '0.663059856', 'v3': '0.49047221', 'v4': '0.395763265', 'v5': '0.567412025', 'v6': '0.866151835' },
        { 'cn': '3', 'v8': '0.516219732', 'v1': '0.475514539', 'v2': '0.969205779', 'v3': '0.711250309', 'v4': '0.153847069', 'v5': '0.304927473', 'v6': '0.410092395' },
        { 'cn': '4', 'v8': '0.454533809', 'v1': '0.486179086', 'v2': '0.481023842', 'v3': '0.467410582', 'v4': '0.42602231', 'v5': '', 'v6': '0.849701641' },
        { 'cn': '5', 'v8': '0.844099348', 'v1': '0.697242433', 'v2': '0.67532802', 'v3': '0.174644416', 'v4': '0.045652267', 'v5': '', 'v6': '0.397104668' },
        { 'cn': '6', 'v8': '0.15264562', 'v1': '0.259252779', 'v2': '0.566177431', 'v3': '0.679637706', 'v4': '0.377814487', 'v5': '', 'v6': '0.400248119' },
        { 'cn': '7', 'v8': '0.399132301', 'v1': '0.263793391', 'v2': '0.167895215', 'v3': '0.794808602', 'v4': '0.107070584', 'v5': '', 'v6': '0.011822872' },
        { 'cn': '8', 'v8': '', 'v1': '0.360426795', 'v2': '0.014346373', 'v3': '0.000469616', 'v4': '0.4082693', 'v5': '', 'v6': '0.913806611' },
        { 'cn': '9', 'v8': '', 'v1': '0.167996664', 'v2': '0.711054429', 'v3': '0.363177921', 'v4': '0.206849994', 'v5': '', 'v6': '0.636855344' },
        { 'cn': '10', 'v8': '', 'v1': '0.324665077', 'v2': '0.973218005', 'v3': '0.883927423', 'v4': '0.176906125', 'v5': '', 'v6': '0.20087887' },
        { 'cn': '11', 'v8': '', 'v1': '', 'v2': '', 'v3': '', 'v4': '', 'v5': '', 'v6': '' },
        { 'cn': '12', 'v8': '', 'v1': '', 'v2': '', 'v3': '', 'v4': '', 'v5': '', 'v6': '' },
        { 'cn': '13', 'v8': '', 'v1': '0.984003751', 'v2': '0.32549507', 'v3': '0.987090751', 'v4': '0.192745589', 'v5': '', 'v6': '0.735133561' },
        { 'cn': '14', 'v8': '', 'v1': '0.083431884', 'v2': '0.565146092', 'v3': '0.935388666', 'v4': '0.637675154', 'v5': '', 'v6': '0.523815661' }
    ]
    ltdtNew = [
        { 'cn': '1', 'v9': '', 'v1': '0.791303871', 'v2': '0.716898185', 'v3': '0.506002098', 'v4': '0.137888903', 'v5': '', 'v6': '0.626724085' },
        { 'cn': '2', 'v9': '', 'v1': '0.183988239', 'v2': '0.663059856', 'v3': '0.49047221', 'v4': '0.395763265', 'v5': '', 'v6': '0.866151835' },
        { 'cn': '3', 'v9': '', 'v1': '0.475514539', 'v2': '0.969205779', 'v3': '0.711250309', 'v4': '0.153847069', 'v5': '0.304927473', 'v6': '0.410092395' },
        { 'cn': '4', 'v9': '', 'v1': '0.486179086', 'v2': '0.481023842', 'v3': '0.467410582', 'v4': '0.42602231', 'v5': '', 'v6': '0.849701641' },
        { 'cn': '5', 'v9': '', 'v1': '0.697242433', 'v2': '0.67532802', 'v3': '0.174644416', 'v4': '0.045652267', 'v5': '', 'v6': '0.397104668' },
        { 'cn': '7', 'v9': '0.15264562', 'v1': '0.263793391', 'v2': '0.167895215', 'v3': '0.794808602', 'v4': '0.107070584', 'v5': '', 'v6': '0.011822872' },
        { 'cn': '8', 'v9': '0.399132301', 'v1': '0.360426795', 'v2': '0.014346373', 'v3': '0.000469616', 'v4': '0.4082693', 'v5': '', 'v6': '0.913806611' },
        { 'cn': '9', 'v9': '0.84324243', 'v1': '0.167996664', 'v2': '0.711054429', 'v3': '0.363173478', 'v4': '', 'v5': '0.729361837', 'v6': '0.636855344' },
        { 'cn': '10', 'v9': '0.631250305', 'v1': '0.324665077', 'v2': '0.973218005', 'v3': '0.883927423', 'v4': '0.176906125', 'v5': '0.397795245', 'v6': '0.20087887' },
        { 'cn': '13', 'v9': '0.980581906', 'v1': '0.984003751', 'v2': '0.32549507', 'v3': '0.987090751', 'v4': '0.192745589', 'v5': '', 'v6': '0.735133561' },
        { 'cn': '14', 'v9': '', 'v1': '0.083431884', 'v2': '0.565146092', 'v3': '0.935388666', 'v4': '0.637675154', 'v5': '', 'v6': '0.523815661' },
        { 'cn': 'n1', 'v9': '', 'v1': '0.89950443', 'v2': '0.182709318', 'v3': '0.892820757', 'v4': '0.709746901', 'v5': '', 'v6': '0.097385354' },
        { 'cn': 'n2', 'v9': '', 'v1': '0.061355308', 'v2': '0.314826137', 'v3': '0.855857651', 'v4': '0.653550539', 'v5': '', 'v6': '0.772500773' },
        { 'cn': 'n3', 'v9': '0.057840884', 'v1': '0.085078711', 'v2': '0.844664253', 'v3': '0.21630142', 'v4': '0.912931341', 'v5': '', 'v6': '0.735138313' }
    ]
    r = ltdtDiff(ltdtOld, ltdtNew)
    console.log('diff', r.diff)
    // => diff [
    //   {
    //     count: 14,
    //     added: false,
    //     removed: true,
    //     value: '0∶1⟋1∶0.701731713⟋2∶0.791303871⟋3∶0.716898185⟋4∶0.506002098⟋5∶0.137888903⟋6∶⟋7∶0.626724085⟋8∶∗empty∗⟋\n' +
    //       '0∶2⟋1∶0.944311349⟋2∶0.839882385⟋3∶0.663059856⟋4∶0.49047221⟋5∶0.395763265⟋6∶0.567412025⟋7∶0.866151835⟋8∶∗empty∗⟋\n' +
    //       '0∶3⟋1∶0.516219732⟋2∶0.475514539⟋3∶0.969205779⟋4∶0.711250309⟋5∶0.153847069⟋6∶0.304927473⟋7∶0.410092395⟋8∶∗empty∗⟋\n' +
    //       '0∶4⟋1∶0.454533809⟋2∶0.486179086⟋3∶0.481023842⟋4∶0.467410582⟋5∶0.42602231⟋6∶⟋7∶0.849701641⟋8∶∗empty∗⟋\n' +
    //       '0∶5⟋1∶0.844099348⟋2∶0.697242433⟋3∶0.67532802⟋4∶0.174644416⟋5∶0.045652267⟋6∶⟋7∶0.397104668⟋8∶∗empty∗⟋\n' +
    //       '0∶6⟋1∶0.15264562⟋2∶0.259252779⟋3∶0.566177431⟋4∶0.679637706⟋5∶0.377814487⟋6∶⟋7∶0.400248119⟋8∶∗empty∗⟋\n' +
    //       '0∶7⟋1∶0.399132301⟋2∶0.263793391⟋3∶0.167895215⟋4∶0.794808602⟋5∶0.107070584⟋6∶⟋7∶0.011822872⟋8∶∗empty∗⟋\n' +
    //       '0∶8⟋1∶⟋2∶0.360426795⟋3∶0.014346373⟋4∶0.000469616⟋5∶0.4082693⟋6∶⟋7∶0.913806611⟋8∶∗empty∗⟋\n' +
    //       '0∶9⟋1∶⟋2∶0.167996664⟋3∶0.711054429⟋4∶0.363177921⟋5∶0.206849994⟋6∶⟋7∶0.636855344⟋8∶∗empty∗⟋\n' +
    //       '0∶10⟋1∶⟋2∶0.324665077⟋3∶0.973218005⟋4∶0.883927423⟋5∶0.176906125⟋6∶⟋7∶0.20087887⟋8∶∗empty∗⟋\n' +
    //       '0∶11⟋1∶⟋2∶⟋3∶⟋4∶⟋5∶⟋6∶⟋7∶⟋8∶∗empty∗⟋\n' +
    //       '0∶12⟋1∶⟋2∶⟋3∶⟋4∶⟋5∶⟋6∶⟋7∶⟋8∶∗empty∗⟋\n' +
    //       '0∶13⟋1∶⟋2∶0.984003751⟋3∶0.32549507⟋4∶0.987090751⟋5∶0.192745589⟋6∶⟋7∶0.735133561⟋8∶∗empty∗⟋\n' +
    //       '0∶14⟋1∶⟋2∶0.083431884⟋3∶0.565146092⟋4∶0.935388666⟋5∶0.637675154⟋6∶⟋7∶0.523815661⟋8∶∗empty∗⟋\n'
    //   },
    //   {
    //     count: 14,
    //     added: true,
    //     removed: false,
    //     value: '0∶1⟋1∶∗empty∗⟋2∶0.791303871⟋3∶0.716898185⟋4∶0.506002098⟋5∶0.137888903⟋6∶⟋7∶0.626724085⟋8∶⟋\n' +
    //       '0∶2⟋1∶∗empty∗⟋2∶0.183988239⟋3∶0.663059856⟋4∶0.49047221⟋5∶0.395763265⟋6∶⟋7∶0.866151835⟋8∶⟋\n' +
    //       '0∶3⟋1∶∗empty∗⟋2∶0.475514539⟋3∶0.969205779⟋4∶0.711250309⟋5∶0.153847069⟋6∶0.304927473⟋7∶0.410092395⟋8∶⟋\n' +
    //       '0∶4⟋1∶∗empty∗⟋2∶0.486179086⟋3∶0.481023842⟋4∶0.467410582⟋5∶0.42602231⟋6∶⟋7∶0.849701641⟋8∶⟋\n' +
    //       '0∶5⟋1∶∗empty∗⟋2∶0.697242433⟋3∶0.67532802⟋4∶0.174644416⟋5∶0.045652267⟋6∶⟋7∶0.397104668⟋8∶⟋\n' +
    //       '0∶7⟋1∶∗empty∗⟋2∶0.263793391⟋3∶0.167895215⟋4∶0.794808602⟋5∶0.107070584⟋6∶⟋7∶0.011822872⟋8∶0.15264562⟋\n' +
    //       '0∶8⟋1∶∗empty∗⟋2∶0.360426795⟋3∶0.014346373⟋4∶0.000469616⟋5∶0.4082693⟋6∶⟋7∶0.913806611⟋8∶0.399132301⟋\n' +
    //       '0∶9⟋1∶∗empty∗⟋2∶0.167996664⟋3∶0.711054429⟋4∶0.363173478⟋5∶⟋6∶0.729361837⟋7∶0.636855344⟋8∶0.84324243⟋\n' +
    //       '0∶10⟋1∶∗empty∗⟋2∶0.324665077⟋3∶0.973218005⟋4∶0.883927423⟋5∶0.176906125⟋6∶0.397795245⟋7∶0.20087887⟋8∶0.631250305⟋\n' +
    //       '0∶13⟋1∶∗empty∗⟋2∶0.984003751⟋3∶0.32549507⟋4∶0.987090751⟋5∶0.192745589⟋6∶⟋7∶0.735133561⟋8∶0.980581906⟋\n' +
    //       '0∶14⟋1∶∗empty∗⟋2∶0.083431884⟋3∶0.565146092⟋4∶0.935388666⟋5∶0.637675154⟋6∶⟋7∶0.523815661⟋8∶⟋\n' +
    //       '0∶n1⟋1∶∗empty∗⟋2∶0.89950443⟋3∶0.182709318⟋4∶0.892820757⟋5∶0.709746901⟋6∶⟋7∶0.097385354⟋8∶⟋\n' +
    //       '0∶n2⟋1∶∗empty∗⟋2∶0.061355308⟋3∶0.314826137⟋4∶0.855857651⟋5∶0.653550539⟋6∶⟋7∶0.772500773⟋8∶⟋\n' +
    //       '0∶n3⟋1∶∗empty∗⟋2∶0.085078711⟋3∶0.844664253⟋4∶0.21630142⟋5∶0.912931341⟋6∶⟋7∶0.735138313⟋8∶0.057840884⟋\n'
    //   }
    // ]
    for (let i = 0; i < r.dfs.length; i++) {
        let df = r.dfs[i]
        console.log(i, 'df', df)
    }
    // => 0 df {
    //   cn: { p: '', k: 'cn', vo: '1', vn: '' },
    //   v8: { p: 'modify', k: 'v8', vo: '0.701731713', vn: '∗empty∗' },
    //   v1: { p: '', k: 'v1', vo: '0.791303871', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.716898185', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.506002098', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.137888903', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.626724085', vn: '' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '' }
    // }
    // 1 df {
    //   cn: { p: '', k: 'cn', vo: '2', vn: '' },
    //   v8: { p: 'modify', k: 'v8', vo: '0.944311349', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '0.839882385', vn: '0.183988239' },
    //   v2: { p: '', k: 'v2', vo: '0.663059856', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.49047221', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.395763265', vn: '' },
    //   v5: { p: 'modify', k: 'v5', vo: '0.567412025', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.866151835', vn: '' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '' }
    // }
    // 2 df {
    //   cn: { p: '', k: 'cn', vo: '3', vn: '' },
    //   v8: { p: 'modify', k: 'v8', vo: '0.516219732', vn: '∗empty∗' },
    //   v1: { p: '', k: 'v1', vo: '0.475514539', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.969205779', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.711250309', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.153847069', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '0.304927473', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.410092395', vn: '' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '' }
    // }
    // 3 df {
    //   cn: { p: '', k: 'cn', vo: '4', vn: '' },
    //   v8: { p: 'modify', k: 'v8', vo: '0.454533809', vn: '∗empty∗' },
    //   v1: { p: '', k: 'v1', vo: '0.486179086', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.481023842', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.467410582', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.42602231', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.849701641', vn: '' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '' }
    // }
    // 4 df {
    //   cn: { p: '', k: 'cn', vo: '5', vn: '' },
    //   v8: { p: 'modify', k: 'v8', vo: '0.844099348', vn: '∗empty∗' },
    //   v1: { p: '', k: 'v1', vo: '0.697242433', vn: '' },
    //   v2: { p: '', k: 'v2', vo: '0.67532802', vn: '' },
    //   v3: { p: '', k: 'v3', vo: '0.174644416', vn: '' },
    //   v4: { p: '', k: 'v4', vo: '0.045652267', vn: '' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: '', k: 'v6', vo: '0.397104668', vn: '' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '' }
    // }
    // 5 df {
    //   cn: { p: 'modify', k: 'cn', vo: '6', vn: '7' },
    //   v8: { p: 'modify', k: 'v8', vo: '0.15264562', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '0.259252779', vn: '0.263793391' },
    //   v2: { p: 'modify', k: 'v2', vo: '0.566177431', vn: '0.167895215' },
    //   v3: { p: 'modify', k: 'v3', vo: '0.679637706', vn: '0.794808602' },
    //   v4: { p: 'modify', k: 'v4', vo: '0.377814487', vn: '0.107070584' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'modify', k: 'v6', vo: '0.400248119', vn: '0.011822872' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '0.15264562' }
    // }
    // 6 df {
    //   cn: { p: 'modify', k: 'cn', vo: '7', vn: '8' },
    //   v8: { p: 'modify', k: 'v8', vo: '0.399132301', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '0.263793391', vn: '0.360426795' },
    //   v2: { p: 'modify', k: 'v2', vo: '0.167895215', vn: '0.014346373' },
    //   v3: { p: 'modify', k: 'v3', vo: '0.794808602', vn: '0.000469616' },
    //   v4: { p: 'modify', k: 'v4', vo: '0.107070584', vn: '0.4082693' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'modify', k: 'v6', vo: '0.011822872', vn: '0.913806611' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '0.399132301' }
    // }
    // 7 df {
    //   cn: { p: 'modify', k: 'cn', vo: '8', vn: '9' },
    //   v8: { p: 'modify', k: 'v8', vo: '', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '0.360426795', vn: '0.167996664' },
    //   v2: { p: 'modify', k: 'v2', vo: '0.014346373', vn: '0.711054429' },
    //   v3: { p: 'modify', k: 'v3', vo: '0.000469616', vn: '0.363173478' },
    //   v4: { p: 'modify', k: 'v4', vo: '0.4082693', vn: '' },
    //   v5: { p: 'modify', k: 'v5', vo: '', vn: '0.729361837' },
    //   v6: { p: 'modify', k: 'v6', vo: '0.913806611', vn: '0.636855344' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '0.84324243' }
    // }
    // 8 df {
    //   cn: { p: 'modify', k: 'cn', vo: '9', vn: '10' },
    //   v8: { p: 'modify', k: 'v8', vo: '', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '0.167996664', vn: '0.324665077' },
    //   v2: { p: 'modify', k: 'v2', vo: '0.711054429', vn: '0.973218005' },
    //   v3: { p: 'modify', k: 'v3', vo: '0.363177921', vn: '0.883927423' },
    //   v4: { p: 'modify', k: 'v4', vo: '0.206849994', vn: '0.176906125' },
    //   v5: { p: 'modify', k: 'v5', vo: '', vn: '0.397795245' },
    //   v6: { p: 'modify', k: 'v6', vo: '0.636855344', vn: '0.20087887' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '0.631250305' }
    // }
    // 9 df {
    //   cn: { p: 'modify', k: 'cn', vo: '10', vn: '13' },
    //   v8: { p: 'modify', k: 'v8', vo: '', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '0.324665077', vn: '0.984003751' },
    //   v2: { p: 'modify', k: 'v2', vo: '0.973218005', vn: '0.32549507' },
    //   v3: { p: 'modify', k: 'v3', vo: '0.883927423', vn: '0.987090751' },
    //   v4: { p: 'modify', k: 'v4', vo: '0.176906125', vn: '0.192745589' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'modify', k: 'v6', vo: '0.20087887', vn: '0.735133561' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '0.980581906' }
    // }
    // 10 df {
    //   cn: { p: 'modify', k: 'cn', vo: '11', vn: '14' },
    //   v8: { p: 'modify', k: 'v8', vo: '', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '', vn: '0.083431884' },
    //   v2: { p: 'modify', k: 'v2', vo: '', vn: '0.565146092' },
    //   v3: { p: 'modify', k: 'v3', vo: '', vn: '0.935388666' },
    //   v4: { p: 'modify', k: 'v4', vo: '', vn: '0.637675154' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'modify', k: 'v6', vo: '', vn: '0.523815661' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '' }
    // }
    // 11 df {
    //   cn: { p: 'modify', k: 'cn', vo: '12', vn: 'n1' },
    //   v8: { p: 'modify', k: 'v8', vo: '', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '', vn: '0.89950443' },
    //   v2: { p: 'modify', k: 'v2', vo: '', vn: '0.182709318' },
    //   v3: { p: 'modify', k: 'v3', vo: '', vn: '0.892820757' },
    //   v4: { p: 'modify', k: 'v4', vo: '', vn: '0.709746901' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'modify', k: 'v6', vo: '', vn: '0.097385354' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '' }
    // }
    // 12 df {
    //   cn: { p: 'modify', k: 'cn', vo: '13', vn: 'n2' },
    //   v8: { p: 'modify', k: 'v8', vo: '', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '0.984003751', vn: '0.061355308' },
    //   v2: { p: 'modify', k: 'v2', vo: '0.32549507', vn: '0.314826137' },
    //   v3: { p: 'modify', k: 'v3', vo: '0.987090751', vn: '0.855857651' },
    //   v4: { p: 'modify', k: 'v4', vo: '0.192745589', vn: '0.653550539' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'modify', k: 'v6', vo: '0.735133561', vn: '0.772500773' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '' }
    // }
    // 13 df {
    //   cn: { p: 'modify', k: 'cn', vo: '14', vn: 'n3' },
    //   v8: { p: 'modify', k: 'v8', vo: '', vn: '∗empty∗' },
    //   v1: { p: 'modify', k: 'v1', vo: '0.083431884', vn: '0.085078711' },
    //   v2: { p: 'modify', k: 'v2', vo: '0.565146092', vn: '0.844664253' },
    //   v3: { p: 'modify', k: 'v3', vo: '0.935388666', vn: '0.21630142' },
    //   v4: { p: 'modify', k: 'v4', vo: '0.637675154', vn: '0.912931341' },
    //   v5: { p: '', k: 'v5', vo: '', vn: '' },
    //   v6: { p: 'modify', k: 'v6', vo: '0.523815661', vn: '0.735138313' },
    //   v9: { p: 'modify', k: 'v9', vo: '∗empty∗', vn: '0.057840884' }
    // }
}
Parameters:
Name Type Attributes Default Description
ltdtOld Array

輸入原始數據物件陣列

ltdtNew Array

輸入更新數據物件陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
dls String <optional>
'⟋'

輸入若數據為矩陣時各儲存格分隔字串,預設'⟋'

dlq String <optional>
'∶'

輸入若數據為矩陣時各儲存格欄位指標分隔字串,預設'∶'

empty String <optional>
'∗empty∗'

輸入若無鍵值時給予之預設值,預設'∗empty∗'

Returns:

回傳比對結果物件,回傳鍵值diff、dfrs與dfs,其中diff一樣為比對原始結果字串,dfrs為各列比對結果陣列,dfs為各儲存格比對結果陣列

Type
Object

(static) ltdtDiffByKey(ltdtOld, ltdtNew, key, optopt) → {Object}

Description:
  • 比對新舊物件陣列,基於指定key為主鍵,比對vnew對vold中有差異之項目

    Unit Test: Github

Source:
Example
let ltdtOld = [{ id: 'id-1', a: 'a1' }, { id: 'id-2', a: 'a2' }, { id: 'id-3', a: 'a3' }]
let ltdtNew = [{ id: 'id-1', z: 'z3' }, { id: 'id-3', a: 'a3' }, { id: 'id-4', a: 'a4' }]
console.log(ltdtDiffByKey(ltdtOld, ltdtNew, 'id'))
// => {
//   del: [ { id: 'id-2', a: 'a2' } ],
//   add: [ { id: 'id-4', a: 'a4' } ],
//   same: [ { id: 'id-3', a: 'a3' } ],
//   diff: [ { id: 'id-1', z: 'z3' } ],
//   infor: { 'id-2': 'del', 'id-4': 'add', 'id-1': 'diff', 'id-3': 'same' }
// }
Parameters:
Name Type Attributes Default Description
ltdtOld Array | Object

輸入舊的物件陣列或物件

ltdtNew Array | Object

輸入新的物件陣列或物件

key String

輸入比對的主鍵key值字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
withInfor Boolean <optional>
true

輸入是否提供infor資訊布林值,不提供可再加速,預設true

Returns:

回傳比對結果物件

Type
Object

(static) ltdtkeys2mat(ltdt, keysopt, optopt) → {Array|Object}

Description:
  • 由物件陣列ltdt並使用keys取值轉二維陣列mat

    Unit Test: Github

Source:
Example
console.log(ltdtkeys2mat([{ a: 12, b: 34.56 }, { a: '123', b: 'xyz' }], ['a', 'b']))
// => [[12, 34.56], ['123', 'xyz']]
Parameters:
Name Type Attributes Default Description
ltdt Array

輸入物件陣列

keys Array <optional>
null

輸入字串陣列,若不輸入則由ltdt提取,預設為null

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
empty String | Number | Array | Object <optional>
''

輸入若無鍵值時給予之預設值,預設''

returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳資料陣列,ltdt無效、無法取得keys或任一元素非有效物件時回傳空陣列;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Array | Object

(static) ltdtkeysheads2mat(ltdt, keysopt, kpheadopt, optopt) → {Array|Object}

Description:
  • keys轉heads,物件陣列ltdt使用keys取值,兩者合併轉出二維陣列mat

    Unit Test: Github

Source:
Example
console.log(ltdtkeysheads2mat([{ a: 12, b: 34.56 }, { a: '123', b: 'xyz' }], ['a', 'b'], { a: 'c1', b: 'c2' }))
// => [['c1', 'c2'], [12, 34.56], ['123', 'xyz']]
Parameters:
Name Type Attributes Default Description
ltdt Array

輸入物件陣列

keys Array <optional>
[]

輸入字串陣列,若不輸入則由ltdt提取,預設[]

kphead Object <optional>
{}

輸入字典物件,內含keys對應values之物件,供keys查詢得values,若不輸入則由keys提取,預設為{}

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳資料陣列,ltdt無效或任一元素非有效物件時回傳空陣列;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Array | Object

(static) ltdtmapping(ltdt, keys, defopt) → {Array}

Description:
  • 由ltdt陣列各元素物件提取指定keys欄位出來成為新的物件陣列,若物件不存在keys的欄位時,則會自動填入指定數據def

    Unit Test: Github

Source:
Example
let r = [
    { a: 'a123', b: 123, c: 'abc' },
    { a: '1b23', b: 456, c: '123XYZ' },
    { a: '12c3', b: 789.0123, c: null }
]

console.log(ltdtmapping(r, ['a', 'b']))
// => [
//     { a: 'a123', b: 123 },
//     { a: '1b23', b: 456 },
//     { a: '12c3', b: 789.0123 }
// ]

console.log(ltdtmapping(r, ['a', 'c', 'x']))
// => [
//     { a: 'a123', c: 'abc', x: '' },
//     { a: '1b23', c: '123XYZ', x: '' },
//     { a: '12c3', c: null, x: '' }
// ]
Parameters:
Name Type Attributes Default Description
ltdt Array

輸入要被提取的任意資料陣列,各元素需為物件,否則提取後為空物件

keys Array

輸入要提取欄位集合的字串陣列

def * <optional>
''

輸入若無提取欄位時所給予的預設值,預設為''

Returns:

回傳提取的物件資料陣列

Type
Array

(static) ltdtmerge(ltdt1, ltdt2) → {Array}

Description:
  • 針對物件陣列ltdt1與ltdt2逐一深度合併

    回傳新物件陣列,不修改ltdt1與ltdt2(含其內巢狀陣列與物件),結果內之陣列與純物件與輸入無共用參照(TypedArray/Buffer取複本,Date、Map、類別實例等其他非純物件之值直接取用)。 合併規則:同路徑之陣列依索引合併、純物件依鍵合併;ltdt2之值為undefined時保留ltdt1之值(ltdt1無該鍵時設為undefined);同路徑之種類不同(陣列與物件、物件與原始值等)時以ltdt2為準;ltdt2之資料列非陣列非物件(undefined、null、原始值)時忽略。 物件之length與其他鍵同為一般鍵,不會使該物件被當作陣列處理;鍵__proto__與constructor不會污染原型;ltdt1之原型所共用之容器不會被修改(只讀自有鍵之值)。 ltdt1與ltdt2非陣列或長度不同時回傳空陣列。

    Unit Test: Github

Source:
Example
console.log(ltdtmerge([{ a: 12, b: 34.56 }, {}], [{ a: '123', c: 'mn' }, { aa: 'a123', bb: 'bmn' }]))
// => [{ a: '123', b: 34.56, c: 'mn' }, { aa: 'a123', bb: 'bmn' }]

console.log(ltdtmerge([{ x: [1, 2, 3], y: { length: 2, a: 1 } }], [{ x: [9], y: { length: 5, b: 2 } }]))
// => [{ x: [9, 2, 3], y: { length: 5, a: 1, b: 2 } }]
Parameters:
Name Type Description
ltdt1 Array

輸入資料物件陣列

ltdt2 Array

輸入資料物件陣列,若與ltdt1內物件有重複的欄位值,優先保留ltdt2內物件的欄位值

Returns:

回傳處理後物件陣列

Type
Array

(static) ltdtpick(ltdt, keys) → {Array}

Description:
  • 由ltdt陣列各元素物件提取指定keys欄位出來成為新的物件陣列,僅保留有keys的欄位,若不存在欄位就不提取

    Unit Test: Github

Source:
Example
let r = [
    { a: 'a123', b: 123, c: 'abc' },
    { a: '1b23', b: 456, c: '123XYZ' },
    { a: '12c3', b: 789.0123, c: null }
]

console.log(ltdtpick(r, ['a', 'b']))
// => [
//     { a: 'a123', b: 123 },
//     { a: '1b23', b: 456 },
//     { a: '12c3', b: 789.0123 }
// ]

console.log(ltdtpick(r, ['a', 'c', 'x']))
// => [
//     { a: 'a123', c: 'abc' },
//     { a: '1b23', c: '123XYZ' },
//     { a: '12c3', c: null }
// ]
Parameters:
Name Type Description
ltdt Array

輸入要被提取的任意資料陣列,各元素需為物件,否則提取後為空物件

keys Array

輸入要提取欄位集合的字串陣列

Returns:

回傳提取的物件資料陣列

Type
Array

(static) ltdtpull(ltdtAll, ltdtDel, key) → {Array}

Description:
  • 由ltdtAll陣列移除ltdtDel陣列,只會針對ltdtDel內各元素的指定key來查找

    Unit Test: Github

Source:
Example
console.log(ltdtpull([{ x: 1, y: 'y1' }, { x: 2, y: 'y2' }], [{ x: 1, y: 'y3' }], 'x'))
// => [ { x: 2, y: 'y2' } ]

console.log(ltdtpull([{ id: 1, v: 'v1' }, { id: 2, v: 'v2' }, { id: 3, v: 'v3' }], [{ id: 1, v: '-v1' }, { id: 3, v: '-v3' }], 'id'))
// => [ { id: 2, v: 'v2' } ]
Parameters:
Name Type Description
ltdtAll Array

輸入要被刪除的物件陣列

ltdtDel Array

輸入要刪除的物件陣列

key String

輸入要比對物件的key值

Returns:

回傳被刪除的物件陣列

Type
Array

(static) mat2ltdt(mat, optopt) → {Array|Object}

Description:
  • 由mat第1行當head,其餘當data,轉ltdt mat第1行需為字串陣列,才能當head

    Unit Test: Github

Source:
Example
console.log(mat2ltdt([['a', 'b'], [12, 34.56], ['x', '12.34']]))
// => [{ a: 12, b: 34.56 }, { a: 'x', b: '12.34' }]
Parameters:
Name Type Attributes Default Description
mat Array

輸入資料陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳物件陣列,mat無效、列數少於2或任一列非有效陣列時回傳空陣列;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Array | Object

(static) matConcat(mat, istart, iendopt) → {Array}

Description:
  • 合併二維陣列mat1與mat2

    Unit Test: Github

Source:
Example
let mat1
let mat2

mat1 = [['a', 'b'], [1.1, 2.2]]
mat2 = [['c', 'd'], [10.1, 20.2]]
console.log(matConcat(mat1, mat2))
// => [
//   [ 'a', 'b', 'c', 'd' ],
//   [ 1.1, 2.2, 10.1, 20.2 ]
// ]

mat1 = [['a', 'b', 'c'], [1, 2, 3], [1.1, 2.2, 3.3]]
mat2 = [['x', 'y'], [-10.1, -20.2]]
console.log(matConcat(mat1, mat2))
// => [
//   [ 'a', 'b', 'c', 'x', 'y' ],
//   [ 1, 2, 3, -10.1, -20.2 ],
//   [ 1.1, 2.2, 3.3, null, null ]
// ]

mat1 = [['a', 'b'], [1.1, 2.2]]
mat2 = [['x', 'y', 'z'], [-1, -2, -3], [-10.1, -20.2, -30.3]]
console.log(matConcat(mat1, mat2))
// => [
//   [ 'a', 'b', 'x', 'y', 'z' ],
//   [ 1.1, 2.2, -1, -2, -3 ],
//   [ null, null, -10.1, -20.2, -30.3 ]
// ]
Parameters:
Name Type Attributes Default Description
mat Array

輸入要被提取的資料陣列

istart Integer

輸入起始的欄位指標整數

iend Integer <optional>
istart

輸入結束的欄位指標整數,若不輸入則等同於istart

Returns:

回傳提取的資料陣列

Type
Array

(static) matDiff(matOld, matNew, optopt) → {Object}

Description:
  • 比對新舊二維陣列差異處

    Unit Test: Github

Source:
Example
let r
let matOld
let matNew

if (true) {
    matOld = [
        [1, 0.974848293, 0.791303871, 0.716898185, 0.506002098, 0.137888903, 0.626724085],
        [2, -0.529408622, 0.839882385, 0.663059856, 0.49047221, 123, 0.395763265, 0.866151835],
        [3, -0.10320217, 0.475514539, 0.969205779, 0.711250309, 0.153847069, 0.410092395],
        [4, -0.121479865, 0.486179086, 0.481023842, 0.467410582, 0.42602231, 0.849701641],
        [5, 0.757346003, 0.697242433, 0.67532802, 0.174644416, 0.045652267, 0.397104668],
        [6, 0.663032731, 0.259252779, 0.566177431, 0.679637706, 0.377814487, 0.400248119],
        [7, 0.72721374, 0.263793391, 0.167895215, 0.794808602, 0.107070584, 0.011822872],
        [8, 0.247416664, 0.360426795, 0.014346373, 0.000469616, 0.4082693, 0.913806611],
        [9, 0.345880037, 0.167996664, 0.711054429, 0.363177921, 0.206849994, 0.636855344],
        [10, 0.576739457, 0.324665077, 0.973218005, 0.883927423, 0.176906125, 0.20087887],
        [11, 1.2],
        [12, 23.5],
        [13, 0.504421248, 0.984003751, 0.32549507, 0.987090751, 0.192745589, 0.735133561],
        [14, 0.273214614, 0.083431884, 0.565146092, 0.935388666, 0.637675154, 0.523815661],
    ]
    matNew = [
        [1, 0.974848293, 0.791303871, 0.716898185, 0.506002098, 0.137888903, 0.626724085],
        [2, -0.529408622, 0.1839882385, 0.663059856, 0.49047221, 0.395763265, 45.6, 0.866151835],
        [3, -0.10320217, 0.475514539, 0.969205779, 0.711250309, 0.153847069, 0.410092395],
        [4, -0.121479865, 0.486179086, 0.481023842, 0.467410582, 0.42602231, 0.849701641],
        [5, 0.757346003, 0.697242433, 0.67532802, 0.174644416, 0.045652267, 0.397104668],
        [7, 0.72721374, 0.263793391, 0.167895215, 0.794808602, 0.107070584, 0.011822872],
        [8, 0.247416664, 0.360426795, 0.014346373, 0.000469616, 0.4082693, 0.913806611],
        [9, 0.345880037, 0.167996664, 0.711054429, 0.363173478, 0.636855344],
        [10, 0.576739457, 0.324665077, 0.973218005, 0.883927423, 0.176906125, 0.20087887],
        [13, 0.504421248, 0.984003751, 0.32549507, 0.987090751, 0.192745589, 0.735133561],
        [14, 0.273214614, 0.083431884, 0.565146092, 0.935388666, 0.637675154, 0.523815661],
        ['n1', 0.944492151, 0.89950443, 0.182709318, 0.892820757, 0.709746901, 0.097385354],
        ['n2', 0.769805921, 0.061355308, 0.314826137, 0.855857651, 0.653550539, 0.772500773],
        ['n3', 0.158739038, 0.085078711, 0.844664253, 0.21630142, 0.912931341, 0.735138313],
    ]
    r = matDiff(matOld, matNew, { mode: 'matrix' })
    console.log('diff', r.diff)
    // => diff [
    //   {
    //     count: 1,
    //     added: false,
    //     removed: false,
    //     value: '0∶1⟋1∶0.974848293⟋2∶0.791303871⟋3∶0.716898185⟋4∶0.506002098⟋5∶0.137888903⟋6∶0.626724085⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: true,
    //     value: '0∶2⟋1∶-0.529408622⟋2∶0.839882385⟋3∶0.663059856⟋4∶0.49047221⟋5∶123⟋6∶0.395763265⟋7∶0.866151835⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶2⟋1∶-0.529408622⟋2∶0.1839882385⟋3∶0.663059856⟋4∶0.49047221⟋5∶0.395763265⟋6∶45.6⟋7∶0.866151835⟋\n'
    //   },
    //   {
    //     count: 3,
    //     added: false,
    //     removed: false,
    //     value: '0∶3⟋1∶-0.10320217⟋2∶0.475514539⟋3∶0.969205779⟋4∶0.711250309⟋5∶0.153847069⟋6∶0.410092395⟋\n' +
    //       '0∶4⟋1∶-0.121479865⟋2∶0.486179086⟋3∶0.481023842⟋4∶0.467410582⟋5∶0.42602231⟋6∶0.849701641⟋\n' +
    //       '0∶5⟋1∶0.757346003⟋2∶0.697242433⟋3∶0.67532802⟋4∶0.174644416⟋5∶0.045652267⟋6∶0.397104668⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: true,
    //     value: '0∶6⟋1∶0.663032731⟋2∶0.259252779⟋3∶0.566177431⟋4∶0.679637706⟋5∶0.377814487⟋6∶0.400248119⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: false,
    //     value: '0∶7⟋1∶0.72721374⟋2∶0.263793391⟋3∶0.167895215⟋4∶0.794808602⟋5∶0.107070584⟋6∶0.011822872⟋\n' +
    //       '0∶8⟋1∶0.247416664⟋2∶0.360426795⟋3∶0.014346373⟋4∶0.000469616⟋5∶0.4082693⟋6∶0.913806611⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: true,
    //     value: '0∶9⟋1∶0.345880037⟋2∶0.167996664⟋3∶0.711054429⟋4∶0.363177921⟋5∶0.206849994⟋6∶0.636855344⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: true,
    //     removed: false,
    //     value: '0∶9⟋1∶0.345880037⟋2∶0.167996664⟋3∶0.711054429⟋4∶0.363173478⟋5∶0.636855344⟋\n'
    //   },
    //   {
    //     count: 1,
    //     added: false,
    //     removed: false,
    //     value: '0∶10⟋1∶0.576739457⟋2∶0.324665077⟋3∶0.973218005⟋4∶0.883927423⟋5∶0.176906125⟋6∶0.20087887⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: true,
    //     value: '0∶11⟋1∶1.2⟋\n0∶12⟋1∶23.5⟋\n'
    //   },
    //   {
    //     count: 2,
    //     added: false,
    //     removed: false,
    //     value: '0∶13⟋1∶0.504421248⟋2∶0.984003751⟋3∶0.32549507⟋4∶0.987090751⟋5∶0.192745589⟋6∶0.735133561⟋\n' +
    //       '0∶14⟋1∶0.273214614⟋2∶0.083431884⟋3∶0.565146092⟋4∶0.935388666⟋5∶0.637675154⟋6∶0.523815661⟋\n'
    //   },
    //   {
    //     count: 3,
    //     added: true,
    //     removed: false,
    //     value: '0∶n1⟋1∶0.944492151⟋2∶0.89950443⟋3∶0.182709318⟋4∶0.892820757⟋5∶0.709746901⟋6∶0.097385354⟋\n' +
    //       '0∶n2⟋1∶0.769805921⟋2∶0.061355308⟋3∶0.314826137⟋4∶0.855857651⟋5∶0.653550539⟋6∶0.772500773⟋\n' +
    //       '0∶n3⟋1∶0.158739038⟋2∶0.085078711⟋3∶0.844664253⟋4∶0.21630142⟋5∶0.912931341⟋6∶0.735138313⟋\n'
    //   }
    // ]
    for (let i = 0; i < r.dfs.length; i++) {
        let df = r.dfs[i]
        console.log(i, 'df', df)
    }
    // => 0 df [
    //   { p: '', k: 0, vo: '1', vn: '' },
    //   { p: '', k: 1, vo: '0.974848293', vn: '' },
    //   { p: '', k: 2, vo: '0.791303871', vn: '' },
    //   { p: '', k: 3, vo: '0.716898185', vn: '' },
    //   { p: '', k: 4, vo: '0.506002098', vn: '' },
    //   { p: '', k: 5, vo: '0.137888903', vn: '' },
    //   { p: '', k: 6, vo: '0.626724085', vn: '' }
    // ]
    // 1 df [
    //   { p: '', k: 0, vo: '2', vn: '' },
    //   { p: '', k: 1, vo: '-0.529408622', vn: '' },
    //   { p: 'modify', k: 2, vo: '0.839882385', vn: '0.1839882385' },
    //   { p: '', k: 3, vo: '0.663059856', vn: '' },
    //   { p: '', k: 4, vo: '0.49047221', vn: '' },
    //   { p: 'modify', k: 5, vo: '123', vn: '0.395763265' },
    //   { p: 'modify', k: 6, vo: '0.395763265', vn: '45.6' },
    //   { p: '', k: 7, vo: '0.866151835', vn: '' }
    // ]
    // 2 df [
    //   { p: '', k: 0, vo: '3', vn: '' },
    //   { p: '', k: 1, vo: '-0.10320217', vn: '' },
    //   { p: '', k: 2, vo: '0.475514539', vn: '' },
    //   { p: '', k: 3, vo: '0.969205779', vn: '' },
    //   { p: '', k: 4, vo: '0.711250309', vn: '' },
    //   { p: '', k: 5, vo: '0.153847069', vn: '' },
    //   { p: '', k: 6, vo: '0.410092395', vn: '' }
    // ]
    // 3 df [
    //   { p: '', k: 0, vo: '4', vn: '' },
    //   { p: '', k: 1, vo: '-0.121479865', vn: '' },
    //   { p: '', k: 2, vo: '0.486179086', vn: '' },
    //   { p: '', k: 3, vo: '0.481023842', vn: '' },
    //   { p: '', k: 4, vo: '0.467410582', vn: '' },
    //   { p: '', k: 5, vo: '0.42602231', vn: '' },
    //   { p: '', k: 6, vo: '0.849701641', vn: '' }
    // ]
    // 4 df [
    //   { p: '', k: 0, vo: '5', vn: '' },
    //   { p: '', k: 1, vo: '0.757346003', vn: '' },
    //   { p: '', k: 2, vo: '0.697242433', vn: '' },
    //   { p: '', k: 3, vo: '0.67532802', vn: '' },
    //   { p: '', k: 4, vo: '0.174644416', vn: '' },
    //   { p: '', k: 5, vo: '0.045652267', vn: '' },
    //   { p: '', k: 6, vo: '0.397104668', vn: '' }
    // ]
    // 5 df [
    //   { p: 'remove', k: 0, vo: '6', vn: '' },
    //   { p: 'remove', k: 1, vo: '0.663032731', vn: '' },
    //   { p: 'remove', k: 2, vo: '0.259252779', vn: '' },
    //   { p: 'remove', k: 3, vo: '0.566177431', vn: '' },
    //   { p: 'remove', k: 4, vo: '0.679637706', vn: '' },
    //   { p: 'remove', k: 5, vo: '0.377814487', vn: '' },
    //   { p: 'remove', k: 6, vo: '0.400248119', vn: '' }
    // ]
    // 6 df [
    //   { p: '', k: 0, vo: '7', vn: '' },
    //   { p: '', k: 1, vo: '0.72721374', vn: '' },
    //   { p: '', k: 2, vo: '0.263793391', vn: '' },
    //   { p: '', k: 3, vo: '0.167895215', vn: '' },
    //   { p: '', k: 4, vo: '0.794808602', vn: '' },
    //   { p: '', k: 5, vo: '0.107070584', vn: '' },
    //   { p: '', k: 6, vo: '0.011822872', vn: '' }
    // ]
    // 7 df [
    //   { p: '', k: 0, vo: '8', vn: '' },
    //   { p: '', k: 1, vo: '0.247416664', vn: '' },
    //   { p: '', k: 2, vo: '0.360426795', vn: '' },
    //   { p: '', k: 3, vo: '0.014346373', vn: '' },
    //   { p: '', k: 4, vo: '0.000469616', vn: '' },
    //   { p: '', k: 5, vo: '0.4082693', vn: '' },
    //   { p: '', k: 6, vo: '0.913806611', vn: '' }
    // ]
    // 8 df [
    //   { p: '', k: 0, vo: '9', vn: '' },
    //   { p: '', k: 1, vo: '0.345880037', vn: '' },
    //   { p: '', k: 2, vo: '0.167996664', vn: '' },
    //   { p: '', k: 3, vo: '0.711054429', vn: '' },
    //   { p: 'modify', k: 4, vo: '0.363177921', vn: '0.363173478' },
    //   { p: 'modify', k: 5, vo: '0.206849994', vn: '0.636855344' },
    //   { p: 'remove', k: 6, vo: '0.636855344', vn: '' }
    // ]
    // 9 df [
    //   { p: '', k: 0, vo: '10', vn: '' },
    //   { p: '', k: 1, vo: '0.576739457', vn: '' },
    //   { p: '', k: 2, vo: '0.324665077', vn: '' },
    //   { p: '', k: 3, vo: '0.973218005', vn: '' },
    //   { p: '', k: 4, vo: '0.883927423', vn: '' },
    //   { p: '', k: 5, vo: '0.176906125', vn: '' },
    //   { p: '', k: 6, vo: '0.20087887', vn: '' }
    // ]
    // 10 df [
    //   { p: 'remove', k: 0, vo: '11', vn: '' },
    //   { p: 'remove', k: 1, vo: '1.2', vn: '' }
    // ]
    // 11 df [
    //   { p: 'remove', k: 0, vo: '12', vn: '' },
    //   { p: 'remove', k: 1, vo: '23.5', vn: '' }
    // ]
    // 12 df [
    //   { p: '', k: 0, vo: '13', vn: '' },
    //   { p: '', k: 1, vo: '0.504421248', vn: '' },
    //   { p: '', k: 2, vo: '0.984003751', vn: '' },
    //   { p: '', k: 3, vo: '0.32549507', vn: '' },
    //   { p: '', k: 4, vo: '0.987090751', vn: '' },
    //   { p: '', k: 5, vo: '0.192745589', vn: '' },
    //   { p: '', k: 6, vo: '0.735133561', vn: '' }
    // ]
    // 13 df [
    //   { p: '', k: 0, vo: '14', vn: '' },
    //   { p: '', k: 1, vo: '0.273214614', vn: '' },
    //   { p: '', k: 2, vo: '0.083431884', vn: '' },
    //   { p: '', k: 3, vo: '0.565146092', vn: '' },
    //   { p: '', k: 4, vo: '0.935388666', vn: '' },
    //   { p: '', k: 5, vo: '0.637675154', vn: '' },
    //   { p: '', k: 6, vo: '0.523815661', vn: '' }
    // ]
    // 14 df [
    //   { p: 'add', k: 0, vo: 'n1', vn: '' },
    //   { p: 'add', k: 1, vo: '0.944492151', vn: '' },
    //   { p: 'add', k: 2, vo: '0.89950443', vn: '' },
    //   { p: 'add', k: 3, vo: '0.182709318', vn: '' },
    //   { p: 'add', k: 4, vo: '0.892820757', vn: '' },
    //   { p: 'add', k: 5, vo: '0.709746901', vn: '' },
    //   { p: 'add', k: 6, vo: '0.097385354', vn: '' }
    // ]
    // 15 df [
    //   { p: 'add', k: 0, vo: 'n2', vn: '' },
    //   { p: 'add', k: 1, vo: '0.769805921', vn: '' },
    //   { p: 'add', k: 2, vo: '0.061355308', vn: '' },
    //   { p: 'add', k: 3, vo: '0.314826137', vn: '' },
    //   { p: 'add', k: 4, vo: '0.855857651', vn: '' },
    //   { p: 'add', k: 5, vo: '0.653550539', vn: '' },
    //   { p: 'add', k: 6, vo: '0.772500773', vn: '' }
    // ]
    // 16 df [
    //   { p: 'add', k: 0, vo: 'n3', vn: '' },
    //   { p: 'add', k: 1, vo: '0.158739038', vn: '' },
    //   { p: 'add', k: 2, vo: '0.085078711', vn: '' },
    //   { p: 'add', k: 3, vo: '0.844664253', vn: '' },
    //   { p: 'add', k: 4, vo: '0.21630142', vn: '' },
    //   { p: 'add', k: 5, vo: '0.912931341', vn: '' },
    //   { p: 'add', k: 6, vo: '0.735138313', vn: '' }
    // ]
}
Parameters:
Name Type Attributes Default Description
matOld Array

輸入原始數據二維陣列

matNew Array

輸入更新數據二維陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
dls String <optional>
'⟋'

輸入若數據為矩陣時各儲存格分隔字串,預設'⟋'

dlq String <optional>
'∶'

輸入若數據為矩陣時各儲存格欄位指標分隔字串,預設'∶'

Returns:

回傳比對結果物件,回傳鍵值diff、dfrs與dfs,其中diff一樣為比對原始結果字串,dfrs為各列比對結果陣列,dfs為各儲存格比對結果陣列

Type
Object

(static) matat(mat, istart, iendopt) → {Array}

Description:
  • 由二維陣列mat提取資料。若istart與iend有效則提取istart~iend欄位,而若僅istart有效則提取istart欄位

    Unit Test: Github

Source:
Example
console.log(matat([['a', 'b', '12.34'], [12, 34.56, 'abc']], 1))
// => [['b'], [34.56]]

console.log(matat([['a', 'b', '12.34'], [12, 34.56, 'abc']], 1, 2))
// => [['b', '12.34'], [34.56, 'abc']]
Parameters:
Name Type Attributes Default Description
mat Array

輸入要被提取的資料陣列

istart Integer

輸入起始的欄位指標整數

iend Integer <optional>
istart

輸入結束的欄位指標整數,若不輸入則等同於istart

Returns:

回傳提取的資料陣列

Type
Array

(static) now2str() → {String}

Description:
  • 取得目前至秒時間

    Unit Test: Github

Source:
Example
console.log(now2str())
// => dayjs().format('YYYY-MM-DDTHH:mm:ssZ') //use dayjs or moment
Returns:

回傳目前至秒時間字串

Type
String

(static) now2strp() → {String}

Description:
  • 取得目前至秒時間,產生無特殊字串之時間戳

    Unit Test: Github

Source:
Example
console.log(now2strp())
// => dayjs().format('YYYYMMDDHHmmss') //use dayjs or moment
Returns:

回傳目前至秒時間字串

Type
String

(static) nowDay2str() → {String}

Description:
  • 取得目前至日時間

    Unit Test: Github

Source:
Example
console.log(nowDay2str())
// => dayjs().format('YYYY-MM-DD') //use dayjs or moment
Returns:

回傳目前至日時間字串

Type
String

(static) nowms2str() → {String}

Description:
  • 取得目前至毫秒時間

    Unit Test: Github

Source:
Example
console.log(nowms2str())
// => dayjs().format('YYYY-MM-DDTHH:mm:ss.SSSZ') //use dayjs or moment
Returns:

回傳目前至毫秒時間字串

Type
String

(static) nowms2strp() → {String}

Description:
  • 取得目前至毫秒時間,產生無特殊字串之時間戳

    Unit Test: Github

Source:
Example
console.log(nowms2strp())
// => dayjs().format('YYYYMMDDHHmmssSSS') //use dayjs or moment
Returns:

回傳目前至毫秒時間字串

Type
String

(static) o2j(v, bFormatopt, optopt) → {String|Object}

Description:
  • 任意資料轉json文字

    Unit Test: Github

Source:
Example
console.log(o2j([1, '3', 'abc']))
// => '[1,"3","abc"]'

console.log(o2j({ a: 12.34, b: 'abc' }))
// => '{"a":12.34,"b":"abc"}'

console.log(o2j({ a: 12.34, b: 'abc' }, true))
// => {
  "a": 12.34,
  "b": "abc"
}
Parameters:
Name Type Attributes Default Description
v *

輸入任意資料;Error依JSON原生只保留可列舉自有欄位(一般Error為{}),記錄錯誤請先以getErrorMessage轉字串

bFormat Boolean <optional>
false

輸入是否格式化布林值,預設為false

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳json格式字串,輸入為undefined或序列化失敗(如含BigInt、循環參照)時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) obj2b64(data, optopt) → {String|Object}

Description:
  • 任意資料轉base64字串

    Unit Test: Github

Source:
Example
console.log(obj2b64({ a: 1 }))
// => 'eyJhIjoxfQ=='

console.log(obj2b64(undefined, { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'o2j: invalid v' }
Parameters:
Name Type Attributes Default Description
data *

輸入任意資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳base64字串,輸入為undefined或序列化失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) obj2pb64(data, key, optopt) → {String|Object}

Description:
  • 任意資料由key進行AES加密轉為為base64

    Unit Test: Github

Source:
Example
let key = '1234567890abcdef'
console.log(obj2pb64([1, '3', 'abc'], key))
// => U2FsdGVkX1+yPgYSW6mKi1VqyslaSKYHKRaw8Pu4QduzHggojRZA6b/T/oB7ltXf (is random)
Parameters:
Name Type Attributes Default Description
data *

輸入任意資料

key String

輸入加密key

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳加密base64字串,data為undefined或key非有效字串時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) obj2str(data, extopt, optopt) → {String|Object}

Description:
  • 任意資料轉字串,可支援內含Uint8Array與Uint16Array數據

    Unit Test: Github

Source:
Example
let o = {
    a: 'abc',
    b: 12.3,
    u8a: new Uint8Array([66, 97, 115]),
    u16a: new Uint16Array([11, 79, 6]),
}
console.log(obj2str(o))
// => '{"a":"abc","b":12.3,"u8a":"[Uint8Array]::QmFz","u16a":{"0":11,"1":79,"2":6}}'

console.log(obj2str(o, ['Uint8Array', 'Uint16Array']}))
// => '{"a":"abc","b":12.3,"u8a":"[Uint8Array]::QmFz","u16a":"[Uint16Array]::CwBPAAYA"}'
Parameters:
Name Type Attributes Default Description
data *

輸入任意資料,為undefined時回傳空字串;其中之真Error(含子類別)轉為'名稱: 訊息'字串,同obj2stru8arr

ext String | Array <optional>
'Uint8Array'

輸入擴充數據種類字串或陣列,非字串亦非陣列時回退為'Uint8Array',預設'Uint8Array'

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳轉換後字串,data為undefined或無法轉為JSON時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) obj2stru8arr(data, optopt) → {Object}

Description:
  • 物件資料轉字串與Unit8Array,物件內可含Unit8Array數據,適用於大檔。

    通過JSON序列化物件內非Unit8Array數據成為字串,另分拆Unit8Array數據出來回傳,兩者間通過指標關聯,主要為避免序列化大型Unit8Array數據造成效能或記憶體不足問題

    因以JSON.stringify序列化,下列型別會靜默失真且不會拋錯,須由呼叫端自行避免:NaN與Infinity轉為null;值為undefined、函數或Symbol者該鍵會消失(若為唯一鍵則results為'{}');Map、Set、RegExp轉為{};Date轉為ISO字串;稀疏陣列之空洞補為null;超出Number精度之大整數會失精。另Error物件(含子類別、DOMException、跨realm之Error)由本函數以toString轉為字串(toString為Object預設或不可呼叫者以Error.prototype.toString之語意得'名稱: 訊息',其餘欄位不保留);未繼承Error之物件(含恰帶字串name與message之資料類別或自訂錯誤類別)照一般物件序列化,保留其可列舉之自有欄位

    Unit Test: Github

Source:
Example
let data = {
    a: 123,
    b: 45.67,
    c: 'l1-測試中文',
    d: {
        da: 123,
        db: 45.67,
        dc: 'l2-測試中文',
        dd: ['a', 'xyz', 321, 76.54],
        de: new Uint8Array([66, 97, 115]),
    },
}
let r = obj2stru8arr(data)
console.log(r)
// => {
//     results: '{"a":123,"b":45.67,"c":"l1-測試中文","d":{"da":123,"db":45.67,"dc":"l2-測試中文","dd":["a","xyz",321,76.54],"de":"[BlazeForUint8Array]::0"}}',
//     binarys: [ Uint8Array [ 66, 97, 115 ] ]
// }

console.log(obj2stru8arr({ id: 1n }, { returnWithStateAndMsg: true }))
// => {
//     state: 'error',
//     msg: 'TypeError: Do not know how to serialize a BigInt'
// }
Parameters:
Name Type Attributes Default Description
data Object | Array

輸入物件或陣列資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳物件,results欄位儲存物件內非序列化文字,binarys欄位儲存各Unit8Array數據;二進位數據於results內以'[BlazeForUint8Array]::'一類標記代表,應用字串若恰與標記格式完整相同則會被前置'[BlazeForPreventEscape]'跳脫,供解碼端還原時區分;輸入非物件非陣列、空物件空陣列或序列化失敗(如含BigInt、循環參照)時回傳{ results: '', binarys: [] };若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Object

(static) obj2u8arr(data, optopt) → {Uint8Array|Object}

Description:
  • 物件或陣列資料轉Uint8Array

    因底層以JSON.stringify序列化,下列型別會靜默失真且不會拋錯,須由呼叫端自行避免:NaN與Infinity轉為null;值為undefined、函數或Symbol者該鍵會消失;Map、Set、RegExp轉為{};Date轉為ISO字串;稀疏陣列之空洞補為null;超出Number精度之大整數會失精。另BigInt與循環參照會使序列化失敗,回傳空Uint8Array

    Unit Test: Github

Source:
Example
let data = {
    a: [123, 45.67, 'test中文'],
    b: {
        c: new Uint8Array([66, 97, 115]),
    },
}
let u8a = obj2u8arr(data)
console.log(u8a)
// => Uint8Array [
//     64,  24,   0,   0,   0,  0,  0,   0,  91,  54,  54,  44,
//     51,  93, 123,  34,  97, 34, 58,  91,  49,  50,  51,  44,
//     52,  53,  46,  54,  55, 44, 34, 116, 101, 115, 116, 228,
//    184, 173, 230, 150, 135, 34, 93,  44,  34,  98,  34,  58,
//    123,  34,  99,  34,  58, 34, 91,  66, 108,  97, 122, 101,
//     70, 111, 114,  85, 105, 110, 116, 56,  65, 114, 114,  97,
//    121,  93,  58,  58,  48, 34, 125, 125,  66,  97, 115
// ]

console.log(obj2u8arr({ id: 1n }, { returnWithStateAndMsg: true }))
// => {
//     state: 'error',
//     msg: 'obj2stru8arr: TypeError: Do not know how to serialize a BigInt'
// }
Parameters:
Name Type Attributes Default Description
data Object | Array

輸入物件或陣列資料,物件內可支援Uint8Array、Uint16Array、ArrayBuffer,三者皆以逐位元組方式打包,並由標記記錄原型別,故u8arr2obj可原型別原數值還原

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Uint8Array,輸入非有效物件或陣列、或序列化失敗(如含BigInt、循環參照)時回傳空Uint8Array;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Uint8Array | Object

(static) objSortBy(obj, fun) → {Object}

Description:
  • 對物件內各鍵進行排序,通過傳入排序函數取得各物件值或鍵,回傳可排序的數字或字串,則可進行對物件的鍵排序

    排序規則同arrSort:數字與數字字串依數值排序排在最前,其他字串其次,無法轉數字或無法解析者(空字串、純空白字串、null、undefined、NaN、物件等)放最末並維持原順序

    Unit Test: Github

Source:
Example
let obj1 = {
    'a': 3,
    'b': 2,
    'c': 1,
}
let robj1 = objSortBy(obj1, (v, k) => {
    return v
})
console.log(robj1)
// => { c: 1, b: 2, a: 3 }

let obj2 = {
    'x2': 2,
    'x1': 1,
    'x3': 3,
}
let robj2 = objSortBy(obj2, (v, k) => {
    return k
})
console.log(robj2)
// => { x1: 1, x2: 2, x3: 3 }
Parameters:
Name Type Description
obj Object

輸入物件資料

fun function

輸入回調的排序函數,將傳入(v,k)代表各物件的值與鍵,回傳排序用的值,可為數字或字串

Returns:

回傳排序後物件

Type
Object

(static) objSortByKeys(obj, fun) → {Object}

Description:
  • 對物件內各鍵進行排序,通過傳入指定鍵值陣列,可基於物件各鍵位於指定鍵值陣列之位置進行排序

    Unit Test: Github

Source:
Example
let obj1 = {
    'x2': 2,
    'x1': 1,
    'x3': 3,
}
let arr1 = ['x1', 'x2', 'x3']
let robj1 = objSortByKeys(obj1, arr1)
console.log(robj1)
// => { x1: 1, x2: 2, x3: 3 }

let obj2 = {
    'x2': 2,
    'x111': 1,
    'x33': 3,
}
let arr2 = ['x111', 'x2', 'x33']
let robj2 = objSortByKeys(obj2, arr2)
console.log(robj2)
// => { x111: 1, x2: 2, x33: 3 }
Parameters:
Name Type Description
obj Object

輸入物件資料

fun function

輸入回調的排序函數,將傳入(v,k)代表各物件的值與鍵,回傳排序用的值,可為數字或字串

Returns:

回傳排序後物件

Type
Object

(static) oo(o) → {*}

Description:
  • 暴力cloneDeep物件

    Unit Test: Github

Source:
Example
console.log(oo([1, '3', 'abc']))
// => [1, '3', 'abc']

console.log(oo([1, '3', 'abc', function(){}]))
// => [1, '3', 'abc', null]

console.log(oo({ a: 12.34, b: 'abc' }))
// => { a: 12.34, b: 'abc' }

console.log(oo({ a: 12.34, b: 'abc', c: function(){} }))
// => { a: 12.34, b: 'abc' }
Parameters:
Name Type Description
o *

輸入任意資料

Returns:

回傳任意資料

Type
*
Description:
  • 前端開啟連結

    Unit Test: Github

Source:
Example
need test in browser

let url = 'https://www.google.com/'
openLink(url)
Parameters:
Name Type Description
url String

(static) pb642obj(key, b64, optopt) → {*}

Description:
  • base64解密取得原始資料

    Unit Test: Github

Source:
Example
let key = '1234567890abcdef'
let str = U2FsdGVkX1+yPgYSW6mKi1VqyslaSKYHKRaw8Pu4QduzHggojRZA6b/T/oB7ltXf //(is random)
console.log(pb642obj(str, key))
// => [1, '3', 'abc']
Parameters:
Name Type Attributes Default Description
key String

輸入加密key

b64 String

輸入加密base64字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳任意物件,b64或key非有效字串時回傳空字串,解密失敗時回傳undefined;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
*

(static) pmChain(pms, initial) → {Promise}

Description:
  • 傳入initial初始值並循序執行Promise陣列

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test1() {
        return new Promise((resolve, reject) => {
            let ms = []

            let pm1 = function(v) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log('resolve pm1' + v)
                        ms.push('resolve pm1' + v)
                        resolve('pm1' + v)
                    }, 100)
                })
            }
            let pm2 = function(v) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log('resolve pm2' + v)
                        ms.push('resolve pm2' + v)
                        resolve('pm2' + v)
                    }, 150)
                })
            }
            let pm3 = function(v) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log('resolve pm3' + v)
                        ms.push('resolve pm3' + v)
                        resolve('pm3' + v)
                    }, 50)
                })
            }

            pmChain([pm1, pm2, pm3], '*')
                .then((msg) => {
                    console.log('t1 then: ', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch((msg) => {
                    console.log('t1 catch: ', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(() => {
                    resolve(ms)
                })

        })
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // resolve pm1*
    // resolve pm2pm1*
    // resolve pm3pm2pm1*
    // t1 then:  pm3pm2pm1*
    // ["resolve pm1*","resolve pm2pm1*","resolve pm3pm2pm1*","t1 then: pm3pm2pm1*"]

    async function test2() {
        return new Promise((resolve, reject) => {
            let ms = []

            let pm1 = function(v) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log('resolve pm1' + v)
                        ms.push('resolve pm1' + v)
                        resolve('pm1' + v)
                    }, 100)
                })
            }
            //pm2為reject
            let pm2 = function(v) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log('reject pm2' + v)
                        ms.push('reject pm2' + v)
                        reject('pm2' + v)
                    }, 150)
                })
            }
            let pm3 = function(v) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log('resolve pm3' + v)
                        ms.push('resolve pm3' + v)
                        resolve('pm3' + v)
                    }, 50)
                })
            }

            pmChain([pm1, pm2, pm3], '*')
                .then((msg) => {
                    console.log('t1 then: ', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch((msg) => {
                    console.log('t1 catch: ', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(() => {
                    resolve(ms)
                })

        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // resolve pm1*
    // reject pm2pm1*
    // t1 catch:  pm2pm1*
    // ["resolve pm1*","reject pm2pm1*","t1 catch: pm2pm1*"]

}
topAsync().catch(() => {})
Parameters:
Name Type Default Description
pms Array

輸入函數陣列,各函數依序接收前一結果,回傳一般值或Promise(同Promise之then);元素須為函數,傳入Promise(而非回傳Promise之函數)將被忽略,該Promise之reject亦不會被處理;元素於進入時即取值,呼叫後改動pms不影響執行,讀取元素拋錯時以該錯誤reject

initial * null

輸入循序執行函數陣列的初始值

Returns:

回傳Promise,resolve回傳成功結果,reject回傳失敗結果

Type
Promise

(static) pmConvertResolve(fn) → {function}

Description:
  • 將Promise函式的resolve與reject皆轉為resolve

    回傳之Promise只會resolve且不產生未處理之rejection:fn同步拋錯(含拋出null)與其Promise被reject皆為error,掛載then或讀取拒絕值時拋錯者(如標籤為Promise卻無可用之then、已撤銷之Proxy、會拋錯之getter)亦為error。惟fn回傳永不settle之Promise或永不呼叫回呼之thenable時亦不settle,需逾時者以pmTimeout包裝

    取消依拒絕值之形狀判定(其reason為'cancelled'),原拒絕值不保留,同步拋出之值不判取消。拒絕值來自外部(應用端、伺服器)而不屬wsemi之取消家族(pmQueue之takeLast、pmThrottle、pmInvResolve)時,勿以本函數分類,否則形似{ reason: 'cancelled' }之外部值會被改判為取消

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test1() {
        return new Promise((resolve, reject) => {
            let ms = []

            function fun1(c) {
                return new Promise((resolve, reject) => {
                    setTimeout(function() {
                        console.log('resolve fun1: ' + c)
                        ms.push('resolve fun1: ' + c)
                        resolve('fun1: ' + c)
                    }, 1)
                })
            }

            pmConvertResolve(fun1)('abc')
                .then((msg) => {
                    console.log('t1 then: ', msg)
                    ms.push({ mode: 't1 then', msg })
                })
                .catch((msg) => {
                    console.log('t1 catch: ', msg)
                    ms.push({ mode: 't1 catch', msg })
                })
                .finally(() => {
                    resolve(ms)
                })

        })
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // resolve fun1: abc
    // t1 then:  { state: 'success', msg: 'fun1: abc' }
    // ["resolve fun1: abc",{"mode":"t1 then","msg":{"state":"success","msg":"fun1: abc"}}]

    async function test2() {
        return new Promise((resolve, reject) => {
            let ms = []

            function fun1(c) {
                return new Promise((resolve, reject) => {
                    setTimeout(function() {
                        console.log('reject fun1: ' + c)
                        ms.push('reject fun1: ' + c)
                        reject('fun1: ' + c)
                    }, 1)
                })
            }

            pmConvertResolve(fun1)('abc')
                .then((msg) => {
                    console.log('t1 then: ', msg)
                    ms.push({ mode: 't1 then', msg })
                })
                .catch((msg) => {
                    console.log('t1 catch: ', msg)
                    ms.push({ mode: 't1 catch', msg })
                })
                .finally(() => {
                    resolve(ms)
                })

        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // reject fun1: abc
    // { state: 'error', msg: 'fun1: abc' }
    // ["reject fun1: abc",{"mode":"t1 then","msg":{"state":"error","msg":"fun1: abc"}}]

    async function test3() {
        return new Promise((resolve, reject) => {
            let ms = []

            function fun1(p1, p2) {
                return new Promise((resolve, reject) => {
                    setTimeout(function() {
                        console.log('resolve fun1: ' + p1 + ', ' + p2)
                        ms.push('resolve fun1: ' + p1 + ', ' + p2)
                        resolve('fun1: ' + p1 + ', ' + p2)
                    }, 1)
                })
            }

            pmConvertResolve(fun1)('abc', 'def')
                .then((msg) => {
                    console.log('t1 then: ', msg)
                    ms.push({ mode: 't1 then', msg })
                })
                .catch((msg) => {
                    console.log('t1 catch: ', msg)
                    ms.push({ mode: 't1 catch', msg })
                })
                .finally(() => {
                    resolve(ms)
                })

        })
    }
    console.log('test3')
    let r3 = await test3()
    console.log(JSON.stringify(r3))
    // test3
    // resolve fun1: abc, def
    // t1 then:  { state: 'success', msg: 'fun1: abc, def' }
    // ["resolve fun1: abc, def",{"mode":"t1 then","msg":{"state":"success","msg":"fun1: abc, def"}}]

    async function test4() {
        return new Promise((resolve, reject) => {
            let ms = []

            async function fun1(p1, p2) {
                console.log('resolve fun1: ' + p1 + ', ' + p2)
                return 'fun1: ' + p1 + ', ' + p2
            }

            pmConvertResolve(fun1)('abc', 'def')
                .then((msg) => {
                    console.log('t1 then: ', msg)
                    ms.push({ mode: 't1 then', msg })
                })
                .catch((msg) => {
                    console.log('t1 catch: ', msg)
                    ms.push({ mode: 't1 catch', msg })
                })
                .finally(() => {
                    resolve(ms)
                })

        })
    }
    console.log('test4')
    let r4 = await test4()
    console.log(JSON.stringify(r4))
    // test4
    // t1 then:  { state: 'success', msg: 'fun1: abc, def' }
    // ["resolve fun1: abc, def",{"mode":"t1 then","msg":{"state":"success","msg":"fun1: abc, def"}}]

}
topAsync().catch(() => {})
Parameters:
Name Type Description
fn function

輸入函數,可支援async與sync函數

Returns:

回傳包裝函數,呼叫後回傳Promise,只會resolve物件資料,物件欄位有state與msg,state可有success、error與cancelled。success之msg為回傳值或resolve值,error之msg為拋出值或reject值(判定拋錯時為該錯誤),cancelled代表reject回傳{ reason: 'cancelled' },其msg為空字串;fn非函數時於本函數呼叫當下同步拋出Error('fn is not a function')

Type
function

(static) pmDebounce(msopt, optopt) → {function}

Description:
  • 非同步函數進行防抖

    同時僅會執行一個佇列(非同步函數),到達防抖時間才會取最後一個佇列執行,待其執行完畢,若期間有新佇列進來則儲存,待執行完畢後取最後佇列出來執行

    防抖時間(ms)自最後一次調用起算,任務執行中進來之調用亦計入;每20ms偵測一次,任務執行完畢時若自最後一次調用起已閒置滿ms,即於下一次偵測執行最後之佇列,未滿則只再等剩餘時間(不因任務執行完畢而重新起算)

    任務之結果經pm2resolve正規化,任務同步拋錯或其Promise被reject皆視為執行完畢,以console.log記錄而不外拋(不產生未處理之rejection),不影響後續佇列

    Unit Test: Github

Source:
Example
let dbc = pmDebounce(300)

let i = 0
let d = 100
if (true) {
    console.log(i, 'ms')
    let t = setInterval(() => { //dbc內時間比較快
        i += d
        if (i % 100 === 0) {
            console.log(i, 'ms')
        }
        if (i >= 2000) {
            clearInterval(t)
        }
    }, d)
}

let ms = []

let n = 0
function test(rin) {
    console.log(i, 'ms', 'test in:', rin)
    ms.push(`test in: ${rin}`)
    return new Promise(function(resolve, reject) {
        setTimeout(() => {
            n++
            let rout = `${rin}:${n}`
            console.log(i, 'ms', 'test out:', rout)
            ms.push(`test out: ${rout}`)
            resolve(rout)
        }, 500)
    })
}

if (true) {

    setTimeout(() => {
        dbc(async(inp) => {
            return test(inp)
        }, 'a') //無阻攔時1->300發呆, 300->800執行
    }, 1)
    //1ms, a進入排程, 但a尚未發呆300ms, 故a無法執行
    setTimeout(() => {
        dbc(async(inp) => {
            return test(inp)
        }, 'b') //無阻攔時200->500發呆, 500->1000執行
    }, 200)
    //200ms, b進入排程, 同時排程前面還有a, 故只會取最末b出來判識(a被強制剔除), 因b尚未發呆完300ms故b無法執行
    setTimeout(() => {
        dbc(async(inp) => {
            return test(inp)
        }, 'c') //無阻攔時600->900發呆, 900->1400執行
    }, 600)
    //500ms, b已發呆完開始執行
    //600ms, c進入排程, 因b已於500ms開始執行(b執行為500->1000), c進入排程只能等待
    setTimeout(() => {
        dbc(async(inp) => {
            return test(inp)
        }, 'd') //無阻攔時900->1200發呆, 1200->1700執行
    }, 900)
    //900ms, d進入排程, 因b執行尚未結束, d雖可進入排程, 同時排程前面還有c, 故c與d皆為等待, 發呆時間自最後一次調用(d, 900ms)起算
    //1000ms, b已執行完, 取最末d出來判識(c被強制剔除), 此時d僅發呆100ms(900->1000)未滿300ms, 故再等剩餘200ms
    //1200ms, d已發呆滿300ms(900->1200), 開始執行(d執行為1200->1700)
    //1700ms, d執行完

}
//0 ms
//100 ms
//200 ms
//300 ms
//400 ms
//400 ms test in: b
//500 ms
//600 ms
//700 ms
//800 ms
//900 ms
//900 ms test out: b:1
//1000 ms
//1100 ms
//1200 ms
//1200 ms test in: d
//1300 ms
//1400 ms
//1500 ms
//1600 ms
//1700 ms
//1700 ms test out: d:2
//1800 ms
//1900 ms
//2000 ms
setTimeout(() => {
    console.log(ms)
    // => [ 'test in: b', 'test out: b:1', 'test in: d', 'test out: d:2' ]
}, 2200) //實測setInterval因多次執行會比較不準, 故顯示2000ms結束, 實際為2200ms之後
Parameters:
Name Type Attributes Default Description
ms Integer <optional>
300

輸入未有調用的時間區間,為正整數,預設300ms

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'push'時為任務排入佇列(含size為排入後之佇列長度),'start'時為開始偵測(每20ms檢查一次是否已超過ms未有調用),'run'時為已超過ms未有調用而即將執行最後之任務(含dropped為被捨棄之較早任務數),'done'時為被執行之任務已執行完畢(含ok為是否成功,其後才恢復可執行下一批),'stop'時為佇列已空而停止偵測;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響去除抖動,預設null

Returns:

回傳Function,輸入為非同步函數與其輸入,會推入佇列後等待執行,呼叫無回傳值,被執行任務之結果與錯誤皆不回傳給呼叫端,須由任務自行處理;輸入非函數時不排入佇列(不影響已排入之任務),以console.log記錄'func is not a function'

Type
function

(static) pmHook(fun, cbopt) → {function|null}

Description:
  • 掛勾非同步(Promise)函數,可監聽或修改Promise的輸出入訊號

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test1() {
        return new Promise((resolve, reject) => {
            let ms = []

            //使用resolve
            let pm = function (v1, v2) {
                return new Promise(function(resolve, reject) {
                    ms.push(`resolve: v1=${v1}, v2=${v2}`)
                    resolve(`resolve: v1=${v1}, v2=${v2}`)
                })
            }

            //針對before修改輸入
            let pmr = pmHook(pm, (msg) => {
                console.log('cb', msg)
                if (msg.mode === 'before') {
                    //arguments有兩個輸入故得分開改
                    msg.data[0] = '[modify input a]' + msg.data[0]
                    msg.data[1] = '[modify input b]' + msg.data[1]
                    return msg.data
                }
            })

            pmr('t1', 12.3)
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(function() {
                    resolve(ms)
                })

        })
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // cb { mode: 'before', data: [Arguments] { '0': 't1', '1': 12.3 } }
    // cb {
    //   mode: 'afterThen',
    //   data: 'resolve: v1=[modify input a]t1, v2=[modify input b]12.3'
    // }
    // t1 then resolve: v1=[modify input a]t1, v2=[modify input b]12.3
    // ["resolve: v1=[modify input a]t1, v2=[modify input b]12.3","t1 then: resolve: v1=[modify input a]t1, v2=[modify input b]12.3"]

    async function test2() {
        return new Promise((resolve, reject) => {
            let ms = []

            //使用resolve
            let pm = function (v1, v2) {
                return new Promise(function(resolve, reject) {
                    ms.push(`resolve: v1=${v1}, v2=${v2}`)
                    resolve(`resolve: v1=${v1}, v2=${v2}`)
                })
            }

            //針對afterThen修改輸出
            let pmr = pmHook(pm, (msg) => {
                console.log('cb', msg)
                if (msg.mode === 'afterThen') {
                    //arguments有兩個輸入故得分開改
                    msg.data = '[modify output]' + msg.data
                    return msg.data
                }
            })

            pmr('t1', 12.3)
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(function() {
                    resolve(ms)
                })

        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // cb { mode: 'before', data: [Arguments] { '0': 't1', '1': 12.3 } }
    // cb { mode: 'afterThen', data: 'resolve: v1=t1, v2=12.3' }
    // t1 then [modify output]resolve: v1=t1, v2=12.3
    // ["resolve: v1=t1, v2=12.3","t1 then: [modify output]resolve: v1=t1, v2=12.3"]

    async function test3() {
        return new Promise((resolve, reject) => {
            let ms = []

            //使用reject
            let pm = function (v1, v2) {
                return new Promise(function(resolve, reject) {
                    ms.push(`reject: v1=${v1}, v2=${v2}`)
                    reject(`reject: v1=${v1}, v2=${v2}`)
                })
            }

            //針對afterThen修改輸出, 但因使用reject故改不到
            let pmr = pmHook(pm, (msg) => {
                console.log('cb', msg)
                if (msg.mode === 'afterThen') {
                    //arguments有兩個輸入故得分開改
                    msg.data = '[modify output]' + msg.data
                    return msg.data
                }
            })

            pmr('t1', 12.3)
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(function() {
                    resolve(ms)
                })

        })
    }
    console.log('test3')
    let r3 = await test3()
    console.log(JSON.stringify(r3))
    // test3
    // cb { mode: 'before', data: [Arguments] { '0': 't1', '1': 12.3 } }
    // cb { mode: 'afterCatch', data: 'reject: v1=t1, v2=12.3' }
    // t1 catch reject: v1=t1, v2=12.3
    // ["reject: v1=t1, v2=12.3","t1 catch: reject: v1=t1, v2=12.3"]

    async function test4() {
        return new Promise((resolve, reject) => {
            let ms = []

            //使用reject
            let pm = function (v1, v2) {
                return new Promise(function(resolve, reject) {
                    ms.push(`reject: v1=${v1}, v2=${v2}`)
                    reject(`reject: v1=${v1}, v2=${v2}`)
                })
            }

            //針對afterCatch修改輸出
            let pmr = pmHook(pm, (msg) => {
                console.log('cb', msg)
                if (msg.mode === 'afterCatch') {
                    //arguments有兩個輸入故得分開改
                    msg.data = '[modify output]' + msg.data
                    return msg.data
                }
            })

            pmr('t1', 12.3)
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(function() {
                    resolve(ms)
                })

        })
    }
    console.log('test4')
    let r4 = await test4()
    console.log(JSON.stringify(r4))
    // test4
    // cb { mode: 'before', data: [Arguments] { '0': 't1', '1': 12.3 } }
    // cb { mode: 'afterCatch', data: 'reject: v1=t1, v2=12.3' }
    // t1 catch [modify output]reject: v1=t1, v2=12.3
    // ["reject: v1=t1, v2=12.3","t1 catch: [modify output]reject: v1=t1, v2=12.3"]

    async function test5() {
        return new Promise((resolve, reject) => {
            let ms = []

            //使用resolve, 此函數無輸入
            let pm = function () {
                return new Promise(function(resolve, reject) {
                    ms.push(`resolve`)
                    resolve(`resolve`)
                })
            }

            //針對afterThen修改輸出
            let pmr = pmHook(pm, (msg) => {
                console.log('cb', msg)
                if (msg.mode === 'afterThen') {
                    //arguments有兩個輸入故得分開改
                    msg.data = '[modify output]' + msg.data
                    return msg.data
                }
            })

            pmr()
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(function() {
                    resolve(ms)
                })

        })
    }
    console.log('test5')
    let r5 = await test5()
    console.log(JSON.stringify(r5))
    // test5
    // cb { mode: 'before', data: [Arguments] {} }
    // cb { mode: 'afterThen', data: 'resolve' }
    // t1 then [modify output]resolve
    // ["resolve","t1 then: [modify output]resolve"]

}
topAsync().catch(() => {})
Parameters:
Name Type Attributes Default Description
fun function

輸入非同步Promise函數

cb function <optional>
() => {}

輸入回調函數,預設()={},cb函數之輸入為監聽到的資訊物件,欄位有mode與data,mode可為'before'、'afterThen'、'afterCatch'字串,而data則代表非同步函數的輸入或輸出資訊。若想於cb函數修改回傳,則由cb函數的輸入修改完回傳即可。例如收到msg={mode:'before',data:'123'},將msg.data='abc',再return msg.data

Returns:

回傳包裝函數,呼叫後回傳Promise,resolve為回傳成功結果,reject為回傳失敗訊息;fun非函數時回傳null。fun同步拋錯視為reject,回傳一般值視為resolve;before之cb拋錯、fun拋錯或reject、afterThen之cb拋錯皆交afterCatch之cb後reject,afterCatch之cb拋錯則以該錯誤reject;惟fun回傳永不settle之Promise或永不呼叫回呼之thenable時亦不settle

Type
function | null

(static) pmHookReject(fun, cbopt) → {function|null}

Description:
  • 掛勾非同步(Promise)函數,可監聽或修改Promise的reject輸出數據

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test1() {
        return new Promise((resolve, reject) => {
            let ms = []

            let pm = function (v1, v2) {
                return new Promise(function(resolve, reject) {
                    ms.push(`reject: v1=${v1}, v2=${v2}`)
                    reject(`reject: v1=${v1}, v2=${v2}`)
                })
            }

            let pmr = pmHookReject(pm, (msg) => {
                console.log('cb: ' + msg)
                msg = '[modify catch]' + msg
                ms.push('cb: ' + msg)
                return msg
            })

            pmr('t1', 12.3)
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(function() {
                    resolve(ms)
                })

        })
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // cb: reject: v1=t1, v2=12.3
    // t1 catch [modify catch]reject: v1=t1, v2=12.3
    // ["reject: v1=t1, v2=12.3","cb: [modify catch]reject: v1=t1, v2=12.3","t1 catch: [modify catch]reject: v1=t1, v2=12.3"]

    async function test2() {
        return new Promise((resolve, reject) => {
            let ms = []

            let pm = function () {
                return new Promise(function(resolve, reject) {
                    ms.push(`reject`)
                    reject(`reject`)
                })
            }

            let pmr = pmHookReject(pm, (msg) => {
                console.log('cb: ' + msg)
                msg = '[modify catch]' + msg
                ms.push('cb: ' + msg)
                return msg
            })

            pmr() //測試無輸入
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + msg)
                })
                .finally(function() {
                    resolve(ms)
                })

        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // cb: reject
    // t1 catch [modify catch]reject
    // ["reject","cb: [modify catch]reject","t1 catch: [modify catch]reject"]

}
topAsync().catch(() => {})
Parameters:
Name Type Attributes Default Description
fun function

輸入非同步Promise函數

cb function <optional>
()=>{}

輸入回調函數,預設()={},cb函數之輸入為監聽reject到的資訊數據。若想於cb函數修改reject數據,則由cb函數的輸入修改完回傳即可。例如收到reject的msg為'abc',將msg='def'再return msg即可

Returns:

回傳包裝函數,呼叫後回傳Promise,resolve為回傳成功結果,reject為回傳失敗訊息;fun非函數時回傳null

Type
function | null

(static) pmIni() → {Promise}

Description:
  • 以Promise開啟鏈式

    Unit Test: Github

Source:
Example
pmIni()
    .then(function() {
        console.log('then')
        //code here
    })
Returns:

回傳Promise

Type
Promise

(static) pmInvResolve(pm, optopt) → {Promise}

Description:
  • 將pmConvertResolve回傳結果轉回原本Promise的resolve與reject

    Unit Test: Github

Source:
Example
async function test1() {
    let pmInp = genPm()
    pmInp.resolve({
        state: 'success',
        msg: 'abc',
    })
    let pmOut = pmInvResolve(pmInp)
    await pmOut
        .then((res) => {
            console.log('test1 then', res)
            // test1 then abc
        })
        .catch((res) => {
            console.log('test1 catch', res)
        })
}
test1()

async function test2() {
    let pmInp = genPm()
    pmInp.resolve({
        state: 'error',
        msg: 'abc',
    })
    let pmOut = pmInvResolve(pmInp)
    await pmOut
        .then((res) => {
            console.log('test2 then', res)
        })
        .catch((res) => {
            console.log('test2 catch', res)
            // test2 catch abc
        })
}
test2()

async function test3() {
    let pmInp = genPm()
    pmInp.resolve({
        state: 'cancelled',
        msg: 'abc',
    })
    let pmOut = pmInvResolve(pmInp)
    await pmOut
        .then((res) => {
            console.log('test3 then', res)
        })
        .catch((res) => {
            console.log('test3 catch', res)
            // test3 catch { reason: 'cancelled' }
        })
}
test3()

async function test4() {
    let pmInp = genPm()
    pmInp.resolve({
        data: {
            state: 'success',
            msg: 'abc',
        },
    })
    let pmOut = pmInvResolve(pmInp, { thenExtractData: true })
    await pmOut
        .then((res) => {
            console.log('test4 then', res)
            // test4 then abc
        })
        .catch((res) => {
            console.log('test4 catch', res)
        })
}
test4()
Parameters:
Name Type Attributes Default Description
pm Promise

輸入pmConvertResolve所轉出的Promise,其皆使用resolve回傳物件資料,物件欄位有state與msg;pm非Promise時同步拋出Error('pm is not a promise')

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
thenExtractData Boolean <optional>
false

輸入是否於then提取data欄位值布林值,主要用於轉換axios取得之pm,預設false

Returns:

回傳Promise,state為success時resolve其msg,為cancelled時reject { reason: 'cancelled' },其餘(含error、缺state或非結果物件)reject其msg;msg原樣傳遞(含undefined);輸入之pm被reject時原樣reject其值

Type
Promise

(static) pmKeyMutex() → {function}

Description:
  • 依key分組之非同步互斥鎖(per-key mutex),相同key之呼叫循序執行(排隊),不同key之呼叫互不影響可並行

    通過new建構後回傳run函數,run(key, fn)會將fn接在該key當前佇列尾端,等前面同key之fn皆執行完畢才輪到自己執行。可用於避免同一資源(以key識別)被並發存取,例如同一檔案、同一帳號、同一筆資料之非同步操作須序列化時

    各caller之成敗互相獨立:某個fn拋錯(reject)只會將錯誤傳回給該次呼叫者,不會阻擋同key後續排隊之caller執行。不同key各自獨立排隊,彼此並行。當某key之佇列全數結束(無排隊中亦無執行中),會自動清除該key之內部記錄,避免Map無限增長

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test1() {
        let ms = []
        let mx = pmKeyMutex()

        function fun(key, v, d) {
            console.log('call', key + '-' + v)
            ms.push('call ' + key + '-' + v)
            return new Promise(function(resolve) {
                setTimeout(function() {
                    console.log('resolve', key + '-' + v)
                    ms.push('resolve ' + key + '-' + v)
                    resolve('#' + v)
                }, d)
            })
        }

        //相同key 'A' 之 a1、a2 循序執行(a1完成才換a2), 不同key 'B' 之 b1 與 'A' 並行
        let pms = []
        pms.push(mx('A', () => fun('A', 'a1', 300)).then(r => ms.push('then A-a1: ' + r)))
        pms.push(mx('A', () => fun('A', 'a2', 100)).then(r => ms.push('then A-a2: ' + r)))
        pms.push(mx('B', () => fun('B', 'b1', 200)).then(r => ms.push('then B-b1: ' + r)))
        await Promise.all(pms)
        return ms
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // call A-a1
    // call B-b1
    // resolve B-b1
    // resolve A-a1
    // call A-a2
    // resolve A-a2
    // ["call A-a1","call B-b1","resolve B-b1","then B-b1: #b1","resolve A-a1","call A-a2","then A-a1: #a1","resolve A-a2","then A-a2: #a2"]

    async function test2() {
        let ms = []
        let mx = pmKeyMutex()

        function funOk(v, d) {
            return new Promise(function(resolve) {
                setTimeout(function() {
                    resolve('#' + v)
                }, d)
            })
        }
        function funErr(v, d) {
            return new Promise(function(resolve, reject) {
                setTimeout(function() {
                    reject('err-' + v)
                }, d)
            })
        }

        //同key 'K' 中 k2 拋錯, 只會將錯誤傳回k2之呼叫者, 不會阻擋後續排隊之k3執行
        let pms = []
        pms.push(mx('K', () => funOk('k1', 100)).then(r => ms.push('then k1: ' + r), e => ms.push('catch k1: ' + e)))
        pms.push(mx('K', () => funErr('k2', 100)).then(r => ms.push('then k2: ' + r), e => ms.push('catch k2: ' + e)))
        pms.push(mx('K', () => funOk('k3', 100)).then(r => ms.push('then k3: ' + r), e => ms.push('catch k3: ' + e)))
        await Promise.all(pms)
        return ms
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // ["then k1: #k1","catch k2: err-k2","then k3: #k3"]

}
topAsync().catch(() => {})
Returns:

回傳run函數,第1參數key為分組識別字串(相同key循序執行,不同key並行),第2參數fn為待執行之非同步函數(回傳Promise)。run回傳為Promise,resolve回傳fn成功結果而reject回傳fn失敗結果

Type
function

(static) pmMap(rs, fun, takeLimitopt, optopt) → {Promise}

Description:
  • Promise的map,可設定同時處理數量

    若輸入rs為資料陣列則fn需將數據處理並回傳Promise,若輸入rs為Promise陣列則fn可不給,並循序執行各Promise。 fn之回傳亦可為一般值(視為resolve),fn同步拋錯視為reject;不給fn時rs之元素亦可為一般值,皆同pmSeries。任一者reject即reject並停止派發後續任務;不給fn時先行產生之Promise於進入時即被觀察,未派發者之reject亦不成為未處理之rejection。rs之元素於進入時即取值,呼叫後改動rs不影響fn收到之值,讀取元素拋錯時以該錯誤reject。 rs亦可為鍵值物件,依其自有可列舉字串鍵展開,fn收到(值, 鍵字串);名為length之鍵與其他鍵同為一般鍵,不因其值為非負整數而改成依索引迭代。 若先行產生promise則takeLimit設定會無效,因promise已先行初始化啟動。 等同於Bluebird的Promise.map,而concurrency為takeLimit。 使用函數執行時,當takeLimit=1,等同於mapSeries,promise依序執行完畢才會調用下一個promise。 使用函數執行時,當takeLimit=0,等同於先行產生promise的情形,各promise直接執行視各自執行時間結束。

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test1() {
        return new Promise((resolve, reject) => {
            let ms = []
            let takeLimit = 2 //通過function調用產生promise, 可受takeLimit控管同時執行數量, takeLimit=2
            pmMap([1, 2, 3, 4, 5], function (v, k) {
                return new Promise(function(resolve, reject) {
                    let d = 400 - ((v ** 2) * 10 + 50)
                    console.log('call', v)
                    ms.push({ call: v })
                    setTimeout(function() {
                        console.log('resolve', v, 'd', d)
                        ms.push({ resolve: v, d })
                        resolve('#' + v)
                    }, d)
                })
            }, takeLimit)
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })
        })
    }
    //cost: (1)340, (2)310, (3)260, (4)190, (5)100
    //1core: call 2(0) -> resolve 2(310) -> call 3(310) -> resolve 3(310+260=570)
    //2core: call 1(0) -> resolve 1(340) -> call 4(340) -> resolve 4(340+190=530) -> call 5(530) -> resolve 5(340+190+100=630)
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // call 1
    // call 2
    // resolve 2 d 310
    // call 3
    // resolve 1 d 340
    // call 4
    // resolve 4 d 190
    // call 5
    // resolve 3 d 260
    // resolve 5 d 100
    // then ["#1","#2","#3","#4","#5"]
    // [{"call":1},{"call":2},{"resolve":2,"d":310},{"call":3},{"resolve":1,"d":340},{"call":4},{"resolve":4,"d":190},{"call":5},{"resolve":3,"d":260},{"resolve":5,"d":100},{"res":["#1","#2","#3","#4","#5"]}]

    async function test2() {
        return new Promise((resolve, reject) => {
            let ms = []
            let takeLimit = 1 //通過function調用產生promise, takeLimit=1, 等同於mapSeries
            pmMap([1, 2, 3, 4, 5], function (v, k) {
                return new Promise(function(resolve, reject) {
                    let d = 400 - ((v ** 2) * 10 + 50)
                    console.log('call', v)
                    ms.push({ call: v })
                    setTimeout(function() {
                        console.log('resolve', v, 'd', d)
                        ms.push({ resolve: v, d })
                        resolve('#' + v)
                    }, d)
                })
            }, takeLimit)
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })
        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // call 1
    // resolve 1 d 340
    // call 2
    // resolve 2 d 310
    // call 3
    // resolve 3 d 260
    // call 4
    // resolve 4 d 190
    // call 5
    // resolve 5 d 100
    // then ["#1","#2","#3","#4","#5"]
    // [{"call":1},{"resolve":1,"d":340},{"call":2},{"resolve":2,"d":310},{"call":3},{"resolve":3,"d":260},{"call":4},{"resolve":4,"d":190},{"call":5},{"resolve":5,"d":100},{"res":["#1","#2","#3","#4","#5"]}]

    async function test3() {
        return new Promise((resolve, reject) => {
            let ms = []
            let takeLimit = 0 //通過function調用產生promise, takeLimit=0, 無限制同時執行數量
            pmMap([1, 2, 3, 4, 5], function (v, k) {
                return new Promise(function(resolve, reject) {
                    let d = 400 - ((v ** 2) * 10 + 50)
                    console.log('call', v)
                    ms.push({ call: v })
                    setTimeout(function() {
                        console.log('resolve', v, 'd', d)
                        ms.push({ resolve: v, d })
                        resolve('#' + v)
                    }, d)
                })
            }, takeLimit)
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })
        })
    }
    console.log('test3')
    let r3 = await test3()
    console.log(JSON.stringify(r3))
    // test3
    // call 1
    // call 2
    // call 3
    // call 4
    // call 5
    // resolve 5 d 100
    // resolve 4 d 190
    // resolve 3 d 260
    // resolve 2 d 310
    // resolve 1 d 340
    // then ["#1","#2","#3","#4","#5"]
    // [{"call":1},{"call":2},{"call":3},{"call":4},{"call":5},{"resolve":5,"d":100},{"resolve":4,"d":190},{"resolve":3,"d":260},{"resolve":2,"d":310},{"resolve":1,"d":340},{"res":["#1","#2","#3","#4","#5"]}]

    async function test4() {
        return new Promise((resolve, reject) => {
            let ms = []
            let takeLimit = 2 //同takeLimit=0(無限制同時執行數量), 因rs已事先初始化就執行故無法受takeLimit控管
            let rs = [1, 2, 3, 4, 5].map(function (v, k) {
                return new Promise(function(resolve, reject) {
                    let d = 400 - ((v ** 2) * 10 + 50)
                    console.log('call', v)
                    ms.push({ call: v })
                    setTimeout(function() {
                        console.log('resolve', v, 'd', d)
                        ms.push({ resolve: v, d })
                        resolve('#' + v)
                    }, d)
                })
            })
            pmMap(rs, null, takeLimit)
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })
        })
    }
    console.log('test4')
    let r4 = await test4()
    console.log(JSON.stringify(r4))
    // test4
    // call 1
    // call 2
    // call 3
    // call 4
    // call 5
    // resolve 5 d 100
    // resolve 4 d 190
    // resolve 3 d 260
    // resolve 2 d 310
    // resolve 1 d 340
    // then ["#1","#2","#3","#4","#5"]
    // [{"call":1},{"call":2},{"call":3},{"call":4},{"call":5},{"resolve":5,"d":100},{"resolve":4,"d":190},{"resolve":3,"d":260},{"resolve":2,"d":310},{"resolve":1,"d":340},{"res":["#1","#2","#3","#4","#5"]}]

    async function test5() {
        return new Promise((resolve, reject) => {
            let ms = []
            let takeLimit = 1 //takeLimit=1循序執行, 執行到3先catch而跳出pmMap, 故其他任務4與5不會執行
            pmMap([1, 2, 3, 4, 5], function (v, k) {
                return new Promise(function(resolve, reject) {
                    let d = 400 - ((v ** 2) * 10 + 50)
                    console.log('call', v)
                    ms.push({ call: v })
                    setTimeout(function() {
                        if (v === 3) {
                            console.log('reject', v, 'd', d)
                            ms.push({ reject: v, d })
                            reject('#' + v)
                        }
                        else {
                            console.log('resolve', v, 'd', d)
                            ms.push({ resolve: v, d })
                            resolve('#' + v)
                        }
                    }, d)
                })
            }, takeLimit)
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })
        })
    }
    console.log('test5')
    let r5 = await test5()
    console.log(JSON.stringify(r5))
    // test5
    // call 1
    // resolve 1 d 340
    // call 2
    // resolve 2 d 310
    // call 3
    // reject 3 d 260
    // catch "#3"
    // [{"call":1},{"resolve":1,"d":340},{"call":2},{"resolve":2,"d":310},{"call":3},{"reject":3,"d":260},{"err":"#3"}]

    async function test6() {
        return new Promise((resolve, reject) => {
            let ms = []
            let takeLimit = 0 //無限制同時執行數量, 會於3先catch而跳出pmMap, 但因順發同時執行(1~5都已call), 故3 catch(260m)之後還會有resolve 1(340)與2(310)
            pmMap([1, 2, 3, 4, 5], function (v, k) {
                return new Promise(function(resolve, reject) {
                    let d = 400 - ((v ** 2) * 10 + 50)
                    console.log('call', v)
                    ms.push({ call: v })
                    setTimeout(function() {
                        if (v === 3) {
                            console.log('reject', v, 'd', d)
                            ms.push({ reject: v, d })
                            reject('#' + v)
                        }
                        else {
                            console.log('resolve', v, 'd', d)
                            ms.push({ resolve: v, d })
                            resolve('#' + v)
                        }
                    }, d)
                })
            }, takeLimit)
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })
        })
    }
    console.log('test6')
    let r6 = await test6()
    console.log(JSON.stringify(r6))
    // test6
    // call 1
    // call 2
    // call 3
    // call 4
    // call 5
    // resolve 5 d 100
    // resolve 4 d 190
    // reject 3 d 260
    // catch "#3"
    // [{"call":1},{"call":2},{"call":3},{"call":4},{"call":5},{"resolve":5,"d":100},{"resolve":4,"d":190},{"reject":3,"d":260},{"err":"#3"}]
    // resolve 2 d 310
    // resolve 1 d 340

    let delay = () => {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve()
            }, 700)
        })
    }
    await delay()

    async function test7() {
        return new Promise((resolve, reject) => {
            let ms = []
            pmMap({
                t1: 1,
                t2: 2.345,
                t3: 'abc',
            }, function (v, k) {
                return new Promise(function(resolve, reject) {
                    resolve(`k=${k},v=${v}`)
                })
            },)
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })
        })
    }
    console.log('test7')
    let r7 = await test7()
    console.log(JSON.stringify(r7))
    // test7
    // then ["k=t1,v=1","k=t2,v=2.345","k=t3,v=abc"]
    // [{"res":["k=t1,v=1","k=t2,v=2.345","k=t3,v=abc"]}]

}
topAsync().catch(() => {})
Parameters:
Name Type Attributes Default Description
rs Array | Object

輸入資料陣列或鍵值物件,若不給fn則rs需要為Promise陣列(或值為Promise之物件)

fun function

輸入循序執行值的呼叫async函數

takeLimit Integer <optional>
0

輸入同時處理數量整數,預設0,代表無限制

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;i為任務於展開後陣列之索引(由0起算);type為'start'時為派發任務i(含i、running為含該任務之執行中任務數),'done'時為任務i已完成(含i、ok為是否成功),'resolve'時為全部n個任務皆成功而即將resolve(含n),'reject'時為首個失敗之任務i使pmMap即將reject(含i,輸入檢查失敗時i為-1);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響派發,預設null

Returns:

回傳Promise,resolve回傳成功結果,reject回傳失敗結果

Type
Promise

(static) pmQueue(takeLimitopt, takeLastopt, optopt) → {function}

Description:
  • 通過佇列限制與呼叫非同步(Promise)函數,可推入不同之非同步函數,將一併受限

    通過new建構,呼叫時輸入不同之非同步函數,以及其輸入參數,會推入佇列後並循序等待執行

    可限制同時運行的非同步函數總數量(takeLimit>0),可只取最後呼叫的非同步函數進行防抖功能(takeLast=true),代表前面的呼叫皆自動轉為catch,而其回傳訊息為物件{reason:'cancelled'}

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test1() {
        return new Promise((resolve, reject) => {

            let ms = []
            let pmq = pmQueue(1) //同時處理1個

            function fun1(v) {
                console.log('call fun1')
                ms.push('call fun1')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun1 resolve: ' + v)
                        resolve('#' + v)
                    }, 300)
                })
            }

            function fun2(v) {
                console.log('call fun2')
                ms.push('call fun2')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun2 resolve: ' + v)
                        resolve('#' + v)
                    }, 200)
                })
            }

            function fun3(v) {
                console.log('call fun3')
                ms.push('call fun3')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun3 resolve: ' + v)
                        resolve('#' + v)
                    }, 100)
                })
            }

            pmq(fun1, 'inp1')
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + 'reason ' + msg.reason)
                })
            pmq(fun2, 'inp2')
                .then(function(msg) {
                    console.log('t2 then', msg)
                    ms.push('t2 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t2 catch', msg)
                    ms.push('t2 catch: ' + 'reason ' + msg.reason)
                })
            pmq(fun3, 'inp3')
                .then(function(msg) {
                    console.log('t3 then', msg)
                    ms.push('t3 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t3 catch', msg)
                    ms.push('t3 catch: ' + 'reason ' + msg.reason)
                })
            setTimeout(function() {
                resolve(ms)
            }, 700)
        })
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // call fun1
    // t1 then #inp1
    // call fun2
    // t2 then #inp2
    // call fun3
    // t3 then #inp3
    // ["call fun1","fun1 resolve: inp1","t1 then: #inp1","call fun2","fun2 resolve: inp2","t2 then: #inp2","call fun3","fun3 resolve: inp3","t3 then: #inp3"]

    async function test2() {
        return new Promise((resolve, reject) => {

            let ms = []
            let pmq = pmQueue(2) //同時處理2個

            function fun1(v) {
                console.log('call fun1')
                ms.push('call fun1')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun1 resolve: ' + v)
                        resolve('#' + v)
                    }, 300)
                })
            }

            function fun2(v) {
                console.log('call fun2')
                ms.push('call fun2')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun2 resolve: ' + v)
                        resolve('#' + v)
                    }, 200)
                })
            }

            function fun3(v) {
                console.log('call fun3')
                ms.push('call fun3')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun3 resolve: ' + v)
                        resolve('#' + v)
                    }, 100)
                })
            }

            pmq(fun1, 'inp1')
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + 'reason ' + msg.reason)
                })
            pmq(fun2, 'inp2')
                .then(function(msg) {
                    console.log('t2 then', msg)
                    ms.push('t2 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t2 catch', msg)
                    ms.push('t2 catch: ' + 'reason ' + msg.reason)
                })
            pmq(fun3, 'inp3')
                .then(function(msg) {
                    console.log('t3 then', msg)
                    ms.push('t3 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t3 catch', msg)
                    ms.push('t3 catch: ' + 'reason ' + msg.reason)
                })
            setTimeout(function() {
                resolve(ms)
            }, 700)
        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // call fun1
    // call fun2
    // t2 then #inp2
    // call fun3
    // t1 then #inp1
    // t3 then #inp3
    // ["call fun1","call fun2","fun2 resolve: inp2","t2 then: #inp2","call fun3","fun1 resolve: inp1","t1 then: #inp1","fun3 resolve: inp3","t3 then: #inp3"]

    async function test3() {
        return new Promise((resolve, reject) => {

            let ms = []
            let pmq = pmQueue(null) //同時處理全部

            function fun1(v) {
                console.log('call fun1')
                ms.push('call fun1')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun1 resolve: ' + v)
                        resolve('#' + v)
                    }, 300)
                })
            }

            function fun2(v) {
                console.log('call fun2')
                ms.push('call fun2')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun2 resolve: ' + v)
                        resolve('#' + v)
                    }, 200)
                })
            }

            function fun3(v) {
                console.log('call fun3')
                ms.push('call fun3')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun3 resolve: ' + v)
                        resolve('#' + v)
                    }, 100)
                })
            }

            pmq(fun1, 'inp1')
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + 'reason ' + msg.reason)
                })
            pmq(fun2, 'inp2')
                .then(function(msg) {
                    console.log('t2 then', msg)
                    ms.push('t2 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t2 catch', msg)
                    ms.push('t2 catch: ' + 'reason ' + msg.reason)
                })
            pmq(fun3, 'inp3')
                .then(function(msg) {
                    console.log('t3 then', msg)
                    ms.push('t3 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t3 catch', msg)
                    ms.push('t3 catch: ' + 'reason ' + msg.reason)
                })
            setTimeout(function() {
                resolve(ms)
            }, 700)
        })
    }
    console.log('test3')
    let r3 = await test3()
    console.log(JSON.stringify(r3))
    // test3
    // call fun1
    // call fun2
    // call fun3
    // t3 then #inp3
    // t2 then #inp2
    // t1 then #inp1
    // ["call fun1","call fun2","call fun3","fun3 resolve: inp3","t3 then: #inp3","fun2 resolve: inp2","t2 then: #inp2","fun1 resolve: inp1","t1 then: #inp1"]

    async function test4() {
        return new Promise((resolve, reject) => {

            let ms = []
            let pmq = pmQueue(null, true) //同時處理全部, 但只拿最後執行者的結果

            function fun1(v) {
                console.log('call fun1')
                ms.push('call fun1')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun1 resolve: ' + v)
                        resolve('#' + v)
                    }, 300)
                })
            }

            function fun2(v) {
                console.log('call fun2')
                ms.push('call fun2')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun2 resolve: ' + v)
                        resolve('#' + v)
                    }, 200)
                })
            }

            function fun3(v) {
                console.log('call fun3')
                ms.push('call fun3')
                return new Promise(function(resolve, reject) {
                    setTimeout(function() {
                        ms.push('fun3 resolve: ' + v)
                        resolve('#' + v)
                    }, 100)
                })
            }

            pmq(fun1, 'inp1')
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + 'reason ' + msg.reason)
                })
            pmq(fun2, 'inp2')
                .then(function(msg) {
                    console.log('t2 then', msg)
                    ms.push('t2 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t2 catch', msg)
                    ms.push('t2 catch: ' + 'reason ' + msg.reason)
                })
            pmq(fun3, 'inp3')
                .then(function(msg) {
                    console.log('t3 then', msg)
                    ms.push('t3 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t3 catch', msg)
                    ms.push('t3 catch: ' + 'reason ' + msg.reason)
                })
            setTimeout(function() {
                resolve(ms)
            }, 700)
        })
    }
    console.log('test4')
    let r4 = await test4()
    console.log(JSON.stringify(r4))
    // test4
    // call fun1
    // call fun2
    // call fun3
    // t3 then #inp3
    // t2 catch { reason: 'cancelled' }
    // t1 catch { reason: 'cancelled' }
    // ["call fun1","call fun2","call fun3","fun3 resolve: inp3","t3 then: #inp3","fun2 resolve: inp2","t2 catch: reason cancelled","fun1 resolve: inp1","t1 catch: reason cancelled"]

}
topAsync().catch(() => {})
Parameters:
Name Type Attributes Default Description
takeLimit Integer <optional>
0

輸入同時處理數量整數,預設0,代表無限制

takeLast Boolean <optional>
false

輸入多次觸發時是否只取最後呼叫的非同步函數,預設false,搭配takeLimit=0進行非同步函數防抖

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'push'時為任務排入佇列(含n為依呼叫順序由1起算之任務序號),'start'時為任務開始執行(含n、running為含該任務之執行中任務數),'end'時為任務執行完畢而即將依結果resolve或reject其Promise(含n、running為不含該任務之執行中任務數、ok為任務是否成功),'cancel'時為takeLast下非最後呼叫者之結果被捨棄而即將reject{ reason: 'cancelled' }(含n);任務非函數時仍通知start與end(ok為false);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響佇列,預設null

Returns:

回傳Function,第1參數為非同步函數,第2參數之後為欲輸入非同步函數之參數。回傳為Promise,resolve回傳成功結果而reject回傳失敗訊息;任務之取消(其reject值之reason為'cancelled')以{ reason: 'cancelled' }傳出;第1參數非函數時該任務之Promise以字串'fun is not a function'被reject

Type
function

(static) pmSeries(rs, fun) → {Promise}

Description:
  • Promise的mapSeries,循序執行Promise

    若輸入rs為資料陣列則fn需將數據處理並回傳Promise,若輸入rs為Promise陣列則fn可不給,並循序執行各Promise。 不給fn時,先行產生之Promise於進入時即被觀察,排在後面者先reject亦不成為未處理之rejection,其結果仍依序取用。 rs之元素於進入時即取值,呼叫後改動rs不影響fn收到之值,讀取元素拋錯時以該錯誤reject。 rs亦可為鍵值物件,依其自有可列舉字串鍵展開,fn收到(值, 鍵字串);名為length之鍵與其他鍵同為一般鍵,不因其值為非負整數而改成依索引迭代。 使用函數執行時,等同於pmMap使用函數執行且takeLimit=1,promise依序執行完畢才會調用下一個promise。 先行產生promise時,等同於pmMap使用函數執行且takeLimit=0,或是等同於pmMap先行產生promise,各promise直接執行視各自執行時間結束。

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test1() {
        return new Promise((resolve, reject) => {
            let ms = []

            //通過function調用產生promise, 各promise循序調用
            pmSeries([1, 2, 3, 4, 5], function (v, k) {
                return new Promise(function(resolve, reject) {
                    let d = 400 - ((v ** 2) * 10 + 50)
                    console.log('call', v)
                    ms.push({ call: v })
                    setTimeout(function() {
                        console.log('resolve', v, 'd', d)
                        ms.push({ resolve: v, d })
                        resolve('#' + v)
                    }, d)
                })
            })
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })

        })

    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // call 1
    // resolve 1 d 340
    // call 2
    // resolve 2 d 310
    // call 3
    // resolve 3 d 260
    // call 4
    // resolve 4 d 190
    // call 5
    // resolve 5 d 100
    // then ["#1","#2","#3","#4","#5"]
    // [{"call":1},{"resolve":1,"d":340},{"call":2},{"resolve":2,"d":310},{"call":3},{"resolve":3,"d":260},{"call":4},{"resolve":4,"d":190},{"call":5},{"resolve":5,"d":100},{"res":["#1","#2","#3","#4","#5"]}]

    async function test2() {
        return new Promise((resolve, reject) => {
            let ms = []

            //通過function調用產生promise, 各promise循序調用, 於3會觸發reject而跳出pmSeries
            pmSeries([1, 2, 3, 4, 5], function (v, k) {
                return new Promise(function(resolve, reject) {
                    let d = 400 - ((v ** 2) * 10 + 50)
                    console.log('call', v)
                    ms.push({ call: v })
                    setTimeout(function() {
                        if (v === 3) {
                            console.log('reject', v, 'd', d)
                            ms.push({ reject: v, d })
                            reject('#' + v)
                        }
                        else {
                            console.log('resolve', v, 'd', d)
                            ms.push({ resolve: v, d })
                            resolve('#' + v)
                        }
                    }, d)
                })
            })
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })

        })

    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // call 1
    // resolve 1 d 340
    // call 2
    // resolve 2 d 310
    // call 3
    // reject 3 d 260
    // catch "#3"
    // [{"call":1},{"resolve":1,"d":340},{"call":2},{"resolve":2,"d":310},{"call":3},{"reject":3,"d":260},{"err":"#3"}]

    async function test3() {
        return new Promise((resolve, reject) => {
            let ms = []

            //先產生promise, 因事先初始化故各promise會依照各自執行時間結束
            let rs = [1, 2, 3, 4, 5].map(function (v, k) {
                return new Promise(function(resolve, reject) {
                    let d = 400 - ((v ** 2) * 10 + 50)
                    console.log('call', v)
                    ms.push({ call: v })
                    setTimeout(function() {
                        console.log('resolve', v, 'd', d)
                        ms.push({ resolve: v, d })
                        resolve('#' + v)
                    }, d)
                })
            })
            pmSeries(rs, null)
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })

        })

    }
    console.log('test3')
    let r3 = await test3()
    console.log(JSON.stringify(r3))
    // test3
    // call 1
    // call 2
    // call 3
    // call 4
    // call 5
    // resolve 5 d 100
    // resolve 4 d 190
    // resolve 3 d 260
    // resolve 2 d 310
    // resolve 1 d 340
    // then ["#1","#2","#3","#4","#5"]
    // [{"call":1},{"call":2},{"call":3},{"call":4},{"call":5},{"resolve":5,"d":100},{"resolve":4,"d":190},{"resolve":3,"d":260},{"resolve":2,"d":310},{"resolve":1,"d":340},{"res":["#1","#2","#3","#4","#5"]}]

    async function test4() {
        return new Promise((resolve, reject) => {
            let ms = []

            //通過function調用產生promise, 各promise循序調用
            pmSeries({
                t1: 1,
                t2: 2.345,
                t3: 'abc',
            }, function (v, k) {
                return new Promise(function(resolve, reject) {
                    resolve(`k=${k},v=${v}`)
                })
            })
                .then(function(res) {
                    console.log('then', JSON.stringify(res))
                    ms.push({ res })
                    resolve(ms)
                })
                .catch(function(err) {
                    console.log('catch', JSON.stringify(err))
                    ms.push({ err })
                    resolve(ms)
                })

        })

    }
    console.log('test4')
    let r4 = await test4()
    console.log(JSON.stringify(r4))
    // test4
    // then ["k=t1,v=1","k=t2,v=2.345","k=t3,v=abc"]
    // [{"res":["k=t1,v=1","k=t2,v=2.345","k=t3,v=abc"]}]

}
topAsync().catch(() => {})
Parameters:
Name Type Description
rs Array | Object

輸入資料陣列或鍵值物件,若不給fn則rs需要為Promise陣列(或值為Promise之物件)

fun function

輸入循序執行值的呼叫函數

Returns:

回傳Promise,resolve回傳成功結果,reject回傳失敗結果

Type
Promise

(static) pmThrottle(optopt) → {function}

Description:
  • 非同步(Promise)函數進行節流

    同時會執行多個佇列(非同步函數),但若有一個佇列先執行完畢,則其他佇列皆會自動轉為catch,回傳訊息為物件{reason:'cancelled'}

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test1() {
        return new Promise((resolve, reject) => {

            let ms = []
            let pmt = pmThrottle()

            let fun = function (name, t) {
                return new Promise(function(resolve, reject) {
                    setTimeout(() => {
                        console.log('resolve: ' + name + ', t: ' + t)
                        resolve('resolve: ' + name + ', t: ' + t)
                    }, t)
                })
            }

            pmt(fun, 't1', 150)
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + 'reason ' + msg.reason)
                })
            pmt(fun, 't2', 100)
                .then(function(msg) {
                    console.log('t2 then', msg)
                    ms.push('t2 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t2 catch', msg)
                    ms.push('t2 catch: ' + 'reason ' + msg.reason)
                })
            pmt(fun, 't3', 50)
                .then(function(msg) {
                    console.log('t3 then', msg)
                    ms.push('t3 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t3 catch', msg)
                    ms.push('t3 catch: ' + 'reason ' + msg.reason)
                })

            setTimeout(() => {
                pmt(fun, 't4', 50)
                    .then((msg) => {
                        console.log('t4 then', msg)
                        ms.push('t4 then: ' + msg)
                    })
                    .catch((msg) => {
                        console.log('t4 catch', msg)
                        ms.push('t4 catch: ' + 'reason ' + msg.reason)
                    })
                    .finally(() => {
                        resolve(ms)
                    })
            }, 200)

        })
    }
    console.log('test1')
    let r1 = await test1()
    console.log(JSON.stringify(r1))
    // test1
    // t1 catch { reason: 'cancelled' }
    // t2 catch { reason: 'cancelled' }
    // resolve: t3, t: 50
    // t3 then resolve: t3, t: 50
    // resolve: t4, t: 50
    // t4 then resolve: t4, t: 50
    // ["t1 catch: reason cancelled","t2 catch: reason cancelled","t3 then: resolve: t3, t: 50","t4 then: resolve: t4, t: 50"]

    async function test2() {
        return new Promise((resolve, reject) => {

            let ms = []
            let pmt = pmThrottle()

            let fun1 = function (name, t) {
                return new Promise(function(resolve, reject) {
                    setTimeout(() => {
                        console.log('fun1 resolve: ' + name + ', t: ' + t)
                        resolve('fun1 resolve: ' + name + ', t: ' + t)
                    }, t)
                })
            }

            let fun2 = function (name, t) {
                return new Promise(function(resolve, reject) {
                    setTimeout(() => {
                        console.log('fun2 resolve: ' + name + ', t: ' + t)
                        resolve('fun2 resolve: ' + name + ', t: ' + t)
                    }, t)
                })
            }

            //測試不同函數fun1與fun2
            pmt(fun1, 't1', 150)
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + 'reason ' + msg.reason)
                })
            pmt(fun2, 't2', 100)
                .then(function(msg) {
                    console.log('t2 then', msg)
                    ms.push('t2 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t2 catch', msg)
                    ms.push('t2 catch: ' + 'reason ' + msg.reason)
                })
            pmt(fun2, 't3', 50)
                .then(function(msg) {
                    console.log('t3 then', msg)
                    ms.push('t3 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t3 catch', msg)
                    ms.push('t3 catch: ' + 'reason ' + msg.reason)
                })

            setTimeout(() => {
                pmt(fun1, 't4', 50)
                    .then((msg) => {
                        console.log('t4 then', msg)
                        ms.push('t4 then: ' + msg)
                    })
                    .catch((msg) => {
                        console.log('t4 catch', msg)
                        ms.push('t4 catch: ' + 'reason ' + msg.reason)
                    })
                    .finally(() => {
                        resolve(ms)
                    })
            }, 200)

        })
    }
    console.log('test2')
    let r2 = await test2()
    console.log(JSON.stringify(r2))
    // test2
    // t1 catch { reason: 'cancelled' }
    // t2 catch { reason: 'cancelled' }
    // fun2 resolve: t3, t: 50
    // t3 then fun2 resolve: t3, t: 50
    // fun1 resolve: t4, t: 50
    // t4 then fun1 resolve: t4, t: 50
    // ["t1 catch: reason cancelled","t2 catch: reason cancelled","t3 then: fun2 resolve: t3, t: 50","t4 then: fun1 resolve: t4, t: 50"]

    async function test3() {
        return new Promise((resolve, reject) => {

            let ms = []
            let pmt = pmThrottle()

            let i = 0
            let fun = function () {
                return new Promise(function(resolve, reject) {
                    setTimeout(() => {
                        i++
                        console.log('resolve: ' + i)
                        resolve('resolve: ' + i)
                    }, 100)
                })
            }

            //測試無輸入參數
            pmt(fun)
                .then(function(msg) {
                    console.log('t1 then', msg)
                    ms.push('t1 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t1 catch', msg)
                    ms.push('t1 catch: ' + 'reason ' + msg.reason)
                })
            pmt(fun)
                .then(function(msg) {
                    console.log('t2 then', msg)
                    ms.push('t2 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t2 catch', msg)
                    ms.push('t2 catch: ' + 'reason ' + msg.reason)
                })
            pmt(fun)
                .then(function(msg) {
                    console.log('t3 then', msg)
                    ms.push('t3 then: ' + msg)
                })
                .catch(function(msg) {
                    console.log('t3 catch', msg)
                    ms.push('t3 catch: ' + 'reason ' + msg.reason)
                })

            setTimeout(() => {
                pmt(fun)
                    .then((msg) => {
                        console.log('t4 then', msg)
                        ms.push('t4 then: ' + msg)
                    })
                    .catch((msg) => {
                        console.log('t4 catch', msg)
                        ms.push('t4 catch: ' + 'reason ' + msg.reason)
                    })
                    .finally(() => {
                        resolve(ms)
                    })
            }, 200)

        })
    }
    console.log('test3')
    let r3 = await test3()
    console.log(JSON.stringify(r3))
    // test3
    // t1 catch { reason: 'cancelled' }
    // t2 catch { reason: 'cancelled' }
    // core: resolve: 1
    // t3 then resolve: 1
    // core: resolve: 2
    // t4 then resolve: 2
    // ["t1 catch: reason cancelled","t2 catch: reason cancelled","t3 then: resolve: 1","t4 then: resolve: 2"]

}
topAsync().catch(() => {})
Parameters:
Name Type Attributes Default Description
opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;n為依呼叫順序由1起算之任務序號;type為'push'時為任務排入佇列(含n,輸入非函數時不排入故不通知),'start'時為開始偵測(每20ms檢查一次是否有待處理之任務),'cancel'時為任務n因有較後排入者而即將被取消(含n),'run'時為即將執行該次偵測所取之最後排入任務(含n),'done'時為被執行之任務已執行完畢而即將依結果resolve或reject其Promise(含n、ok為是否成功),'stop'時為佇列已空而停止偵測;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響節流,預設null

Returns:

回傳Function,輸入為非同步函數與其輸入,會推入佇列後並循序等待執行,回傳為Promise,resolve回傳成功結果而reject回傳失敗訊息;輸入非函數時不排入佇列,回傳之Promise以字串'fun is not a function'被reject(拒絕值同pmQueue)

Type
function

(async, static) pmTimeout(pm, ms, optopt) → {Promise}

Description:
  • 為Promise添加逾時限制

    以Promise.race令pm與計時器競速,pm先完成則原樣回傳其resolve或reject之結果,逾時則reject一個Error 該Error之message為'timeout[ms數ms]'(有label時置於前綴),並帶有code為'TIMEOUT'與timeout為逾時毫秒數等欄位,供呼叫端辨別為逾時而非pm自身之錯誤 注意JavaScript無法中止已啟動之Promise,逾時僅代表不再等待其結果,pm內之工作仍會繼續執行至自然結束,若需一併中止內部工作(如子進程)須由pm自行處理 本函數為async function,參數錯誤亦以reject傳出而非同步throw,故可與逾時錯誤一併由.catch或try/catch接住

    Unit Test: Github

Source:
Example
//need test in nodejs

async function test() {

    //pm先完成則原樣回傳其結果
    let r1 = await pmTimeout(delay(10).then(() => 'ok'), 1000)
    console.log('r1', r1)
    // => r1 ok

    //逾時則reject一個code為'TIMEOUT'之Error
    try {
        await pmTimeout(delay(3000), 100)
    }
    catch (err) {
        console.log('r2', err.message, err.code, err.timeout)
        // => r2 timeout[100ms] TIMEOUT 100
    }

    //有label時置於訊息前綴
    try {
        await pmTimeout(delay(3000), 100, { label: 'fetchUser' })
    }
    catch (err) {
        console.log('r3', err.message)
        // => r3 fetchUser timeout[100ms]
    }

    //pm自身之reject原樣傳出, 其code非'TIMEOUT'故可與逾時區分
    try {
        await pmTimeout(Promise.reject(new Error('mine')), 1000)
    }
    catch (err) {
        console.log('r4', err.message, err.code)
        // => r4 mine undefined
    }

}
test()
    .catch((err) => {
        console.log(err)
    })
Parameters:
Name Type Attributes Default Description
pm Promise

輸入Promise物件,非Promise時reject錯誤'invalid pm'

ms Integer

輸入逾時毫秒數,須為非負整數,為0代表下一輪事件迴圈即逾時,其餘reject錯誤'invalid ms[ms數]'

opt Object <optional>
{}

輸入設定物件

Properties
Name Type Attributes Default Description
label String <optional>
''

輸入識別字串,將置於逾時錯誤訊息之前綴以利辨識來源,非有效字串時回退為空字串,預設''

onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'start'時為已啟動逾時計時器(含ms為夾至上限後之逾時毫秒數),'timeout'時為逾時計時器已到期而即將reject逾時錯誤(含ms),'settle'時為本函數即將resolve或reject(含by為'pm'代表pm先完成、'timeout'代表逾時勝出、'invalid'代表輸入檢核失敗,ok為pm先完成時是否resolve,逾時與檢核失敗皆為false);每次呼叫恰通知一次settle,檢核失敗時不通知start與timeout;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響逾時判定,預設null

Returns:

回傳Promise,pm先完成則resolve或reject其結果,逾時則reject一個code為'TIMEOUT'之Error

Type
Promise

(static) preciseNum(v, optopt) → {String}

Description:
  • 轉數字成為原始精度之數字字串

    Unit Test: Github

Source:
Example
console.log(`preciseNum(0)`, preciseNum(0))
// => preciseNum(0) 0

console.log(`preciseNum(1)`, preciseNum(1))
// => preciseNum(1) 1

console.log(`preciseNum(0.3)`, preciseNum(0.3))
// => preciseNum(0.3) 0.3

console.log(`preciseNum(0.1 + 0.2)`, preciseNum(0.1 + 0.2))
// => preciseNum(0.1 + 0.2) 0.3

console.log(`preciseNum(4.67 + 7.12 + 94.4)`, preciseNum(4.67 + 7.12 + 94.4))
// => preciseNum(4.67 + 7.12 + 94.4) 106.19

console.log(`preciseNum(94.4 + 7.12 + 4.67)`, preciseNum(94.4 + 7.12 + 4.67))
// => preciseNum(94.4 + 7.12 + 4.67) 106.19

console.log(`preciseNum(0, { returnDigit: true })`, preciseNum(0, { returnDigit: true }))
// => preciseNum(0, { returnDigit: true })) 0

console.log(`preciseNum(1, { returnDigit: true })`, preciseNum(1, { returnDigit: true }))
// => preciseNum(1, { returnDigit: true })) 0

console.log(`preciseNum(0.3, { returnDigit: true })`, preciseNum(0.3, { returnDigit: true }))
// => preciseNum(0.3, { returnDigit: true })) 1

console.log(`preciseNum(0.1 + 0.2, { returnDigit: true })`, preciseNum(0.1 + 0.2, { returnDigit: true }))
// => preciseNum(0.1 + 0.2, { returnDigit: true })) 1

console.log(`preciseNum(4.67 + 7.12 + 94.4, { returnDigit: true })`, preciseNum(4.67 + 7.12 + 94.4, { returnDigit: true }))
// => preciseNum(4.67 + 7.12 + 94.4, { returnDigit: true })) 2

console.log(`preciseNum(94.4 + 7.12 + 4.67, { returnDigit: true })`, preciseNum(94.4 + 7.12 + 4.67, { returnDigit: true }))
// => preciseNum(94.4 + 7.12 + 4.67, { returnDigit: true })) 2
Parameters:
Name Type Attributes Default Description
v String | Number

輸入數字字串或數字

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnDigit Boolean <optional>
false

輸入是否回傳小數位數布林值,預設false

Returns:

回傳數字字串,若returnDigit=true則回傳小數位數整數

Type
String

(static) pseudoRandom(seedopt, returnFunopt) → {Number}

Description:
Source:
Example
let r

r = pseudoRandom()
console.log('pseudoRandom', r)
// => pseudoRandom [0,1)

r = pseudoRandom(123)
console.log('seed=123', r)
// => seed=123 0.6964691872708499

r = pseudoRandom(12.3)
console.log('seed=12.3', r)
// => seed=12.3 0.8510874302592129

r = pseudoRandom('abc')
console.log('seed=abc', r)
// => seed=abc 0.6314232510048896

r = pseudoRandom('abc')
console.log('seed=abc', r)
// => seed=abc 0.6314232510048896

r = pseudoRandom('def')
console.log('seed=def', r)
// => seed=def 0.9743434484116733

r = pseudoRandom('BH01S123')
console.log('seed=BH01S123', r)
// => seed=BH01S123 0.007978770649060607

r = pseudoRandom('BH-01:S-123')
console.log('seed=BH-01:S-123', r)
// => seed=BH01S123 0.9579511017072946
Parameters:
Name Type Attributes Default Description
seed Integer | Number | String <optional>
'start1'

輸入種子seed,給予'start1'為使用初始值1並且隨呼叫次數自增,若為其他則代表使用為指定seed,預設'start1'

returnFun Boolean <optional>
false

輸入是否回傳產生函數布林值,預設false

Returns:

回傳隨機數字

Type
Number

(static) pseudoRandomIntRange(vstartopt, vendopt, seedopt) → {Integer}

Description:
  • 產生位於指定範圍內偽隨機整數,值域為[vstart,vend],代表值會大於等於vstart且小於等於vend

    Unit Test: Github

Source:
Example
let r

r = pseudoRandomIntRange()
console.log('pseudoRandomIntRange', r)
// => pseudoRandomIntRange 43 (預設範圍為0至100)

r = pseudoRandomIntRange(0, 100)
console.log('pseudoRandomIntRange(0, 100)', r)
// => pseudoRandomIntRange(0, 100) 57 //因第2次呼叫故值會不同, 但維持呼叫次數順序時重複執行仍會相同

r = pseudoRandomIntRange(123, 4567)
console.log('pseudoRandomIntRange(123, 4567)', r)
// => pseudoRandomIntRange(123, 4567) 2571

r = pseudoRandomIntRange(null, null, 123)
console.log('pseudoRandomIntRange(null, null, 123)', r)
// => pseudoRandomIntRange(null, null, 123) 70

r = pseudoRandomIntRange(null, null, 12.3)
console.log('pseudoRandomIntRange(null, null, 12.3)', r)
// => pseudoRandomIntRange(null, null, 12.3) 85

r = pseudoRandomIntRange(null, null, 'abc')
console.log('pseudoRandomIntRange(null, null, "abc")', r)
// => pseudoRandomIntRange(null, null, "abc") 63

r = pseudoRandomIntRange(null, null, 'abc')
console.log('pseudoRandomIntRange(null, null, "abc")', r)
// => pseudoRandomIntRange(null, null, "abc") 63

r = pseudoRandomIntRange(null, null, 'def')
console.log('pseudoRandomIntRange(null, null, "def")', r)
// => pseudoRandomIntRange(null, null, "def") 98

r = pseudoRandomIntRange(null, null, 'BH01S123')
console.log('pseudoRandomIntRange(null, null, "BH01S123")', r)
// => pseudoRandomIntRange(null, null, "BH01S123") 0

r = pseudoRandomIntRange(null, null, 'BH-01:S-123')
console.log('pseudoRandomIntRange(null, null, "BH-01:S-123")', r)
// => pseudoRandomIntRange(null, null, "BH-01:S-123") 96

r = pseudoRandomIntRange(0, 100, 123)
console.log('pseudoRandomIntRange(0, 100, 123)', r)
// => pseudoRandomIntRange(0, 100, 123) 70

r = pseudoRandomIntRange(0, 100, 12.3)
console.log('pseudoRandomIntRange(0, 100, 12.3)', r)
// => pseudoRandomIntRange(0, 100, 12.3) 85

r = pseudoRandomIntRange(0, 100, 'abc')
console.log('pseudoRandomIntRange(0, 100, "abc")', r)
// => pseudoRandomIntRange(0, 100, "abc") 63

r = pseudoRandomIntRange(0, 100, 'abc')
console.log('pseudoRandomIntRange(0, 100, "abc")', r)
// => pseudoRandomIntRange(0, 100, "abc") 63

r = pseudoRandomIntRange(0, 100, 'def')
console.log('pseudoRandomIntRange(0, 100, "def")', r)
// => pseudoRandomIntRange(0, 100, "def") 98

r = pseudoRandomIntRange(0, 100, 'BH01S123')
console.log('pseudoRandomIntRange(0, 100, "BH01S123")', r)
// => pseudoRandomIntRange(0, 100, "BH01S123") 0

r = pseudoRandomIntRange(0, 100, 'BH-01:S-123')
console.log('pseudoRandomIntRange(0, 100, "BH-01:S-123")', r)
// => pseudoRandomIntRange(0, 100, "BH-01:S-123") 96

r = pseudoRandomIntRange(123, 4567, 123)
console.log('pseudoRandomIntRange(123, 4567, 123)', r)
// => pseudoRandomIntRange(123, 4567, 123) 3218

r = pseudoRandomIntRange(123, 4567, 12.3)
console.log('pseudoRandomIntRange(123, 4567, 12.3)', r)
// => pseudoRandomIntRange(123, 4567, 12.3) 3906

r = pseudoRandomIntRange(123, 4567, 'abc')
console.log('pseudoRandomIntRange(123, 456.7, "abc")', r)
// => pseudoRandomIntRange(123, 456.7, "abc") 2929

r = pseudoRandomIntRange(123, 4567, 'abc')
console.log('pseudoRandomIntRange(123, 456.7, "abc")', r)
// => pseudoRandomIntRange(123, 456.7, "abc") 2929

r = pseudoRandomIntRange(123, 4567, 'def')
console.log('pseudoRandomIntRange(123, 456.7, "def")', r)
// => pseudoRandomIntRange(123, 456.7, "def") 4453

r = pseudoRandomIntRange(123, 4567, 'BH01S123')
console.log('pseudoRandomIntRange(123, 456.7, "BH01S123")', r)
// => pseudoRandomIntRange(123, 456.7, "BH01S123") 158

r = pseudoRandomIntRange(123, 4567, 'BH-01:S-123')
console.log('pseudoRandomIntRange(123, 456.7, "BH-01:S-123")', r)
// => pseudoRandomIntRange(123, 456.7, "BH-01:S-123") 4381
Parameters:
Name Type Attributes Default Description
vstart Integer <optional>
0

輸入範圍最小值整數,預設0

vend Integer <optional>
100

輸入範圍最大值整數,預設100

seed Integer | Number | String <optional>
'start1'

輸入種子seed,給予'start1'為使用初始值1並且隨呼叫次數自增,若為其他則代表使用為指定seed,預設'start1'

Returns:

回傳位於指定範圍內隨機整數

Type
Integer

(static) pseudoRandomIntsNdpRange(vstartopt, vendopt, nopt, seedopt) → {Integer}

Description:
  • 產生位於指定範圍內不重複之偽隨機整數陣列,各元素值域為[vstart,vend],代表值會大於等於vstart且小於等於vend

    Unit Test: Github

Source:
Example
let rs

rs = pseudoRandomIntsNdpRange()
console.log('pseudoRandomIntsNdpRange', rs)
// => pseudoRandomIntsNdpRange [ 1 ] (預設範圍為0至100)

rs = pseudoRandomIntsNdpRange(0, 100)
console.log('pseudoRandomIntsNdpRange(0, 100)', rs)
// => pseudoRandomIntsNdpRange(0, 100) [ 85 ] //因第2次呼叫故值會不同, 但維持呼叫次數順序時重複執行仍會相同

rs = pseudoRandomIntsNdpRange(0, 100, 2)
console.log('pseudoRandomIntsNdpRange(0, 100)', rs)
// => pseudoRandomIntsNdpRange(0, 100) [ 59, 20 ]

rs = pseudoRandomIntsNdpRange(123, 4567)
console.log('pseudoRandomIntsNdpRange(123, 4567)', rs)
// => pseudoRandomIntsNdpRange(123, 4567) [ 3951 ]

rs = pseudoRandomIntsNdpRange(null, null, 2, 123)
console.log('pseudoRandomIntsNdpRange(null, null, 2, 123)', rs)
// => pseudoRandomIntsNdpRange(null, null, 2, 123) [ 94, 76 ]

rs = pseudoRandomIntsNdpRange(null, null, 2, 12.3)
console.log('pseudoRandomIntsNdpRange(null, null, 2, 12.3)', rs)
// => pseudoRandomIntsNdpRange(null, null, 2, 12.3) [ 69, 82 ]

rs = pseudoRandomIntsNdpRange(null, null, 2, 'abc')
console.log('pseudoRandomIntsNdpRange(null, null, 2, "abc")', rs)
// => pseudoRandomIntsNdpRange(null, null, 2, "abc") [ 20, 28 ]

rs = pseudoRandomIntsNdpRange(null, null, 2, 'abc')
console.log('pseudoRandomIntsNdpRange(null, null, 2, "abc")', rs)
// => pseudoRandomIntsNdpRange(null, null, 2, "abc") [ 20, 28 ]

rs = pseudoRandomIntsNdpRange(null, null, 2, 'def')
console.log('pseudoRandomIntsNdpRange(null, null, 2, "def")', rs)
// => pseudoRandomIntsNdpRange(null, null, 2, "def") [ 64, 34 ]

rs = pseudoRandomIntsNdpRange(null, null, 2, 'BH01S123')
console.log('pseudoRandomIntsNdpRange(null, null, 2, "BH01S123")', rs)
// => pseudoRandomIntsNdpRange(null, null, 2, "BH01S123") [ 0, 26 ]

rs = pseudoRandomIntsNdpRange(null, null, 2, 'BH-01:S-123')
console.log('pseudoRandomIntsNdpRange(null, null, 2, "BH-01:S-123")', rs)
// => pseudoRandomIntsNdpRange(null, null, 2, "BH-01:S-123") [ 71, 77 ]

rs = pseudoRandomIntsNdpRange(0, 100, 2, 123)
console.log('pseudoRandomIntsNdpRange(0, 100, 2, 123)', rs)
// => pseudoRandomIntsNdpRange(0, 100, 2, 123) [ 94, 76 ]

rs = pseudoRandomIntsNdpRange(0, 100, 2, 12.3)
console.log('pseudoRandomIntsNdpRange(0, 100, 2, 12.3)', rs)
// => pseudoRandomIntsNdpRange(0, 100, 2, 12.3) [ 69, 82 ]

rs = pseudoRandomIntsNdpRange(0, 100, 2, 'abc')
console.log('pseudoRandomIntsNdpRange(0, 100, 2, "abc")', rs)
// => pseudoRandomIntsNdpRange(0, 100, 2, "abc") [ 20, 28 ]

rs = pseudoRandomIntsNdpRange(0, 100, 2, 'abc')
console.log('pseudoRandomIntsNdpRange(0, 100, 2, "abc")', rs)
// => pseudoRandomIntsNdpRange(0, 100, 2, "abc") [ 20, 28 ]

rs = pseudoRandomIntsNdpRange(0, 100, 2, 'def')
console.log('pseudoRandomIntsNdpRange(0, 100, 2, "def")', rs)
// => pseudoRandomIntsNdpRange(0, 100, 2, "def") [ 64, 34 ]

rs = pseudoRandomIntsNdpRange(0, 100, 2, 'BH-01:S-123')
console.log('pseudoRandomIntsNdpRange(0, 100, 2, "BH-01:S-123")', rs)
// => pseudoRandomIntsNdpRange(0, 100, 2, "BH-01:S-123") [ 71, 77 ]

rs = pseudoRandomIntsNdpRange(123, 4567, 2, 123)
console.log('pseudoRandomIntsNdpRange(123, 4567, 2, 123)', rs)
// => pseudoRandomIntsNdpRange(123, 4567, 2, 123) [ 2528, 3854 ]

rs = pseudoRandomIntsNdpRange(123, 4567, 2, 12.3)
console.log('pseudoRandomIntsNdpRange(123, 4567, 2, 12.3)', rs)
// => pseudoRandomIntsNdpRange(123, 4567, 2, 12.3) [ 1818, 4334 ]

rs = pseudoRandomIntsNdpRange(123, 4567, 2, 'abc')
console.log('pseudoRandomIntsNdpRange(123, 456.7, 2, "abc")', rs)
// => pseudoRandomIntsNdpRange(123, 456.7, 2, "abc") [ 478, 3303 ]

rs = pseudoRandomIntsNdpRange(123, 4567, 2, 'abc')
console.log('pseudoRandomIntsNdpRange(123, 456.7, 2, "abc")', rs)
// => pseudoRandomIntsNdpRange(123, 456.7, 2, "abc") [ 478, 3303 ]

rs = pseudoRandomIntsNdpRange(123, 4567, 2, 'def')
console.log('pseudoRandomIntsNdpRange(123, 456.7, 2, "def")', rs)
// => pseudoRandomIntsNdpRange(123, 456.7, 2, "def") [ 983, 3133 ]

rs = pseudoRandomIntsNdpRange(123, 4567, 2, 'BH01S123')
console.log('pseudoRandomIntsNdpRange(123, 456.7, 2, "BH01S123")', rs)
// => pseudoRandomIntsNdpRange(123, 456.7, 2, "BH01S123") [ 2866, 183 ]

rs = pseudoRandomIntsNdpRange(123, 4567, 2, 'BH-01:S-123')
console.log('pseudoRandomIntsNdpRange(123, 456.7, 2, "BH-01:S-123")', rs)
// => pseudoRandomIntsNdpRange(123, 456.7, 2, "BH-01:S-123") [ 3888, 249 ]
Parameters:
Name Type Attributes Default Description
vstart Integer <optional>
0

輸入範圍最小值整數,預設0

vend Integer <optional>
100

輸入範圍最大值整數,預設100

n Integer <optional>
1

輸入產生數量整數,最長為vend-vstart+1個,預設1

seed Integer | Number | String <optional>
'start1'

輸入種子seed,給予'start1'為使用初始值1並且隨呼叫次數自增,若為其他則代表使用為指定seed,預設'start1'

Returns:

回傳位於指定範圍內隨機整數

Type
Integer

(static) pseudoRandomIntsRange(vstartopt, vendopt, nopt, seedopt) → {Integer}

Description:
  • 產生位於指定範圍內偽隨機整數陣列,各元素值域為[vstart,vend],代表值會大於等於vstart且小於等於vend

    Unit Test: Github

Source:
Example
let rs

rs = pseudoRandomIntsRange()
console.log('pseudoRandomIntsRange', rs)
// => pseudoRandomIntsRange [ 42 ] (預設範圍為0至100)

rs = pseudoRandomIntsRange(0, 100)
console.log('pseudoRandomIntsRange(0, 100)', rs)
// => pseudoRandomIntsRange(0, 100) [ 57 ] //因第2次呼叫故值會不同, 但維持呼叫次數順序時重複執行仍會相同

rs = pseudoRandomIntsRange(0, 100, 2)
console.log('pseudoRandomIntsRange(0, 100)', rs)
// => pseudoRandomIntsRange(0, 100) [ 55, 93 ]

rs = pseudoRandomIntsRange(123, 4567)
console.log('pseudoRandomIntsRange(123, 4567)', rs)
// => pseudoRandomIntsRange(123, 4567) [ 280 ]

rs = pseudoRandomIntsRange(null, null, 2, 123)
console.log('pseudoRandomIntsRange(null, null, 2, 123)', rs)
// => pseudoRandomIntsRange(null, null, 2, 123) [ 70, 28 ]

rs = pseudoRandomIntsRange(null, null, 2, 12.3)
console.log('pseudoRandomIntsRange(null, null, 2, 12.3)', rs)
// => pseudoRandomIntsRange(null, null, 2, 12.3) [ 85, 55 ]

rs = pseudoRandomIntsRange(null, null, 2, 'abc')
console.log('pseudoRandomIntsRange(null, null, 2, "abc")', rs)
// => pseudoRandomIntsRange(null, null, 2, "abc") [ 63, 72 ]

rs = pseudoRandomIntsRange(null, null, 2, 'abc')
console.log('pseudoRandomIntsRange(null, null, 2, "abc")', rs)
// => pseudoRandomIntsRange(null, null, 2, "abc") [ 63, 72 ]

rs = pseudoRandomIntsRange(null, null, 2, 'def')
console.log('pseudoRandomIntsRange(null, null, 2, "def")', rs)
// => pseudoRandomIntsRange(null, null, 2, "def") [ 98, 65 ]

rs = pseudoRandomIntsRange(null, null, 2, 'BH01S123')
console.log('pseudoRandomIntsRange(null, null, 2, "BH01S123")', rs)
// => pseudoRandomIntsRange(null, null, 2, "BH01S123") [ 0, 56 ]

rs = pseudoRandomIntsRange(null, null, 2, 'BH-01:S-123')
console.log('pseudoRandomIntsRange(null, null, 2, "BH-01:S-123")', rs)
// => pseudoRandomIntsRange(null, null, 2, "BH-01:S-123") [ 96, 25 ]

rs = pseudoRandomIntsRange(0, 100, 2, 123)
console.log('pseudoRandomIntsRange(0, 100, 2, 123)', rs)
// => pseudoRandomIntsRange(0, 100, 2, 123) [ 70, 28 ]

rs = pseudoRandomIntsRange(0, 100, 2, 12.3)
console.log('pseudoRandomIntsRange(0, 100, 2, 12.3)', rs)
// => pseudoRandomIntsRange(0, 100, 2, 12.3) [ 85, 55 ]

rs = pseudoRandomIntsRange(0, 100, 2, 'abc')
console.log('pseudoRandomIntsRange(0, 100, 2, "abc")', rs)
// => pseudoRandomIntsRange(0, 100, 2, "abc") [ 63, 72 ]

rs = pseudoRandomIntsRange(0, 100, 2, 'abc')
console.log('pseudoRandomIntsRange(0, 100, 2, "abc")', rs)
// => pseudoRandomIntsRange(0, 100, 2, "abc") [ 63, 72 ]

rs = pseudoRandomIntsRange(0, 100, 2, 'def')
console.log('pseudoRandomIntsRange(0, 100, 2, "def")', rs)
// => pseudoRandomIntsRange(0, 100, 2, "def") [ 98, 65 ]

rs = pseudoRandomIntsRange(0, 100, 2, 'BH-01:S-123')
console.log('pseudoRandomIntsRange(0, 100, 2, "BH-01:S-123")', rs)
// => pseudoRandomIntsRange(0, 100, 2, "BH-01:S-123") [ 96, 25 ]

rs = pseudoRandomIntsRange(123, 4567, 2, 123)
console.log('pseudoRandomIntsRange(123, 4567, 2, 123)', rs)
// => pseudoRandomIntsRange(123, 4567, 2, 123) [ 3218, 1374 ]

rs = pseudoRandomIntsRange(123, 4567, 2, 12.3)
console.log('pseudoRandomIntsRange(123, 4567, 2, 12.3)', rs)
// => pseudoRandomIntsRange(123, 4567, 2, 12.3) [ 3906, 2565 ]

rs = pseudoRandomIntsRange(123, 4567, 2, 'abc')
console.log('pseudoRandomIntsRange(123, 456.7, 2, "abc")', rs)
// => pseudoRandomIntsRange(123, 456.7, 2, "abc") [ 2929, 3305 ]

rs = pseudoRandomIntsRange(123, 4567, 2, 'abc')
console.log('pseudoRandomIntsRange(123, 456.7, 2, "abc")', rs)
// => pseudoRandomIntsRange(123, 456.7, 2, "abc") [ 2929, 3305 ]

rs = pseudoRandomIntsRange(123, 4567, 2, 'def')
console.log('pseudoRandomIntsRange(123, 456.7, 2, "def")', rs)
// => pseudoRandomIntsRange(123, 456.7, 2, "def") [ 4453, 3018 ]

rs = pseudoRandomIntsRange(123, 4567, 2, 'BH01S123')
console.log('pseudoRandomIntsRange(123, 456.7, 2, "BH01S123")', rs)
// => pseudoRandomIntsRange(123, 456.7, 2, "BH01S123") [ 158, 2628 ]

rs = pseudoRandomIntsRange(123, 4567, 2, 'BH-01:S-123')
console.log('pseudoRandomIntsRange(123, 456.7, 2, "BH-01:S-123")', rs)
// => pseudoRandomIntsRange(123, 456.7, 2, "BH-01:S-123") [ 4381, 1227 ]
Parameters:
Name Type Attributes Default Description
vstart Integer <optional>
0

輸入範圍最小值整數,預設0

vend Integer <optional>
100

輸入範圍最大值整數,預設100

n Integer <optional>
1

輸入產生數量整數,預設1

seed Integer | Number | String <optional>
'start1'

輸入種子seed,給予'start1'為使用初始值1並且隨呼叫次數自增,若為其他則代表使用為指定seed,預設'start1'

Returns:

回傳位於指定範圍內隨機整數

Type
Integer

(static) pseudoRandomRange(vstartopt, vendopt, seedopt) → {Number}

Description:
  • 產生位於指定範圍內偽隨機數,值域為[vstart,vend),代表值會大於等於vstart但小於vend

    Unit Test: Github

Source:
Example
let r

r = pseudoRandomRange()
console.log('pseudoRandomRange', r)
// => pseudoRandomRange 0.4170219984371215 (預設範圍為0至1)

r = pseudoRandomRange(0, 1)
console.log('pseudoRandomRange(0,1)', r)
// => pseudoRandomRange(0,1) 0.5665697017684579 //因第2次呼叫故值會不同, 但維持呼叫次數順序時重複執行仍會相同

r = pseudoRandomRange(12.3, 456.7)
console.log('pseudoRandomRange(12.3, 456.7)', r)
// => pseudoRandomRange(12.3, 456.7) 257.074588704668

r = pseudoRandomRange(null, null, 123)
console.log('pseudoRandomRange(null, null, 123)', r)
// => pseudoRandomRange(null, null, 123) 0.6964691872708499

r = pseudoRandomRange(null, null, 12.3)
console.log('pseudoRandomRange(null, null, 12.3)', r)
// => pseudoRandomRange(null, null, 12.3) 0.8510874302592129

r = pseudoRandomRange(null, null, 'abc')
console.log('pseudoRandomRange(null, null, "abc")', r)
// => pseudoRandomRange(null, null, 'abc') 0.6314232510048896

r = pseudoRandomRange(null, null, 'abc')
console.log('pseudoRandomRange(null, null, "abc")', r)
// => pseudoRandomRange(null, null, 'abc') 0.6314232510048896

r = pseudoRandomRange(null, null, 'def')
console.log('pseudoRandomRange(null, null, "def")', r)
// => pseudoRandomRange(null, null, 'def') 0.9743434484116733

r = pseudoRandomRange(null, null, 'BH01S123')
console.log('pseudoRandomRange(null, null, "BH01S123")', r)
// => pseudoRandomRange(null, null, 'BH01S123') 0.007978770649060607

r = pseudoRandomRange(null, null, 'BH-01:S-123')
console.log('pseudoRandomRange(null, null, "BH-01:S-123")', r)
// => pseudoRandomRange(null, null, 'BH-01:S-123') 0.9579511017072946

r = pseudoRandomRange(12.3, 456.7, 123)
console.log('pseudoRandomRange(12.3, 456.7, 123)', r)
// => pseudoRandomRange(12.3, 456.7, 123) 321.81090682316574

r = pseudoRandomRange(12.3, 456.7, 12.3)
console.log('pseudoRandomRange(12.3, 456.7, 12.3)', r)
// => pseudoRandomRange(12.3, 456.7, 12.3) 390.52325400719417

r = pseudoRandomRange(12.3, 456.7, 'abc')
console.log('pseudoRandomRange(12.3, 456.7,"abc")', r)
// => pseudoRandomRange(12.3, 456.7, 'abc') 292.90449274657294

r = pseudoRandomRange(12.3, 456.7, 'abc')
console.log('pseudoRandomRange(12.3, 456.7,"abc")', r)
// => pseudoRandomRange(12.3, 456.7, 'abc') 292.90449274657294

r = pseudoRandomRange(12.3, 456.7, 'def')
console.log('pseudoRandomRange(12.3, 456.7,"def")', r)
// => pseudoRandomRange(12.3, 456.7, 'def') 445.2982284741476

r = pseudoRandomRange(12.3, 456.7, 'BH01S123')
console.log('pseudoRandomRange(12.3, 456.7,"BH01S123")', r)
// => pseudoRandomRange(12.3, 456.7, 'BH01S123') 15.845765676442534

r = pseudoRandomRange(12.3, 456.7, 'BH-01:S-123')
console.log('pseudoRandomRange(12.3, 456.7,"BH-01:S-123")', r)
// => pseudoRandomRange(12.3, 456.7, 'BH-01:S-123') 438.0134695987217
Parameters:
Name Type Attributes Default Description
vstart Number <optional>
0

輸入範圍最小值,預設0

vend Number <optional>
1

輸入範圍最大值,預設1

seed Integer | Number | String <optional>
'start1'

輸入種子seed,給予'start1'為使用初始值1並且隨呼叫次數自增,若為其他則代表使用為指定seed,預設'start1'

Returns:

回傳位於指定範圍內隨機數字

Type
Number

(static) pseudoRandomsRange(vstartopt, vendopt, nopt, seedopt) → {Number}

Description:
  • 產生位於指定範圍內偽隨機數陣列,各元素值域為[vstart,vend),代表值會大於等於vstart但小於vend

    Unit Test: Github

Source:
Example
let rs

rs = pseudoRandomsRange()
console.log('pseudoRandomsRange', rs)
// => pseudoRandomsRange [ 0.4170219984371215 ] (預設範圍為0至1)

rs = pseudoRandomsRange(0, 1)
console.log('pseudoRandomsRange(0,1)', rs)
// => pseudoRandomsRange(0,1) [ 0.5665697017684579 ] //因第2次呼叫故值會不同, 但維持呼叫次數順序時重複執行仍會相同

rs = pseudoRandomsRange(0, 1, 2)
console.log('pseudoRandomsRange(0,1,2)', rs)
// => pseudoRandomsRange(0,1,2) [ 0.5507979043759406, 0.9240253241732717 ]

rs = pseudoRandomsRange(12.3, 456.7)
console.log('pseudoRandomsRange(12.3, 456.7)', rs)
// => pseudoRandomsRange(12.3, 456.7) [ 28.09080612603575 ]

rs = pseudoRandomsRange(12.3, 456.7, 2)
console.log('pseudoRandomsRange(12.3, 456.7, 2)', rs)
// => pseudoRandomsRange(12.3, 456.7, 2) [ 110.95376535998656, 36.822045660205184 ]

rs = pseudoRandomsRange(null, null, 2, 123)
console.log('pseudoRandomsRange(null, null, 2, 123)', rs)
// => pseudoRandomsRange(null, null, 2, 123) [ 0.6964691872708499, 0.28155276202596724 ]

rs = pseudoRandomsRange(null, null, 2, 12.3)
console.log('pseudoRandomsRange(null, null, 2, 12.3)', rs)
// => pseudoRandomsRange(null, null, 2, 12.3) [ 0.8510874302592129, 0.5495069304015487 ]

rs = pseudoRandomsRange(null, null, 2, 'abc')
console.log('pseudoRandomsRange(null, null, 2, "abc")', rs)
// => pseudoRandomsRange(null, null, 2, 'abc') [ 0.6314232510048896, 0.7160592079162598 ]

rs = pseudoRandomsRange(null, null, 2, 'abc')
console.log('pseudoRandomsRange(null, null, 2, "abc")', rs)
// => pseudoRandomsRange(null, null, 2, 'abc') [ 0.6314232510048896, 0.7160592079162598 ]

rs = pseudoRandomsRange(null, null, 2, 'def')
console.log('pseudoRandomsRange(null, null, 2, "def")', rs)
// => pseudoRandomsRange(null, null, 2, 'def') [ 0.9743434484116733, 0.6514900834299624 ]

rs = pseudoRandomsRange(null, null, 2, 'BH01S123')
console.log('pseudoRandomsRange(null, null, 2, "BH01S123")', rs)
// => pseudoRandomsRange(null, null, 2, 'BH01S123') [ 0.007978770649060607, 0.5637312876060605 ]

rs = pseudoRandomsRange(null, null, 2, 'BH-01:S-123')
console.log('pseudoRandomsRange(null, null, 2, "BH-01:S-123")', rs)
// => pseudoRandomsRange(null, null, 2, 'BH-01:S-123') [ 0.9579511017072946, 0.24845449766144156 ]

rs = pseudoRandomsRange(12.3, 456.7, 2, 123)
console.log('pseudoRandomsRange(12.3, 456.7, 2, 123)', rs)
// => pseudoRandomsRange(12.3, 456.7, 2, 123) [ 321.81090682316574, 137.42204744433985 ]

rs = pseudoRandomsRange(12.3, 456.7, 2, 12.3)
console.log('pseudoRandomsRange(12.3, 456.7, 2, 12.3)', rs)
// => pseudoRandomsRange(12.3, 456.7, 2, 12.3) [ 390.52325400719417, 256.5008798704483 ]

rs = pseudoRandomsRange(12.3, 456.7, 2, 'abc')
console.log('pseudoRandomsRange(12.3, 456.7, 2, "abc")', rs)
// => pseudoRandomsRange(12.3, 456.7, 2, 'abc') [ 292.90449274657294, 330.51671199798585 ]

rs = pseudoRandomsRange(12.3, 456.7, 2, 'abc')
console.log('pseudoRandomsRange(12.3, 456.7, 2, "abc")', rs)
// => pseudoRandomsRange(12.3, 456.7, 2, 'abc') [ 292.90449274657294, 330.51671199798585 ]

rs = pseudoRandomsRange(12.3, 456.7, 2, 'def')
console.log('pseudoRandomsRange(12.3, 456.7, 2, "def")', rs)
// => pseudoRandomsRange(12.3, 456.7, 2, 'def') [ 445.2982284741476, 301.8221930762753 ]

rs = pseudoRandomsRange(12.3, 456.7, 2, 'BH01S123')
console.log('pseudoRandomsRange(12.3, 456.7, 2, "BH01S123")', rs)
// => pseudoRandomsRange(12.3, 456.7, 2, 'BH01S123') [ 15.845765676442534, 262.82218421213327 ]

rs = pseudoRandomsRange(12.3, 456.7, 2, 'BH-01:S-123')
console.log('pseudoRandomsRange(12.3, 456.7, 2, "BH-01:S-123")', rs)
// => pseudoRandomsRange(12.3, 456.7, 2, 'BH-01:S-123') [ 438.0134695987217, 122.71317876074463 ]
Parameters:
Name Type Attributes Default Description
vstart Number <optional>
0

輸入範圍最小值,預設0

vend Number <optional>
1

輸入範圍最大值,預設1

n Integer <optional>
1

輸入產生數量整數,預設1

seed Integer | Number | String <optional>
'start1'

輸入種子seed,給予'start1'為使用初始值1並且隨呼叫次數自增,若為其他則代表使用為指定seed,預設'start1'

Returns:

回傳位於指定範圍內隨機數字

Type
Number

(static) queue(takeLimitopt, optopt) → {Object}

Description:
  • 佇列處理器,單生產者單消費者模式,具有消息堵塞與可限定同時處理上限數量功能

    Unit Test: Github

Source:
Example
async function topAsync() {

    async function test(takeLimit, timeCallBack) {
        return new Promise((resolve, reject) => {

            //queue
            let q = queue(takeLimit)
            let n = 0
            let ms = []

            //message
            q.on('message', function(qs) {
                console.log('message', JSON.stringify(qs))

                //ms
                ms.push(JSON.parse(JSON.stringify(qs)))

                //get
                let v = q.get()
                if (!v) {
                    return
                }
                console.log('get', v)

                setTimeout(function() {
                    console.log('cb', v)

                    //cb
                    q.cb()

                    //resolve
                    if (v === '$10') {
                        resolve(ms)
                    }

                }, timeCallBack)

            })

            //queues push 1~5
            setTimeout(function() {
                console.log('queues push 1~5')
                let t = setInterval(function() {
                    n += 1
                    q.push('$' + n)
                    if (n === 5) {
                        clearInterval(t)
                    }
                }, 50)
            }, 1)

            //queues push 6~10 by delay 1s
            setTimeout(function() {
                console.log('queues push 6~10')
                let t = setInterval(function() {
                    n += 1
                    q.push('$' + n)
                    if (n === 10) {
                        clearInterval(t)
                    }
                }, 50)
            }, 500)

        })
    }

    console.log('test1')
    let r1 = await test(2, 1000)
    console.log(JSON.stringify(r1))
    // queues push 1~5
    // message ["$1"]
    // get $1
    // message ["$2"]
    // get $2
    // queues push 6~10
    // cb $1
    // message ["$3","$4","$5","$6","$7","$8","$9","$10"]
    // get $3
    // cb $2
    // message ["$4","$5","$6","$7","$8","$9","$10"]
    // get $4
    // cb $3
    // message ["$5","$6","$7","$8","$9","$10"]
    // get $5
    // cb $4
    // message ["$6","$7","$8","$9","$10"]
    // get $6
    // cb $5
    // message ["$7","$8","$9","$10"]
    // get $7
    // cb $6
    // message ["$8","$9","$10"]
    // get $8
    // cb $7
    // message ["$9","$10"]
    // get $9
    // cb $8
    // message ["$10"]
    // get $10
    // cb $9
    // cb $10
    // [["$1"],["$2"],["$3","$4","$5","$6","$7","$8","$9","$10"],["$4","$5","$6","$7","$8","$9","$10"],["$5","$6","$7","$8","$9","$10"],["$6","$7","$8","$9","$10"],["$7","$8","$9","$10"],["$8","$9","$10"],["$9","$10"],["$10"]]

    console.log('test2')
    let r2 = await test(0, 500) //takeLimit=0, timeCallBack=500ms
    console.log(JSON.stringify(r2))
    // test2
    // queues push 1~5
    // message ["$1"]
    // get $1
    // message ["$2"]
    // get $2
    // message ["$3"]
    // get $3
    // message ["$4"]
    // get $4
    // message ["$5"]
    // get $5
    // queues push 6~10
    // message ["$6"]
    // get $6
    // cb $1
    // message ["$7"]
    // get $7
    // cb $2
    // message ["$8"]
    // get $8
    // cb $3
    // message ["$9"]
    // get $9
    // cb $4
    // message ["$10"]
    // get $10
    // cb $5
    // cb $6
    // cb $7
    // cb $8
    // cb $9
    // cb $10
    // [["$1"],["$2"],["$3"],["$4"],["$5"],["$6"],["$7"],["$8"],["$9"],["$10"]]

}
topAsync().catch(() => {})
Parameters:
Name Type Attributes Default Description
takeLimit Integer <optional>
0

輸入同時處理數量整數,預設0,代表無限制

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'push'時為消息加入佇列(含size為加入後之佇列長度),'hold'時為push後因同時處理數量已達takeLimit而暫不派發message(含size為佇列長度,taking為目前處理中數量),'message'時為即將派發message事件(含by為'push'或'cb'代表由何者觸發,size為派發當下之佇列長度),'get'時為呼叫get(含hit為佇列是否有消息可取,size為取出後之佇列長度,taking為取出後之處理中數量),'cb'時為呼叫cb(含size為佇列長度,taking為扣減後之處理中數量),'clear'時為呼叫clear(含size與taking為清空前之佇列長度與處理中數量);回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響佇列,預設null

Returns:

回傳事件物件,可呼叫函數on、push、get、cb、clear。on為監聽事件,需自行監聽message事件,message事件傳入派發當下佇列之快照(淺複製之陣列,各消息本身不複製),監聽器修改該快照不影響佇列,其後佇列之push、get、clear亦不改變已傳出之快照,所有監聽器收到的是同一份快照,每次派發需複製一次佇列,成本與積壓數成正比,push為加入最新佇列消息,get為回傳當前最早佇列消息,cb為於message事件內回調使迭代器可取得下一個佇列消息,clear為清空佇列;get以陣列前端移出實作,積壓量大時每次get之成本與積壓數成正比。事件物件為原生EventEmitter(eventemitter3)。本模組於派發處以try攔截監聽器之同步拋錯,故其不會使行程崩潰,會改以error事件回報{ fun: 'listener', name, msg, args },無error監聽者則console.error;惟依EventEmitter規範,同一次派發中先拋錯之監聽器會中止該次派發,其後之監聽器不再被呼叫。async監聽器之reject不被攔截(規範上emit不觀察監聽器回傳值),須由監聽器自行處理,否則為unhandledRejection

Type
Object

(static) random() → {Number}

Description:
  • 產生隨機數,範圍[0,1),代表0≦隨機數<1

    Unit Test: Github

Source:
Example
let r

r = random()
console.log('random', r)
// => random [0,1) (預設範圍為0至1)
Returns:

回傳隨機數字

Type
Number

(static) randomIntRange(vstartopt, vendopt) → {Integer}

Description:
  • 產生位於指定範圍內隨機整數,值域為[vstart,vend],代表值會大於等於vstart且小於等於vend

    Unit Test: Github

Source:
Example
let r

r = randomIntRange()
console.log('randomIntRange', r)
// => randomIntRange [0,100] (預設範圍為0至100)

r = randomIntRange(0, 100)
console.log('randomIntRange(0, 100)', r)
// => randomIntRange(0, 100) [0,100]

r = randomIntRange(123, 4567)
console.log('randomIntRange(123, 4567)', r)
// => randomIntRange(123, 4567) [123,4567]
Parameters:
Name Type Attributes Default Description
vstart Integer <optional>
0

輸入範圍最小值整數,預設0

vend Integer <optional>
100

輸入範圍最大值整數,預設100

Returns:

回傳位於指定範圍內隨機整數

Type
Integer

(static) randomIntsNdpRange(vstartopt, vendopt, nopt) → {Integer}

Description:
  • 產生位於指定範圍內不重複之偽隨機整數陣列,各元素值域為[vstart,vend],代表值會大於等於vstart且小於等於vend

    Unit Test: Github

Source:
Example
let rs

rs = randomIntsNdpRange()
console.log('randomIntsNdpRange', rs)
// => randomIntsNdpRange [ [0,100] ] (預設範圍為0至100)

rs = randomIntsNdpRange(0, 100)
console.log('randomIntsNdpRange(0, 100)', rs)
// => randomIntsNdpRange(0, 100) [ [0,100] ]

rs = randomIntsNdpRange(0, 100, 2)
console.log('randomIntsNdpRange(0, 100)', rs)
// => randomIntsNdpRange(0, 100, 2) [ [0,100], [0,100] ]

rs = randomIntsNdpRange(123, 4567)
console.log('randomIntsNdpRange(123, 4567)', rs)
// => randomIntsNdpRange(123, 4567) [ [123,4567], [123,4567] ]

rs = randomIntsNdpRange(123, 4567, 2)
console.log('randomIntsNdpRange(123, 4567, 2)', rs)
// => randomIntsNdpRange(123, 4567, 2) [ [123,4567], [123,4567] ]
Parameters:
Name Type Attributes Default Description
vstart Integer <optional>
0

輸入範圍最小值整數,預設0

vend Integer <optional>
100

輸入範圍最大值整數,預設100

n Integer <optional>
1

輸入產生數量整數,最長為vend-vstart+1個,預設1

Returns:

回傳位於指定範圍內隨機整數

Type
Integer

(static) randomIntsRange(vstartopt, vendopt, nopt) → {Integer}

Description:
  • 產生位於指定範圍內隨機整數陣列,各元素值域為[vstart,vend],代表值會大於等於vstart且小於等於vend

    Unit Test: Github

Source:
Example
let rs

rs = randomIntsRange()
console.log('randomIntsRange', rs)
// => randomIntsRange [ [0,100] ] (預設範圍為0至100)

rs = randomIntsRange(0, 100)
console.log('randomIntsRange(0, 100)', rs)
// => randomIntsRange(0, 100) [ [0,100] ]

rs = randomIntsRange(0, 100, 2)
console.log('randomIntsRange(0, 100, 2)', rs)
// => randomIntsRange(0, 100, 2) [ [0,100], [0,100] ]

rs = randomIntsRange(123, 4567)
console.log('randomIntsRange(123, 4567)', rs)
// => randomIntsRange(123, 4567) [ [123,4567] ]

rs = randomIntsRange(123, 4567, 2)
console.log('randomIntsRange(123, 4567, 2)', rs)
// => randomIntsRange(123, 4567, 2) [ [123,4567], [123,4567] ]
Parameters:
Name Type Attributes Default Description
vstart Integer <optional>
0

輸入範圍最小值整數,預設0

vend Integer <optional>
100

輸入範圍最大值整數,預設100

n Integer <optional>
1

輸入產生數量整數,預設1

Returns:

回傳位於指定範圍內隨機整數

Type
Integer

(static) randomRange(vstartopt, vendopt) → {Number}

Description:
  • 產生位於指定範圍內隨機數,值域為[vstart,vend),代表值會大於等於vstart但小於vend

    Unit Test: Github

Source:
Example
let r

r = randomRange()
console.log('randomRange', r)
// => randomRange random [0,1) (預設範圍為0至1)

r = randomRange(0, 1)
console.log('randomRange(0,1)', r)
// => randomRange(0,1) random [0,1)

r = randomRange(12.3, 456.7)
console.log('randomRange(12.3, 456.7)', r)
// => randomRange(12.3, 456.7) random [12.3, 456.7)
Parameters:
Name Type Attributes Default Description
vstart Number <optional>
0

輸入範圍最小值,預設0

vend Number <optional>
1

輸入範圍最大值,預設1

Returns:

回傳位於指定範圍內隨機數字

Type
Number

(static) randomsRange(vstartopt, vendopt, nopt) → {Number}

Description:
  • 產生位於指定範圍內隨機數陣列,各元素值域為[vstart,vend),代表值會大於等於vstart但小於vend

    Unit Test: Github

Source:
Example
let rs

rs = randomsRange()
console.log('randomsRange', rs)
// => randomsRange [ [0,1) ] (預設範圍為0至1)

rs = randomsRange(0, 1)
console.log('randomsRange(0,1)', rs)
// => randomsRange(0,1) [ [0,1) ]

rs = randomsRange(0, 1, 2)
console.log('randomsRange(0,1,2)', rs)
// => randomsRange(0,1,2) [ [0,1), [0,1) ]

rs = randomsRange(12.3, 456.7)
console.log('randomsRange(12.3, 456.7)', rs)
// => randomsRange(12.3, 456.7) [ [12.3, 456.7) ]

rs = randomsRange(12.3, 456.7, 2)
console.log('randomsRange(12.3, 456.7, 2)', rs)
// => randomsRange(12.3, 456.7, 2) [ [12.3, 456.7), [12.3, 456.7) ]
Parameters:
Name Type Attributes Default Description
vstart Number <optional>
0

輸入範圍最小值,預設0

vend Number <optional>
1

輸入範圍最大值,預設1

n Integer <optional>
1

輸入產生數量整數,預設1

Returns:

回傳位於指定範圍內隨機數字

Type
Number

(static) rang(rStart, rEnd, numopt) → {Boolean}

Description:
  • 依照起訖值與切分數量產生陣列,切分數量代表間隔數量,故切3份時包含起訖點就會有4點

    Unit Test: Github

Source:
Example
let r

r = rang(0, 10)
console.log(r)
// => [ 0, 5, 10 ]

r = rang(0, 10, 10)
console.log(r)
// => [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

r = rang(0, 10, 3)
console.log(r)
// => [ 0, 3.3333333333333335, 6.666666666666667, 10 ]

r = rang(0, 10, 4)
console.log(r)
// => [ 0, 2.5, 5, 7.5, 10 ]
Parameters:
Name Type Attributes Default Description
rStart Number

輸入起始數字

rEnd Number

輸入結束數字

num Integer <optional>
2

輸入切分數量正整數,預設2

Returns:

回傳判斷布林值

Type
Boolean

(static) rep(c, n) → {Array}

Description:
  • 產生重複n次字串

    Unit Test: Github

Source:
Example
console.log(rep('abc', 2))
// => 'abcabc'
Parameters:
Name Type Description
c String

輸入欲重複的字串

n Integer

輸入重複次數之含零正整數

Returns:

回傳任意資料之陣列

Type
Array

(static) repObj(o, n) → {Array}

Description:
  • 產生重複n次任意資料之陣列

    Unit Test: Github

Source:
Example
console.log(repObj({ a: 12.45, b: 'opqr' }, 2))
// => [{ a: 12.45, b: 'opqr' }, { a: 12.45, b: 'opqr' }]

console.log(repObj({ a: 12.45, b: 'opqr' }, 0))
// => []
Parameters:
Name Type Description
o *

輸入任意資料

n Integer

輸入重複次數之正整數

Returns:

回傳任意資料之陣列

Type
Array

(static) replace(c, t, r) → {String}

Description:
  • 取代字串 針對c查找是否含有t,並將t全部取代成r 若輸入c,t,r不是字串時則回傳空字串

    Unit Test: Github

Source:
Example
console.log(replace('1.25abc', '5a', '0'))
// => '1.20bc'
Parameters:
Name Type Description
c String

輸入要被取代的字串

t String

輸入要查找的字串

r String

輸入要取代的字串

Returns:

回傳取代後字串

Type
String

(static) replaceObj(c, o) → {String}

Description:
  • 取代字串,由c內大括號會標記為被取代的{key},再由物件o的key所對應value進行取代,而各value皆需要為有效字串

    Unit Test: Github

Source:
Example
console.log(replaceObj('1.25abc中文', { '5a': '0', '中': '英' }))
// => '1.20bc英文'
Parameters:
Name Type Description
c String

輸入要被取代的字串

o Object

輸入用來取代的key,value物件

Returns:

回傳取代後字串

Type
String

(static) replacePlus(c, pre, aft, notopt, r) → {String}

Description:
  • 取代字串,找尋條件為滿足具字首字尾則被取代

    Unit Test: Github

Source:
Example
console.log(replacePlus('1.25mn1.25abc中文結尾', '1', '文', null, 'xyz'))
// => 'xyz結尾'

console.log(replacePlus('1.25mn1.25abc中文結尾', '1', '文', 'm', 'xyz'))
// => '1.25mnxyz結尾'
Parameters:
Name Type Attributes Default Description
c String

輸入要被取代的字串

pre String

輸入需為字首的字串

aft String

輸入需為字尾的字串

not String <optional>
null

輸入需不包含的字串,預設為不使用

r String

輸入要取代的字串

Returns:

回傳取代後字串

Type
String

(static) round(v, idigopt, optopt) → {Number}

Description:
  • 數字或字串四捨五入至指定位數(0位為整數)

    Unit Test: Github

Source:
Example
console.log(round(1.5))
// => 2

console.log(round('1.5'))
// => 2

console.log(round(-1.5))
// => -2 (四捨五入不看正負號得-2)

console.log(round('125abc'))
// => ''

console.log(round(9.83501, 2))
// => 9.84

console.log(round(9.8350, 2))
// => 9.84

console.log(round(9.82501, 2))
// => 9.83

console.log(round(9.8250, 2))
// => 9.83

console.log(round(9.8249, 2))
// => 9.82

console.log(round(12.6449, 2))
// => 12.64

console.log(round(12.645, 2))
// => 12.65

console.log(round(12.6451, 2))
// => 12.65

console.log(round(12.65, 2))
// => 12.65

console.log(round(12.64, 2))
// => 12.64

console.log(round(12.6, 2))
// => 12.6

console.log(round(12, 2))
// => 12

console.log(round(9.83501, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 9.84

console.log(round(9.8350, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 9.84 (四捨六入五成雙, 第3位5進位得9.84)

console.log(round(9.82501, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 9.83

console.log(round(9.8250, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 9.82 (四捨六入五成雙, 第3位5捨去得9.82)

console.log(round(9.8249, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 9.82

console.log(round(12.6449, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 12.64

console.log(round(12.645, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 12.64 (四捨六入五成雙, 第3位5捨去得12.64)

console.log(round(12.6451, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 12.65

console.log(round(12.65, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 12.65

console.log(round(12.64, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 12.64

console.log(round(12.6, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 12.6

console.log(round(12, 2, { rounding: 'ROUND_HALF_EVEN' }))
// => 12
Parameters:
Name Type Attributes Default Description
v Number | String

輸入數字或字串

idig Integer <optional>
0

輸入指定位數整數,預設0

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
rounding String <optional>
'ROUND_HALF_UP'

輸入取捨方式,可選'ROUND_HALF_UP'代表四捨五入或'ROUND_HALF_EVEN'代表四捨六入五成雙,預設'ROUND_HALF_UP'

Returns:

回傳四捨五入的數字

Type
Number

(static) secd(deg) → {Number}

Description:
  • 輸入角度計算sec值

    Unit Test: Github

Source:
Example
console.log(secd(0))
// => 1

console.log(secd(30))
// => 1.1547005383792515

console.log(secd(45))
// => 1.414213562373095

console.log(secd(60))
// => 1.9999999999999996

console.log(secd(90))
// => 16331239353195370

console.log(secd(120))
// => -2.000000000000001

console.log(secd(135))
// => -1.4142135623730951

console.log(secd(150))
// => -1.1547005383792515

console.log(secd(180))
// => -1

console.log(secd(210))
// => -1.1547005383792517

console.log(secd(225))
// => -1.4142135623730947

console.log(secd(240))
// => -1.9999999999999982

console.log(secd(270))
// => -5443746451065123

console.log(secd(300))
// => 1.9999999999999996

console.log(secd(315))
// => 1.4142135623730954

console.log(secd(330))
// => 1.154700538379252

console.log(secd(360))
// => 1

console.log(secd(450))
// => 3266247870639073.5

console.log(secd(720))
// => 1
Parameters:
Name Type Description
deg Number | String

輸入角度數字或字串

Returns:

回傳sec值

Type
Number

(static) sep(c, t) → {Array}

Description:
  • 切割字串並剔除非有效元素

    Unit Test: Github

Source:
Example
console.log(sep('1.25 abc  中文', ' '))
// => ['1.25', 'abc', '中文']
Parameters:
Name Type Description
c String

輸入要被切割的字串

t String

輸入用來切割的符號字串

Returns:

回傳切割後的字串陣列

Type
Array

(static) sepInt(v, n, breverseopt) → {Array}

Description:
  • 由整數v切分n個正整數,回傳切分後之整數陣列

    Unit Test: Github

Source:
Example
console.log(sepInt(10, 3))
// => [3, 3, 4]
Parameters:
Name Type Attributes Default Description
v Integer

輸入要被切分的整數

n Integer

輸入要切分數量的整數

breverse Boolean <optional>
true

輸入是否要反轉順序,預設true為由小至大,反序則為由大至小

Returns:

回傳切分後的整數陣列

Type
Array

(static) setIntervalHp(fun, intervalopt, optopt) → {function}

Description:
  • 高精度setInterval

    Unit Test: Github

Source:
Example
let i = 0
let t = setIntervalHp(() => {
    i++
    //content
    if (i > 10) {
        t()
    }
}, 50)
Parameters:
Name Type Attributes Default Description
fun function

輸入調用函數

interval Number <optional>
1

輸入時間間隔,須為正有限數(通常為正整數,數字字串亦可),單位為毫秒ms,預設1;0、負數、NaN、Infinity、非數字或空白字串等無效值時不啟動,見回傳說明

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
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:

回傳停止函數,呼叫後不再調用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內自行處理(如以旗標略過)

Type
function

(static) setTimeoutHp(fun, intervalopt, optopt) → {null}

Description:
  • 高精度setTimeoutHp

    Unit Test: Github

Source:
Example
let t = setTimeoutHp(() => {
    //content
}, 1000)
Parameters:
Name Type Attributes Default Description
fun function

輸入調用函數

interval Number <optional>
1

輸入時間間隔,須為正有限數(通常為正整數,數字字串亦可),單位為毫秒ms,預設1;0、負數、NaN、Infinity、非數字或空白字串等無效值時不啟動,見回傳說明

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
onTrace function <optional>
null

輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'start'時為開始計時(無其他欄位),'fire'時為已距建立達interval而即將調用fun(含elapsed為距建立實際經過之毫秒數,必不小於interval;僅通知一次),'result'時為該次調用已有結果而即將記錄(含state為'success'、'error'(同步拋錯或Promise被reject,即將console.log記錄)、'cancelled'或'invalid'(fun非函數,即將console.log記錄));回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響計時與調用,預設null

Returns:

回傳null(無停止函數)。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')

Type
null

(static) sind(deg) → {Number}

Description:
  • 輸入角度計算sin值

    Unit Test: Github

Source:
Example
console.log(sind(0))
// => 0

console.log(sind(30))
// => 0.49999999999999994

console.log(sind(45))
// => 0.7071067811865475

console.log(sind(60))
// => 0.8660254037844386

console.log(sind(90))
// => 1

console.log(sind(120))
// => 0.8660254037844387

console.log(sind(135))
// => 0.7071067811865476

console.log(sind(150))
// => 0.49999999999999994

console.log(sind(180))
// => 1.2246467991473532e-16

console.log(sind(210))
// => -0.5000000000000001

console.log(sind(225))
// => -0.7071067811865475

console.log(sind(240))
// => -0.8660254037844385

console.log(sind(270))
// => -1

console.log(sind(300))
// => -0.8660254037844386

console.log(sind(315))
// => -0.7071067811865477

console.log(sind(330))
// => -0.5000000000000004

console.log(sind(360))
// => -2.4492935982947064e-16

console.log(sind(450))
// => 1

console.log(sind(720))
// => -4.898587196589413e-16
Parameters:
Name Type Description
deg Number | String

輸入角度數字或字串

Returns:

回傳sin值

Type
Number

(static) split(c, t) → {Array}

Description:
  • 切割字串

    Unit Test: Github

Source:
Example
console.log(split('1.25 abc  中文', ' '))
// => ['1.25', 'abc', '', '中文']
Parameters:
Name Type Description
c String

輸入要被切割的字串

t String

輸入用來切割的符號字串

Returns:

回傳切割後的字串陣列

Type
Array

(static) str2aes(str, key, base64opt, optopt) → {String|Object}

Description:
  • 一般字串轉AES字串 使用AES-128-CBC加密,字串採用PKCS#7填充

    Unit Test: Github

Source:
Example
let str = 'test中文abcdefghijklmn'
let key = '1234567890abcdefghijk'
console.log(str2aes(str, key))
// => 53616c7465645f5f9d7366e54b51e9352dbccf6a6ec9b3af0595b19b345ea903b713143f5d66417f4cd968c628863f61 (is random)

console.log(str2aes(str, key, true))
// => U2FsdGVkX19c7rKkQ38SfqZLaQEKzLD0PhXzzdYeGbngewsPmzS8PcOwHQIsf2Zo (is random)
Parameters:
Name Type Attributes Default Description
str String

輸入一般字串,非有效字串時回傳空字串

key String

輸入加密key,非有效字串時回傳空字串

base64 Boolean <optional>
false

輸入是否轉為base64字串,非布林值時回退為false,預設為false

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳經AES轉換後字串,採Hex/base64顯示,str或key非有效字串時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) str2b64(str, optopt) → {String|Object}

Description:
  • 一般字串轉base64字串

    Unit Test: Github

Source:
Example
console.log(str2b64('test中文'))
// => 'dGVzdOS4reaWhw=='
Parameters:
Name Type Attributes Default Description
str String

輸入一般字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳base64字串,輸入非有效字串時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) str2hint(str, optopt) → {Integer|Object}

Description:
  • 一般字串轉hash整數

    Unit Test: Github

Source:
Example
console.log(str2hint('abc'))
// => 807794786

console.log(str2hint('123'))
// => 408093746

console.log(str2hint('12.3'))
// => 10159942

console.log(str2hint(''))
// => 0

console.log(str2hint(null))
// => null
Parameters:
Name Type Attributes Default Description
str String

輸入一般字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳轉換後整數,輸入非字串時回傳null,輸入空字串時回傳0;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Integer | Object

(static) str2md5(str, base64opt, optopt) → {String|Object}

Description:
  • 一般字串轉MD5字串

    Unit Test: Github

Source:
Example
console.log(str2md5('test中文'))
// => '5393554e94bf0eb6436f240a4fd71282'
Parameters:
Name Type Attributes Default Description
str String

輸入一般字串,非有效字串時回傳空字串

base64 Boolean <optional>
false

輸入是否轉為base64字串,非布林值時回退為false,預設為false

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳經MD5轉換後字串,str非有效字串時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) str2obj(data, extopt, optopt) → {Object}

Description:
  • 字串轉任意資料,可支援內含Uint8Array與Uint16Array數據

    Unit Test: Github

Source:
Example
console.log(str2obj('{"a":"abc","b":12.3,"u8a":"[Uint8Array]::QmFz","u16a":{"0":11,"1":79,"2":6}}'))
// => {
// =>   a: 'abc',
// =>   b: 12.3,
// =>   u8a: Uint8Array [ 66, 97, 115 ],
// =>   u16a: { '0': 11, '1': 79, '2': 6 }
// => }

console.log(str2obj('{"a":"abc","b":12.3,"u8a":"[Uint8Array]::QmFz","u16a":"[Uint16Array]::CwBPAAYA"}', ['Uint8Array', 'Uint16Array']}))
// => {
// =>   a: 'abc',
// =>   b: 12.3,
// =>   u8a: Uint8Array [ 66, 97, 115 ],
// =>   u16a: Uint16Array [ 11, 79, 6 ]
// => }
Parameters:
Name Type Attributes Default Description
data *

輸入任意資料

ext String | Array <optional>
'Uint8Array'

輸入擴充數據種類字串或陣列,非字串亦非陣列時回退為'Uint8Array',預設'Uint8Array'

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳轉換後資料,data非字串或無法解析為JSON時回傳空物件;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Object

(static) str2sha(str, n, base64opt, optopt) → {String|Object}

Description:
  • 一般字串轉SHA字串 Secure Hash Algorithm n位

    n僅可為1、224、256、384、512,分別對應SHA-1、SHA-224、SHA-256、SHA-384、SHA-512 其中SHA-1之1為演算法版本號而非輸出位元數(實際輸出160位元),且已被證實可構造碰撞,僅適用於非資安用途之校驗 n為其餘值時視為程式撰寫錯誤而擲出錯誤,非正整數擲出'n is not a positive integer',正整數但不在可用範圍則擲出'invalid n[n]'

    Unit Test: Github

Source:
Example
console.log(str2sha('test中文', 1))
// => 'ecabf586cef0d3b11c56549433ad50b81110a836'

console.log(str2sha('test中文', 224))
// => 'c9b3eebeae6fe7e42cd9680475787a77e234bce0abb8d599d995b2a6'

console.log(str2sha('test中文', 256))
// => '8af7c7959618cc0900be74a52eee44aca05aeb72525864dc7ae25d31761beb65'

console.log(str2sha('test中文', 384))
// => '219ece10ffe81c179511d4ffa43a0c75bbee737acdd18ee11839a1def78a4e10c94186ef8483fce85d0589bcbddf632e'

console.log(str2sha('test中文', 512))
// => 'ab43333a01571e767a2eb15460c270fa123964c121b88c73e274a211d6e2cf98b10d89220e8d04e2fc573354da93400f3255e94d445dc956e177aa4201a4fc30'

console.log(str2sha('test中文', 256, true))
// => 'ivfHlZYYzAkAvnSlLu5ErKBa63JSWGTceuJdMXYb62U='

try {
    str2sha('test中文', 160) //160為SHA-1之輸出位元數而非其演算法名稱, 不可用
}
catch (err) {
    console.log(err.message)
    // => invalid n[160]
}

try {
    str2sha('test中文', 'abc')
}
catch (err) {
    console.log(err.message)
    // => n is not a positive integer
}
Parameters:
Name Type Attributes Default Description
str String

輸入一般字串,非有效字串時回傳空字串

n Number

輸入演算法位數,僅可為1、224、256、384、512,其餘一律擲出錯誤,但opt.returnWithStateAndMsg為true時改以error狀態回報而不擲出

base64 Boolean <optional>
false

輸入是否轉為base64字串,非布林值時回退為false,預設為false

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳經SHA-n轉換後字串,str非有效字串時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) str2sha512(str, base64opt, optopt) → {String|Object}

Description:
  • 一般字串轉SHA512字串 Secure Hash Algorithm 512位

    內部調用str2sha並固定n為512

    Unit Test: Github

Source:
Example
console.log(str2sha512('test中文'))
// => 'ab43333a01571e767a2eb15460c270fa123964c121b88c73e274a211d6e2cf98b10d89220e8d04e2fc573354da93400f3255e94d445dc956e177aa4201a4fc30'
Parameters:
Name Type Attributes Default Description
str String

輸入一般字串,非有效字串時回傳空字串

base64 Boolean <optional>
false

輸入是否轉為base64字串,非布林值時回退為false,預設為false

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳經SHA512轉換後字串,str非有效字串時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) str2u8arr(str, optopt) → {Uint8Array|Object}

Description:
  • 字串轉Uint8Array

    以原生TextEncoder編碼為UTF-8,字串含孤立代理碼元(非良構之UTF-16,無法正確編碼為UTF-8)時視為轉換失敗

    Unit Test: Github

Source:
Example
console.log(str2u8arr('test中文'))
// => Uint8Array [116, 101, 115, 116, 228, 184, 173, 230, 150, 135]
Parameters:
Name Type Attributes Default Description
str String

輸入一般字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Uint8Array,輸入非有效字串、含孤立代理碼元或轉換失敗時回傳空Uint8Array;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Uint8Array | Object

(static) strCompare(str1, str2) → {Number}

Description:
Source:
Example
console.log(strCompare('The Woodman(樵夫) set to work at once, and so...', 'Wodooman(樵夫)'))
// => 0.375
Parameters:
Name Type Description
str1 String

輸入欲比對之一般字串

str2 String

輸入欲比對之一般字串

Returns:

回傳字串相似度0~1

Type
Number

(static) strCompareForVersion(v1, v2) → {Number|null}

Description:
  • 比較2版本號字串之大小,自各字串取第一個「數字.數字.數字」後逐段以數值比較,可直接餵入CLI之--version輸出(如'agy 1.1.27 (build 3)'),預發布標籤(如-beta.1)不處理

    Unit Test: Github

Source:
Example
console.log(strCompareForVersion('1.1.27', '1.1.11'))
// => 1

console.log(strCompareForVersion('1.1.11', '1.1.11'))
// => 0

console.log(strCompareForVersion('1.0.0', '1.1.11'))
// => -1

console.log(strCompareForVersion('v1.2.10', '1.2.9'))
// => 1

console.log(strCompareForVersion('agy 1.1.27 (build 3)', '1.1.11'))
// => 1

console.log(strCompareForVersion('abc', '1.0.0'))
// => null

console.log(strCompareForVersion('1.2', '1.2.0'))
// => null
Parameters:
Name Type Description
v1 String

輸入版本號字串

v2 String

輸入版本號字串

Returns:

回傳比較結果,v1大於v2回傳1,相等回傳0,小於回傳-1,任一方無法解析出三段版本號回傳null

Type
Number | null

(static) strDiff(strOld, strNew, optopt) → {Object}

Description:
  • 比對新舊文字差異

    Unit Test: Github

Source:
Example
let r

r = strDiff('test中文', '')
console.log(r)
// => {
//   diff: [ { count: 1, added: false, removed: true, value: 'test中文' } ],
//   dfs: [ { p: 'remove', vo: 'test中文', vn: '' } ]
// }

r = strDiff('test中文1\ntest中文2', '')
console.log(r)
// => {
//   diff: [
//     {
//       count: 2,
//       added: false,
//       removed: true,
//       value: 'test中文1\ntest中文2'
//     }
//   ],
//   dfs: [
//     { p: 'remove', vo: 'test中文1', vn: '' },
//     { p: 'remove', vo: 'test中文2', vn: '' }
//   ]
// }

r = strDiff('', 'test中文')
console.log(r)
// => {
//   diff: [ { count: 1, added: true, removed: false, value: 'test中文' } ],
//   dfs: [ { p: 'add', vo: 'test中文', vn: '' } ]
// }

r = strDiff('', 'test中文1\ntest中文2')
console.log(r)
// => {
//   diff: [
//     {
//       count: 2,
//       added: true,
//       removed: false,
//       value: 'test中文1\ntest中文2'
//     }
//   ],
//   dfs: [
//     { p: 'add', vo: 'test中文1', vn: '' },
//     { p: 'add', vo: 'test中文2', vn: '' }
//   ]
// }

let tab1 = `1|0.974848293|0.791303871|0.716898185|0.506002098|0.137888903|0.626724085
2|-0.529408622|0.839882385|0.663059856|0.49047221|0.395763265|0.866151835
3|-0.10320217|0.475514539|0.969205779|0.711250309|0.153847069|0.410092395
4|-0.121479865|0.486179086|0.481023842|0.467410582|0.42602231|0.849701641
5|0.757346003|0.697242433|0.67532802|0.174644416|0.045652267|0.397104668
6|0.663032731|0.259252779|0.566177431|0.679637706|0.377814487|0.400248119
7|0.72721374|0.263793391|0.167895215|0.794808602|0.107070584|0.011822872
8|0.247416664|0.360426795|0.014346373|0.000469616|0.4082693|0.913806611
9|0.345880037|0.167996664|0.711054429|0.363177921|0.206849994|0.636855344
10|0.576739457|0.324665077|0.973218005|0.883927423|0.176906125|0.20087887
11|a1
12|a2
13|0.504421248|0.984003751|0.32549507|0.987090751|0.192745589|0.735133561
14|0.273214614|0.083431884|0.565146092|0.935388666|0.637675154|0.523815661
`

let tab2 = `1|0.974848293|0.791303871|0.716898185|0.506002098|0.137888903|0.626724085
2|-0.529408622|a0.839882385|0.663059856|0.49047221|0.395763265|0.866151835
3|-0.10320217|0.475514539|0.969205779|0.711250309|0.153847069|0.410092395
4|-0.121479865|0.486179086|0.481023842|0.467410582|0.42602231|0.849701641
5|0.757346003|0.697242433|0.67532802|0.174644416|0.045652267|0.397104668
7|0.72721374|0.263793391|0.167895215|0.794808602|0.107070584|0.011822872
8|0.247416664|0.360426795|0.014346373|0.000469616|0.4082693|0.913806611
9|0.345880037|0.167996664|0.711054429|0.363173478|0.636855344
10|0.576739457|0.324665077|0.973218005|0.883927423|0.176906125|0.20087887
13|0.504421248|0.984003751|0.32549507|0.987090751|0.192745589|0.735133561
14|0.273214614|0.083431884|0.565146092|0.935388666|0.637675154|0.523815661
n1|0.944492151|0.89950443|0.182709318|0.892820757|0.709746901|0.097385354
n2|0.769805921|0.061355308|0.314826137|0.855857651|0.653550539|0.772500773
n3|0.158739038|0.085078711|0.844664253|0.21630142|0.912931341|0.735138313
`

r = strDiff(tab1, tab2)
console.log(r)
// => {
//   diff: [
//     {
//       count: 1,
//       added: false,
//       removed: false,
//       value: '1|0.974848293|0.791303871|0.716898185|0.506002098|0.137888903|0.626724085\n'
//     },
//     {
//       count: 1,
//       added: false,
//       removed: true,
//       value: '2|-0.529408622|0.839882385|0.663059856|0.49047221|0.395763265|0.866151835\n'
//     },
//     {
//       count: 1,
//       added: true,
//       removed: false,
//       value: '2|-0.529408622|a0.839882385|0.663059856|0.49047221|0.395763265|0.866151835\n'
//     },
//     {
//       count: 3,
//       added: false,
//       removed: false,
//       value: '3|-0.10320217|0.475514539|0.969205779|0.711250309|0.153847069|0.410092395\n' +
//         '4|-0.121479865|0.486179086|0.481023842|0.467410582|0.42602231|0.849701641\n' +
//         '5|0.757346003|0.697242433|0.67532802|0.174644416|0.045652267|0.397104668\n'
//     },
//     {
//       count: 1,
//       added: false,
//       removed: true,
//       value: '6|0.663032731|0.259252779|0.566177431|0.679637706|0.377814487|0.400248119\n'
//     },
//     {
//       count: 2,
//       added: false,
//       removed: false,
//       value: '7|0.72721374|0.263793391|0.167895215|0.794808602|0.107070584|0.011822872\n' +
//         '8|0.247416664|0.360426795|0.014346373|0.000469616|0.4082693|0.913806611\n'
//     },
//     {
//       count: 1,
//       added: false,
//       removed: true,
//       value: '9|0.345880037|0.167996664|0.711054429|0.363177921|0.206849994|0.636855344\n'
//     },
//     {
//       count: 1,
//       added: true,
//       removed: false,
//       value: '9|0.345880037|0.167996664|0.711054429|0.363173478|0.636855344\n'
//     },
//     {
//       count: 1,
//       added: false,
//       removed: false,
//       value: '10|0.576739457|0.324665077|0.973218005|0.883927423|0.176906125|0.20087887\n'
//     },
//     { count: 2, added: false, removed: true, value: '11|a1\n12|a2\n' },
//     {
//       count: 2,
//       added: false,
//       removed: false,
//       value: '13|0.504421248|0.984003751|0.32549507|0.987090751|0.192745589|0.735133561\n' +
//         '14|0.273214614|0.083431884|0.565146092|0.935388666|0.637675154|0.523815661\n'
//     },
//     {
//       count: 3,
//       added: true,
//       removed: false,
//       value: 'n1|0.944492151|0.89950443|0.182709318|0.892820757|0.709746901|0.097385354\n' +
//         'n2|0.769805921|0.061355308|0.314826137|0.855857651|0.653550539|0.772500773\n' +
//         'n3|0.158739038|0.085078711|0.844664253|0.21630142|0.912931341|0.735138313\n'
//     }
//   ],
//   dfs: [
//     {
//       p: '',
//       vo: '1|0.974848293|0.791303871|0.716898185|0.506002098|0.137888903|0.626724085',
//       vn: ''
//     },
//     {
//       p: 'modify',
//       vo: '2|-0.529408622|0.839882385|0.663059856|0.49047221|0.395763265|0.866151835',
//       vn: '2|-0.529408622|a0.839882385|0.663059856|0.49047221|0.395763265|0.866151835'
//     },
//     {
//       p: '',
//       vo: '3|-0.10320217|0.475514539|0.969205779|0.711250309|0.153847069|0.410092395',
//       vn: ''
//     },
//     {
//       p: '',
//       vo: '4|-0.121479865|0.486179086|0.481023842|0.467410582|0.42602231|0.849701641',
//       vn: ''
//     },
//     {
//       p: '',
//       vo: '5|0.757346003|0.697242433|0.67532802|0.174644416|0.045652267|0.397104668',
//       vn: ''
//     },
//     {
//       p: 'remove',
//       vo: '6|0.663032731|0.259252779|0.566177431|0.679637706|0.377814487|0.400248119',
//       vn: ''
//     },
//     {
//       p: '',
//       vo: '7|0.72721374|0.263793391|0.167895215|0.794808602|0.107070584|0.011822872',
//       vn: ''
//     },
//     {
//       p: '',
//       vo: '8|0.247416664|0.360426795|0.014346373|0.000469616|0.4082693|0.913806611',
//       vn: ''
//     },
//     {
//       p: 'modify',
//       vo: '9|0.345880037|0.167996664|0.711054429|0.363177921|0.206849994|0.636855344',
//       vn: '9|0.345880037|0.167996664|0.711054429|0.363173478|0.636855344'
//     },
//     {
//       p: '',
//       vo: '10|0.576739457|0.324665077|0.973218005|0.883927423|0.176906125|0.20087887',
//       vn: ''
//     },
//     { p: 'remove', vo: '11|a1', vn: '' },
//     { p: 'remove', vo: '12|a2', vn: '' },
//     {
//       p: '',
//       vo: '13|0.504421248|0.984003751|0.32549507|0.987090751|0.192745589|0.735133561',
//       vn: ''
//     },
//     {
//       p: '',
//       vo: '14|0.273214614|0.083431884|0.565146092|0.935388666|0.637675154|0.523815661',
//       vn: ''
//     },
//     {
//       p: 'add',
//       vo: 'n1|0.944492151|0.89950443|0.182709318|0.892820757|0.709746901|0.097385354',
//       vn: ''
//     },
//     {
//       p: 'add',
//       vo: 'n2|0.769805921|0.061355308|0.314826137|0.855857651|0.653550539|0.772500773',
//       vn: ''
//     },
//     {
//       p: 'add',
//       vo: 'n3|0.158739038|0.085078711|0.844664253|0.21630142|0.912931341|0.735138313',
//       vn: ''
//     }
//   ]
// }
Parameters:
Name Type Attributes Default Description
strOld String

輸入原始文字字串

strNew String

輸入更新文字字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useNormNewline Boolean <optional>
true

輸入是否正規化換行符號布林值,預設true

Returns:

回傳比對結果物件,包含diff與dfs鍵值,diff為比對原始結果字串,dfs為依照各列比對結果陣列

Type
Object

(static) strDiffToHtml(strOld, strNew, optopt) → {Object}

Description:
  • 比對新舊文字差異,並轉成html呈現

    Unit Test: Github

Source:
Example
let r
let sOld
let sNew

r = strDiffToHtml('test中文', 'test')
console.log(r)
// => <div class="d2h-code-line">
//     <span class="d2h-code-line-prefix" style="display:inline-block; width: 0.7rem;">-</span>
//     <span class="d2h-code-line-ctn">test<del style="color:#f26; background:#ffefd5;">中文</del></span>
// </div>
// <div class="d2h-code-line">
//     <span class="d2h-code-line-prefix" style="display:inline-block; width: 0.7rem;">+</span>
//     <span class="d2h-code-line-ctn">test</span>
// </div>

r = strDiffToHtml('test中文', 'tet中英文')
console.log(r)
// => <div class="d2h-code-line">
//     <span class="d2h-code-line-prefix" style="display:inline-block; width: 0.7rem;">-</span>
//     <span class="d2h-code-line-ctn">te<del style="color:#f26; background:#ffefd5;">s</del>t中文</span>
// </div>
// <div class="d2h-code-line">
//     <span class="d2h-code-line-prefix" style="display:inline-block; width: 0.7rem;">+</span>
//     <span class="d2h-code-line-ctn">tet中<ins style="color:#090; background:#e4fed1;">英</ins>文</span>
// </div>

r = strDiffToHtml('test中文', '')
console.log(r)
// => <div class="d2h-code-line">
//     <span class="d2h-code-line-prefix" style="display:inline-block; width: 0.7rem;">-</span>
//     <span class="d2h-code-line-ctn">test中文</span>
// </div>

r = strDiffToHtml('', 'test中文')
console.log(r)
// => <div class="d2h-code-line">
//     <span class="d2h-code-line-prefix" style="display:inline-block; width: 0.7rem;">+</span>
//     <span class="d2h-code-line-ctn">test中文</span>
// </div>

sOld = `This is a long text used for testing.
It contains multiple sentences that will be modified.`
sNew = `This is a long text used for testing.
This section has been replaced completely.`
r = strDiffToHtml(sOld, sNew)
console.log(r)
// => <div class="d2h-code-line">
//     <span class="d2h-code-line-prefix" style="display:inline-block; width: 0.7rem;">&nbsp;</span>
//     <span class="d2h-code-line-ctn">This is a long text used for testing.</span>
// </div>
// <div class="d2h-code-line">
//     <span class="d2h-code-line-prefix" style="display:inline-block; width: 0.7rem;">-</span>
//     <span class="d2h-code-line-ctn"><del style="color:#f26; background:#ffefd5;">It</del> c<del style="color:#f26; background:#ffefd5;">on</del>t<del style="color:#f26; background:#ffefd5;">a</del>ins <del style="color:#f26; background:#ffefd5;">multipl</del>e<del style="color:#f26; background:#ffefd5;"> s</del>en<del style="color:#f26; background:#ffefd5;">t</del>e<del style="color:#f26; background:#ffefd5;">n</del>ce<del style="color:#f26; background:#ffefd5;">s</del> <del style="color:#f26; background:#ffefd5;">that wi</del>l<del style="color:#f26; background:#ffefd5;">l b</del>e<del style="color:#f26; background:#ffefd5;"> modifi</del>e<del style="color:#f26; background:#ffefd5;">d</del>.</span>
// </div>
// <div class="d2h-code-line">
//     <span class="d2h-code-line-prefix" style="display:inline-block; width: 0.7rem;">+</span>
//     <span class="d2h-code-line-ctn"><ins style="color:#090; background:#e4fed1;">This</ins> <ins style="color:#090; background:#e4fed1;">se</ins>cti<ins style="color:#090; background:#e4fed1;">o</ins>n<ins style="color:#090; background:#e4fed1;"> ha</ins>s <ins style="color:#090; background:#e4fed1;">b</ins>een<ins style="color:#090; background:#e4fed1;"> r</ins>e<ins style="color:#090; background:#e4fed1;">pla</ins>ce<ins style="color:#090; background:#e4fed1;">d</ins> <ins style="color:#090; background:#e4fed1;">comp</ins>le<ins style="color:#090; background:#e4fed1;">t</ins>e<ins style="color:#090; background:#e4fed1;">ly</ins>.</span>
// </div>
Parameters:
Name Type Attributes Default Description
strOld String

輸入原始文字字串

strNew String

輸入更新文字字串

opt String <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
useNormNewline Boolean <optional>
true

輸入是否正規化換行符號布林值,預設true

Returns:

回傳比對結果html

Type
Object

(static) strFindFuzz(arr, strkey, bscoreopt) → {Boolean|Number}

Description:
  • 針對字串陣列模糊比對,以空白分切strkey做為關鍵字,查詢字串陣列arr內各字串是否含有相似關鍵字,或與關鍵字之相似度

    Unit Test: Github

Source:
Example
console.log(strFindFuzz('Wodooman(樵夫)', 'Wodooman(樵夫)', true))
// => 100

//第2參數會被空白切分成多關鍵字
console.log(strFindFuzz('Wodooman(樵夫)', 'The Woodman(樵夫) set to work at once, and so...', true))
// => 31.831649831649834

console.log(strFindFuzz('The Woodman(樵夫) set to work at once, and so...', 'Wodooman(樵夫)', true))
// => 40.845872267054474

console.log(strFindFuzz(['abc', 'def123', '中文測試'], 'ef', true))
// => 100

console.log(strFindFuzz(['abc', 'def123', '中文測試'], 'efgg', true))
// => 46

console.log(strFindFuzz(['abc', 'def123', '中文測試'], 'ef'))
// => true
Parameters:
Name Type Attributes Default Description
arr Array | String

輸入資料,若輸入陣列則自動join成字串

strkey String | Number

查找ar內是否含有關鍵字,多關鍵字係以空白區分

bscore Boolean <optional>
false

是否回傳分數,當設定為true時回傳值為百分比數值,設定為false時回傳值為布林值(預設)

Returns:

輸出資料,回傳值為分數或是否

Type
Boolean | Number

(static) strFindSimilar(ar, strkeys) → {Object}

Description:
  • 計算字串ar與多關鍵字陣列strkeys之相似程度

    Unit Test: Github

Source:
Example
console.log(strFindSimilar('The Woodman(樵夫) set to work at once, and so...', ['Wodooman(樵夫)', 'manWood(樵夫)', 'Wood樵man(夫)', 'Woodman(樵夫)']))
// => { 'ratings': [{ 'target': 'Wodooman(樵夫)', 'rating': 0.375 }, { 'target': 'manWood(樵夫)', 'rating': 0.3404255319148936 }, { 'target': 'Wood樵man(夫)', 'rating': 0.2978723404255319 }, { 'target': 'Woodman(樵夫)', 'rating': 0.425531914893617 }], 'bestMatch': { 'target': 'Woodman(樵夫)', 'rating': 0.425531914893617 }, 'bestMatchIndex': 3 }
Parameters:
Name Type Description
ar Array | String

輸入資料,若輸入陣列則自動join成字串

strkeys String | Number | Array

查找ar內與多關鍵字strkeys的個別相似度

Returns:

輸出比對資料物件,回傳與各關鍵字之相似度以及最相關者

Type
Object

(static) strTruncate(str, maxLen, optopt) → {String}

Description:
  • 字串超長時裁切至指定長度並補上刪節號,可另指定函數生成附註訊息

    未超長則原樣回傳,不補刪節號 輸入非有效字串回傳空字串,maxLen非有效非負整數則原樣回傳字串

    Unit Test: Github

Source:
Example
console.log(strTruncate('abcdefghij', 3))
// => abc...

console.log(strTruncate('abcdefghij', 20))
// => abcdefghij

console.log(strTruncate('abcdefghij', 3, { funWithMsg: (str) => `(truncated, total ${str.length} chars)` }))
// => abc...(truncated, total 10 chars)

console.log(strTruncate('abcdefghij', 3, { funWithMsg: (str, maxLen) => `(剩餘${str.length - maxLen}字)` }))
// => abc...(剩餘7字)

console.log(strTruncate('', 3))
// =>

console.log(strTruncate('abcdefghij', -1))
// => abcdefghij
Parameters:
Name Type Attributes Default Description
str String

輸入待裁切字串

maxLen Integer

輸入裁切後最大保留長度非負整數,不含刪節號與附註訊息

opt Object <optional>
{}

輸入設定物件

Properties
Name Type Attributes Default Description
funWithMsg function <optional>
null

輸入生成附註訊息之函數,格式為(str, maxLen)=>String,其中str為原始未裁切字串,回傳字串將接於刪節號之後,回傳非有效字串視為無附註,預設null代表僅補刪節號

Returns:

回傳裁切後字串

Type
String

(static) strdelleft(c, n) → {String}

Description:
  • 刪除字串左邊n個字元

    Unit Test: Github

Source:
Example
console.log(strdelleft('test中文', 2))
// => 'st中文'
Parameters:
Name Type Description
c String

輸入要被刪除的字串

n Integer

輸入刪除左邊n個字元正整數

Returns:

回傳處理後字串

Type
String

(static) strdelright(c, n) → {String}

Description:
  • 刪除字串右邊n個字元

    Unit Test: Github

Source:
Example
console.log(strdelright('test中文', 2))
// => 'test'
Parameters:
Name Type Description
c String

輸入要被刪除的字串

n Integer

輸入刪除右邊n個字元

Returns:

回傳處理後字串

Type
String

(static) strleft(c, n) → {String}

Description:
  • 取字串左邊n個字元

    Unit Test: Github

Source:
Example
console.log(strleft('test中文', 2))
// => 'te'
Parameters:
Name Type Description
c String

輸入要被取的字串

n Integer

輸入取左邊n個字元

Returns:

回傳處理後字串

Type
String

(static) strmid(c, s, nopt) → {String}

Description:
  • 取字串中位置s開始後n個字元

    Unit Test: Github

Source:
Example
console.log(strmid('test中文', 2, 1))
// => 's'

console.log(strmid('test中文', 2, 3))
// => 'st中'
Parameters:
Name Type Attributes Default Description
c String

輸入要被取的字串

s Integer

輸入要由第s個位置開始,為正整數

n Integer <optional>
1

輸入開始後n個字元,為正整數,預設1

Returns:

回傳處理後字串

Type
String

(static) strright(c, n) → {String}

Description:
  • 取字串右邊n個字元

    Unit Test: Github

Source:
Example
console.log(strright('test中文', 2))
// => '中文'
Parameters:
Name Type Description
c String

輸入要被取的字串

n Integer

輸入取右邊n個字元

Returns:

回傳處理後字串

Type
String

(static) stru8arr2obj(optopt) → {*}

Description:
  • 由字串與Unit8Array陣列轉物件,為對obj2stru8arr序列化之數據進行反序列化

    Unit Test: Github

Source:
Example
let r = {
    results: '{"a":123,"b":45.67,"c":"l1-測試中文","d":{"da":123,"db":45.67,"dc":"l2-測試中文","dd":["a","xyz",321,76.54],"de":"[BlazeForUint8Array]::0"}}',
    binarys: [new Uint8Array([66, 97, 115])]
}
let data = stru8arr2obj(r)
console.log(data)
// => {
//     a: 123,
//     b: 45.67,
//     c: 'l1-測試中文',
//     d: {
//         da: 123,
//         db: 45.67,
//         dc: 'l2-測試中文',
//         dd: [ 'a', 'xyz', 321, 76.54 ],
//         de: Uint8Array [ 66, 97, 115 ]
//     }
// }

console.log(stru8arr2obj({ results: 'not-json', binarys: [] }, { returnWithStateAndMsg: true }))
// => {
//     state: 'error',
//     msg: 'SyntaxError: Unexpected token \'o\', "not-json" is not valid JSON'
// }
Parameters:
Name Type Attributes Default Description
data.results String

輸入待反序列化字串,其內二進位數據之標記為'[BlazeForUint8Array]::'、'[BlazeForUint16Array]::'、'[BlazeForArrayBuffer]::',須完整匹配整個字串值才視為二進位參照,故應用字串內夾雜標記文字者不受影響;恰與標記格式完整相同之應用字串由編碼端前置'[BlazeForPreventEscape]'跳脫,此處會剝除一層還原;標記格式正確但索引越界者視為壞封包。注意不支援還原舊版之'[Uint8Array]::'、'[Uint16Array]::'、'[ArrayBuffer]::'標記

data.binarys Array

輸入Unit8Array陣列

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳任意物件,輸入無效或反序列化失敗(如results非合法JSON、同一二進位索引被引用多次)時回傳空物件;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
*

(static) tand(deg) → {Number}

Description:
  • 輸入角度計算tan值

    Unit Test: Github

Source:
Example
console.log(tand(0))
// => 1

console.log(tand(30))
// => 0.8660254037844387

console.log(tand(45))
// => 0.7071067811865476

console.log(tand(60))
// => 0.5000000000000001

console.log(tand(90))
// => 6.123233995736766e-17

console.log(tand(120))
// => -0.4999999999999998

console.log(tand(135))
// => -0.7071067811865475

console.log(tand(150))
// => -0.8660254037844387

console.log(tand(180))
// => -1

console.log(tand(210))
// => -0.8660254037844386

console.log(tand(225))
// => -0.7071067811865477

console.log(tand(240))
// => -0.5000000000000004

console.log(tand(270))
// => -1.8369701987210297e-16

console.log(tand(300))
// => 0.5000000000000001

console.log(tand(315))
// => 0.7071067811865474

console.log(tand(330))
// => 0.8660254037844384

console.log(tand(360))
// => 1

console.log(tand(450))
// => 3.061616997868383e-16

console.log(tand(720))
// => 1
Parameters:
Name Type Description
deg Number | String

輸入角度數字或字串

Returns:

回傳tan值

Type
Number

(static) time2day(t, optopt) → {String|Object}

Description:
  • 秒時間取至日時間

    Unit Test: Github

Source:
Example
console.log(time2day('2019-01-01T12:34:56'))
// => '2019-01-01'
Parameters:
Name Type Attributes Default Description
t String

輸入秒時間字串,不含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳日時間字串,t非有效秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) time2hour(t, optopt) → {String|Object}

Description:
  • 秒時間取至時時間

    Unit Test: Github

Source:
Example
console.log(time2hour('2019-01-01T12:34:56'))
// => '2019-01-01T12'
Parameters:
Name Type Attributes Default Description
t String

輸入秒時間字串,不含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳時時間字串,t非有效秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) time2min(t, optopt) → {String|Object}

Description:
  • 秒時間取至分時間

    Unit Test: Github

Source:
Example
console.log(time2min('2019-01-01T12:34:56'))
// => '2019-01-01T12:34'
Parameters:
Name Type Attributes Default Description
t String

輸入秒時間字串,不含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳分時間字串,t非有效秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timeTZ2day(t, optopt) → {String|Object}

Description:
  • 秒時間取至日時間

    Unit Test: Github

Source:
Example
console.log(timeTZ2day('2019-01-01T12:34:56+08:00'))
// => '2019-01-01'
Parameters:
Name Type Attributes Default Description
t String

輸入秒時間字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳日時間字串,t非有效秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timeTZ2expire(t, tNowopt, optopt) → {Object}

Description:
  • 秒時間轉到期時間

    Unit Test: Github

Source:
Example
let t
let tNow = '2020-10-18T12:34:56+08:00'
let r

t = '2020-10-18T12:34:58+08:00'
r = timeTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '2秒後', err: '' }

t = '2020-10-18T04:34:58+00:00'
r = timeTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '2秒後', err: '' }

t = '2020-10-18T04:34:58Z'
r = timeTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '2秒後', err: '' }

t = '2020-10-18T12:37:58+08:00'
r = timeTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '3分鐘後', err: '' }

t = '2020-10-18T16:37:58+08:00'
r = timeTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '4小時後,今天16:37', err: '' }

t = '2020-10-23T16:37:58+08:00'
r = timeTZ2expire(t, tNow)
console.log(r)
// => { today: false, msg: '5天後', err: '' }

t = '2021-04-23T16:37:58+08:00'
r = timeTZ2expire(t, tNow)
console.log(r)
// => { today: false, msg: '6個月後', err: '' }

t = '2028-04-23T16:37:58+08:00'
r = timeTZ2expire(t, tNow)
console.log(r)
// => { today: false, msg: '7年後', err: '' }

t = '2018-04-23T16:37:58+08:00'
r = timeTZ2expire(t, tNow)
console.log(r)
// => { today: null, msg: '', err: '時間已過' }
Parameters:
Name Type Attributes Default Description
t String

輸入秒時間字串

tNow String <optional>
null

輸入現在秒時間字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳物件,含today、msg、err三欄位;t非有效秒時間字串時回傳{ today: null, msg: '', err: '時間格式錯誤' },時間已過時回傳{ today: null, msg: '', err: '時間已過' };若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件,其中時間已過屬算得之結果故state仍為'success'

Type
Object

(static) timeTZ2hour(t, optopt) → {String|Object}

Description:
  • 秒時間取至時時間

    Unit Test: Github

Source:
Example
console.log(timeTZ2hour('2019-01-01T12:34:56+08:00'))
// => '2019-01-01T12'
Parameters:
Name Type Attributes Default Description
t String

輸入秒時間字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳時時間字串,t非有效秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timeTZ2min(t, optopt) → {String|Object}

Description:
  • 秒時間取至分時間

    Unit Test: Github

Source:
Example
console.log(timeTZ2min('2019-01-01T12:34:56+08:00'))
// => '2019-01-01T12:34'
Parameters:
Name Type Attributes Default Description
t String

輸入秒時間字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳分時間字串,t非有效秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timeTZ2past(t, tNowopt, optopt) → {Object}

Description:
  • 秒時間轉過去時間

    Unit Test: Github

Source:
Example
let t
let tNow = '2020-10-18T12:34:56+08:00'
let r

t = '2020-10-18T12:34:54+08:00'
r = timeTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '2秒前', err: '' }

t = '2020-10-18T04:34:54+00:00'
r = timeTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '2秒前', err: '' }

t = '2020-10-18T04:34:54Z'
r = timeTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '2秒前', err: '' }

t = '2020-10-18T12:31:54+08:00'
r = timeTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '3分鐘前', err: '' }

t = '2020-10-18T08:31:54+08:00'
r = timeTZ2past(t, tNow)
console.log(r)
// => { today: true, msg: '4小時前', err: '' }

t = '2020-10-13T08:31:54+08:00'
r = timeTZ2past(t, tNow)
console.log(r)
// => { today: false, msg: '5天前', err: '' }

t = '2020-04-13T08:31:54+08:00'
r = timeTZ2past(t, tNow)
console.log(r)
// => { today: false, msg: '6個月前', err: '' }

t = '2013-04-13T08:31:54+08:00'
r = timeTZ2past(t, tNow)
console.log(r)
// => { today: false, msg: '7年前', err: '' }

t = '2023-04-13T08:31:54+08:00'
r = timeTZ2past(t, tNow)
console.log(r)
// => { today: null, msg: '', err: '時間未到' }
Parameters:
Name Type Attributes Default Description
t String

輸入秒時間字串

tNow String <optional>
null

輸入現在秒時間字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳物件,含today、msg、err三欄位;t非有效秒時間字串時回傳{ today: null, msg: '', err: '時間格式錯誤' },時間未到時回傳{ today: null, msg: '', err: '時間未到' };若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件,其中時間未到屬算得之結果故state仍為'success'

Type
Object

(static) timems2day(t, optopt) → {String|Object}

Description:
  • 毫秒時間取至日時間

    Unit Test: Github

Source:
Example
console.log(timems2day('2019-01-01T12:34:56.987'))
// => '2019-01-01'
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串,不含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳日時間字串,t非有效毫秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timems2hour(t, optopt) → {String|Object}

Description:
  • 毫秒時間取至時時間

    Unit Test: Github

Source:
Example
console.log(timems2hour('2019-01-01T12:34:56.987'))
// => '2019-01-01T12'
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串,不含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳時時間字串,t非有效毫秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timems2min(t, optopt) → {String|Object}

Description:
  • 毫秒時間取至分時間

    Unit Test: Github

Source:
Example
console.log(timems2min('2019-01-01T12:34:56.987'))
// => '2019-01-01T12:34'
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串,不含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳分時間字串,t非有效毫秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timems2time(t, optopt) → {String|Object}

Description:
  • 毫秒時間取至秒時間

    Unit Test: Github

Source:
Example
console.log(timems2time('2019-01-01T12:34:56.987'))
// => '2019-01-01T12:34:56'
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串,不含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳秒時間字串,t非有效毫秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timemsTZ2day(t, optopt) → {String|Object}

Description:
  • 毫秒時間(含時區)取至日時間

    Unit Test: Github

Source:
Example
console.log(timemsTZ2day('2019-01-01T12:34:56.987+08:00'))
// => '2019-01-01'

console.log(timemsTZ2day('2019-01-01T12:34:56.987Z'))
// => '2019-01-01'
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串,含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳日時間字串,t非有效毫秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timemsTZ2expire(t, tNowopt, optopt) → {Object}

Description:
  • 毫秒時間轉到期時間

    Unit Test: Github

Source:
Example
let t
let tNow = '2020-10-18T12:34:56.987+08:00'
let r

t = '2020-10-18T12:34:58.987+08:00'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '2秒後', err: '' }

t = '2020-10-18T04:34:58.987+00:00'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '2秒後', err: '' }

t = '2020-10-18T04:34:58.987Z'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '2秒後', err: '' }

t = '2020-10-18T12:34:58.123+08:00'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '1秒後', err: '' }

t = '2020-10-18T12:37:58.987+08:00'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '3分鐘後', err: '' }

t = '2020-10-18T16:37:58.987+08:00'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: true, msg: '4小時後,今天16:37', err: '' }

t = '2020-10-23T16:37:58.987+08:00'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: false, msg: '5天後', err: '' }

t = '2021-04-23T16:37:58.987+08:00'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: false, msg: '6個月後', err: '' }

t = '2028-04-23T16:37:58.987+08:00'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: false, msg: '7年後', err: '' }

t = '2018-04-23T16:37:58.987+08:00'
r = timemsTZ2expire(t, tNow)
console.log(r)
// => { today: null, msg: '', err: '時間已過' }
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串

tNow String <optional>
null

輸入現在毫秒時間字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳物件,含today、msg、err三欄位;t非有效毫秒時間字串時回傳{ today: null, msg: '', err: '時間格式錯誤' },時間已過時回傳{ today: null, msg: '', err: '時間已過' };若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件,其中時間已過屬算得之結果故state仍為'success'

Type
Object

(static) timemsTZ2hour(t, optopt) → {String|Object}

Description:
  • 毫秒時間(含時區)取至時時間

    Unit Test: Github

Source:
Example
console.log(timemsTZ2hour('2019-01-01T12:34:56.987+08:00'))
// => '2019-01-01T12'

console.log(timemsTZ2hour('2019-01-01T12:34:56.987Z'))
// => '2019-01-02T12'
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串,含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳時時間字串,t非有效毫秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timemsTZ2min(t, optopt) → {String|Object}

Description:
  • 毫秒時間(含時區)取至分時間

    Unit Test: Github

Source:
Example
console.log(timemsTZ2min('2019-01-01T12:34:56.987+08:00'))
// => '2019-01-01T12:34'

console.log(timemsTZ2min('2019-01-01T12:34:56.987Z'))
// => '2019-01-01T12:34'
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串,含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳分時間字串,t非有效毫秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) timemsTZ2time(t, optopt) → {String|Object}

Description:
  • 毫秒時間(含時區)取至秒時間

    Unit Test: Github

Source:
Example
console.log(timemsTZ2time('2019-01-01T12:34:56.987+08:00'))
// => '2019-01-01T12:34:56'

console.log(timemsTZ2time('2019-01-01T12:34:56.987Z'))
// => '2019-01-01T12:34:56'
Parameters:
Name Type Attributes Default Description
t String

輸入毫秒時間字串,含時區

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳秒時間字串,t非有效毫秒時間字串或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) treeObj(data, hookFunopt, optopt) → {*}

Description:
  • 遍歷物件並回傳複製物件,類似JSON.stringify,但不會序列化成為字串。真陣列(含帶其他Symbol.toStringTag者)依索引遍歷,物件依自有鍵遍歷(length視為一般鍵),以Symbol.toStringTag偽稱Array之物件視為物件

    Unit Test: Github

Source:
Example
let r
let data = {
    a: 123,
    b: 145.67,
    c: 'test中文1',
    d: true,
    e: function() {},
    f: [11, 'xyz', false, new Uint8Array([166, 197, 215])],
    g: {
        ga: 223,
        gb: 245.67,
        gc: 'test中文2',
        gd: new Uint8Array([66, 97, 115]),
    },
    h: Symbol('foo'),
    [Symbol('i-sym-key')]: 'i-sym-value',
}

r = treeObj(data, (value, key, nk) => {
    console.log('=>', value, key, nk)
    return value
})
console.log('force: false', r)
// => 123 a []
// => 145.67 b []
// => test中文1 c []
// => true d []
// => [Function: e] e []
// => [ 11, 'xyz', false, Uint8Array(3) [ 166, 197, 215 ] ] f []
// => 11 0 [ 'f' ]
// => xyz 1 [ 'f' ]
// => false 2 [ 'f' ]
// => Uint8Array(3) [ 166, 197, 215 ] 3 [ 'f' ]
// => {
//   ga: 223,
//   gb: 245.67,
//   gc: 'test中文2',
//   gd: Uint8Array(3) [ 66, 97, 115 ]
// } g []
// => 223 ga [ 'g' ]
// => 245.67 gb [ 'g' ]
// => test中文2 gc [ 'g' ]
// => Uint8Array(3) [ 66, 97, 115 ] gd [ 'g' ]
// => Symbol(foo) h []
// force: false {
//   a: 123,
//   b: 145.67,
//   c: 'test中文1',
//   d: true,
//   e: [Function: e],
//   f: [ 11, 'xyz', false, Uint8Array(3) [ 166, 197, 215 ] ],
//   g: {
//     ga: 223,
//     gb: 245.67,
//     gc: 'test中文2',
//     gd: Uint8Array(3) [ 66, 97, 115 ]
//   },
//   h: Symbol(foo)
// }

r = treeObj(data, (value, key, nk) => {
    console.log('=>', value, key, nk)
    return value
}, { force: true })
console.log('force: true', r)
// => 123 a []
// => 145.67 b []
// => test中文1 c []
// => true d []
// => [Function: e] e []
// => [ 11, 'xyz', false, Uint8Array(3) [ 166, 197, 215 ] ] f []
// => 11 0 [ 'f' ]
// => xyz 1 [ 'f' ]
// => false 2 [ 'f' ]
// => Uint8Array(3) [ 166, 197, 215 ] 3 [ 'f' ]
// => {
//   ga: 223,
//   gb: 245.67,
//   gc: 'test中文2',
//   gd: Uint8Array(3) [ 66, 97, 115 ]
// } g []
// => 223 ga [ 'g' ]
// => 245.67 gb [ 'g' ]
// => test中文2 gc [ 'g' ]
// => Uint8Array(3) [ 66, 97, 115 ] gd [ 'g' ]
// => Symbol(foo) h []
// => i-sym-value Symbol(i-sym-key) []
// force: true {
//   a: 123,
//   b: 145.67,
//   c: 'test中文1',
//   d: true,
//   e: [Function: e],
//   f: [ 11, 'xyz', false, Uint8Array(3) [ 166, 197, 215 ] ],
//   g: {
//     ga: 223,
//     gb: 245.67,
//     gc: 'test中文2',
//     gd: Uint8Array(3) [ 66, 97, 115 ]
//   },
//   h: Symbol(foo),
//   [Symbol(i-sym-key)]: 'i-sym-value'
// }
Parameters:
Name Type Attributes Default Description
data *

輸入任意資料

hookFun function <optional>
null

輸入攔截處理函數,預設null

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
force Boolean <optional>
false

輸入是否允許鍵值為Symbol類型布林值,預設false

Returns:

回傳複製後的任意資料

Type
*

(static) treeToFilepath(treeItems, optopt, delimiteropt, bindRootopt) → {Array}

Description:
  • 轉換為樹狀物件成檔案路徑陣列

    Unit Test: Github

Source:
Example
let fps1 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa/mmm/z1.txt' }]
let r1 = filepathToTree(fps1)
console.log('r1.treeItems', r1.treeItems)
// => r1.treeItems [
//   {
//     ns: 1,
//     ts: [ 'root' ],
//     pathInfors: [ [Object] ],
//     _type: 'folder',
//     type: 'array',
//     numOfChilren: -1,
//     id: 'root',
//     parentId: '',
//     text: 'root',
//     children: [ [Object] ],
//     data: null
//   }
// ]

let rfps1 = treeToFilepath(r1.treeItems, {})
console.log('rfps1', rfps1)
// => rfps1 [
//   { type: 'folder', path: '/aaa' },
//   { type: 'folder', path: '/aaa/mmm' },
//   { type: 'file', path: '/aaa/mmm/z1.txt' }
// ]

let fps2 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa1.txt' }, { 'type': 'file', 'path': '/aaa2.txt' }, { 'type': 'folder', 'path': '/aaa/aaammm' }, { 'type': 'file', 'path': '/aaa/aaammm.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/aaammmccc.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcd/abcde.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdef1.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdef2.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdef3 aaa mmm ccc dddddd eeeeeee fffffffffff ggggggggggggg.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg01.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg02.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg03.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg04.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg05.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg06.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg07.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg08.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg09.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg10.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg11.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg12.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg13.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg14.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg15.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg16.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg17.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg18.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg19.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcde/abcdefg20.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcd1.txt' }, { 'type': 'file', 'path': '/aaa/aaammm/abc/abcd2.txt' }, { 'type': 'folder', 'path': '/mmm' }, { 'type': 'file', 'path': '/ccc/cccddd/cccdddeee.txt' }, { 'type': 'folder', 'path': '/eee' }, { 'type': 'folder', 'path': '/eee/eeefff1' }, { 'type': 'folder', 'path': '/eee/eeefff2' }, { 'type': 'folder', 'path': '/ggg/' }, { 'type': 'folder', 'path': 'c:\\\\hhh' }, { 'type': 'folder', 'path': '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff' }, { 'type': 'file', 'path': '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/gfedcba.txt' }, { 'type': 'file', 'path': '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/ggg/hhh.txt' }, { 'type': 'folder', 'path': 'd:\\\\中文路徑1' }, { 'type': 'folder', 'path': '/中文路徑2' }, { 'type': 'file', 'path': '/中文路徑2/aaa/aaammm/abc/測試.txt' }]
let r2 = filepathToTree(fps2, { delimiter: '>' })
console.log('r2.treeItems', r2.treeItems)
// => r2.treeItems [
//   {
//     ns: 1,
//     ts: [ 'root' ],
//     pathInfors: [ [Object] ],
//     _type: 'folder',
//     type: 'array',
//     numOfChilren: -1,
//     id: 'root',
//     parentId: '',
//     text: 'root',
//     children: [
//       [Object], [Object],
//       [Object], [Object],
//       [Object], [Object],
//       [Object], [Object],
//       [Object], [Object],
//       [Object]
//     ],
//     data: null
//   }
// ]

let rfps2 = treeToFilepath(r2.treeItems, {})
console.log('rfps2', rfps2)
// rfps2 [
//   { type: 'folder', path: '/aaa' },
//   { type: 'folder', path: '/aaa/aaammm' },
//   { type: 'folder', path: '/aaa/aaammm/abc' },
//   { type: 'folder', path: '/aaa/aaammm/abc/abcd' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcd/abcde.txt' },
//   { type: 'folder', path: '/aaa/aaammm/abc/abcde' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdef1.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdef2.txt' },
//   {
//     type: 'file',
//     path: '/aaa/aaammm/abc/abcde/abcdef3 aaa mmm ccc dddddd eeeeeee fffffffffff ggggggggggggg.txt'
//   },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg01.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg02.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg03.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg04.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg05.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg06.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg07.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg08.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg09.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg10.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg11.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg12.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg13.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg14.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg15.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg16.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg17.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg18.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg19.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcde/abcdefg20.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcd1.txt' },
//   { type: 'file', path: '/aaa/aaammm/abc/abcd2.txt' },
//   { type: 'file', path: '/aaa/aaammm/aaammmccc.txt' },
//   { type: 'file', path: '/aaa/aaammm.txt' },
//   { type: 'folder', path: '/aaaa' },
//   {
//     type: 'folder',
//     path: '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff'
//   },
//   {
//     type: 'folder',
//     path: '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/ggg'
//   },
//   {
//     type: 'file',
//     path: '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/ggg/hhh.txt'
//   },
//   {
//     type: 'file',
//     path: '/aaaa/mmmmmm cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/gfedcba.txt'
//   },
//   { type: 'folder', path: '/mmm' },
//   { type: 'folder', path: '/c:' },
//   { type: 'folder', path: '/c:/hhh' },
//   { type: 'folder', path: '/ccc' },
//   { type: 'folder', path: '/ccc/cccddd' },
//   { type: 'file', path: '/ccc/cccddd/cccdddeee.txt' },
//   { type: 'folder', path: '/d:' },
//   { type: 'folder', path: '/d:/中文路徑1' },
//   { type: 'folder', path: '/eee' },
//   { type: 'folder', path: '/eee/eeefff1' },
//   { type: 'folder', path: '/eee/eeefff2' },
//   { type: 'folder', path: '/ggg' },
//   { type: 'folder', path: '/中文路徑2' },
//   { type: 'folder', path: '/中文路徑2/aaa' },
//   { type: 'folder', path: '/中文路徑2/aaa/aaammm' },
//   { type: 'folder', path: '/中文路徑2/aaa/aaammm/abc' },
//   { type: 'file', path: '/中文路徑2/aaa/aaammm/abc/測試.txt' },
//   { type: 'file', path: '/aaa1.txt' },
//   { type: 'file', path: '/aaa2.txt' }
// ]

let fps3 = [{ 'type': 'folder', 'path': '/aaa1' }, { 'type': 'folder', 'path': '/aaa2' }, { 'type': 'folder', 'path': '/aaa10' }, { 'type': 'file', 'path': '/aaa/mmm/z1.txt' }, { 'type': 'file', 'path': '/aaa/mmm/z2.txt' }, { 'type': 'file', 'path': '/aaa/mmm/z10.txt' }]
let r3 = filepathToTree(fps3)
console.log('r3.treeItems', r3.treeItems)
// => r3.treeItems [
//   {
//     ns: 1,
//     ts: [ 'root' ],
//     pathInfors: [ [Object] ],
//     _type: 'folder',
//     type: 'array',
//     numOfChilren: -1,
//     id: 'root',
//     parentId: '',
//     text: 'root',
//     children: [ [Object], [Object], [Object], [Object] ],
//     data: null
//   }
// ]

let rfps3 = treeToFilepath(r3.treeItems, {})
console.log('rfps3', rfps3)
// => rfps3 [
//   { type: 'folder', path: '/aaa' },
//   { type: 'folder', path: '/aaa/mmm' },
//   { type: 'file', path: '/aaa/mmm/z1.txt' },
//   { type: 'file', path: '/aaa/mmm/z10.txt' },
//   { type: 'file', path: '/aaa/mmm/z2.txt' },
//   { type: 'folder', path: '/aaa1' },
//   { type: 'folder', path: '/aaa10' },
//   { type: 'folder', path: '/aaa2' }
// ]

let fps4 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa/mmm/z1.txt' }]
let r4 = filepathToTree(fps4)
console.log('r4.treeItems', r4.treeItems)
// => r1.treeItems [
//   {
//     ns: 1,
//     ts: [ 'root' ],
//     pathInfors: [ [Object] ],
//     _type: 'folder',
//     type: 'array',
//     numOfChilren: -1,
//     id: 'root',
//     parentId: '',
//     text: 'root',
//     children: [ [Object] ],
//     data: null
//   }
// ]

let rfps4 = treeToFilepath(r4.treeItems, { delimiter: '>' })
console.log('rfps4', rfps4)
// => rfps4 [
//   { type: 'folder', path: '>aaa' },
//   { type: 'folder', path: '>aaa>mmm' },
//   { type: 'file', path: '>aaa>mmm>z1.txt' }
// ]

let fps5 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa/mmm/z1.txt' }]
let r5 = filepathToTree(fps5, { bindRoot: 'ROOT' })
console.log('r5.treeItems', r5.treeItems)
// => r5.treeItems [
//   {
//     ns: 1,
//     ts: [ 'ROOT' ],
//     pathInfors: [ [Object] ],
//     _type: 'folder',
//     type: 'array',
//     numOfChilren: -1,
//     id: 'ROOT',
//     parentId: '',
//     text: 'ROOT',
//     children: [ [Object] ],
//     data: null
//   }
// ]

let rfps5 = treeToFilepath(r5.treeItems, { bindRoot: 'ROOT' })
console.log('rfps5', rfps5)
// => rfps5 [
//   { type: 'folder', path: '/aaa' },
//   { type: 'folder', path: '/aaa/mmm' },
//   { type: 'file', path: '/aaa/mmm/z1.txt' }
// ]
Parameters:
Name Type Attributes Default Description
treeItems Array

輸入樹狀物件陣列,每個須提供pathInfors、_type鍵值,其中pathInfors為各所屬(含自己)階層節點資訊,_type需為'folder'或'file',詳見filepathToTree轉出treeItems

opt Object <optional>
{}

輸入設定物件,預設{}

delimiter String <optional>
'/'

輸入路徑分隔字串,預設'/'

bindRoot String <optional>
'root'

輸入臨時封裝用根目錄字串,詳見filepathToTree內設定,預設'root'

Returns:

回傳檔案路徑陣列,每個項目為物件並提供path與type兩鍵值,其中type為'folder'或'file'

Type
Array

(static) trim(c, optopt) → {String}

Description:
  • 字串頭尾去除空白字串,若字串開頭含有BOM亦可清除,若輸入不是字串時則回傳空字串

    Unit Test: Github

Source:
Example
console.log(trim(' abc '))
// => 'abc'

console.log(trim(' 中 文 '))
// => '中 文'

console.log(trim(' abc 中 文 '))
// => 'abc 中 文'

console.log(trim(' abc123 '))
// => 'abc123'

console.log(trim(' abc123 ', { excludeString: true }))
// => '123'

console.log(trim(' 123abc ', { excludeString: true }))
// => '123'

console.log(trim(' a3b321c ', { excludeString: true }))
// => '3b321'
Parameters:
Name Type Attributes Default Description
c String

輸入字串

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
excludeString Boolean <optional>
false

輸入是否頭尾去除非數字字串布林值,預設false

Returns:

回傳去除空白的字串

Type
String

(static) u16arr2b64(u16a, optopt) → {String|Object}

Description:
  • Uint16Array轉base64字串

    以逐位元組(little-endian)方式編碼,故各元素之高位元組不會遺失;不可經u16arr2u8arr轉換,該函數為逐元素轉換會將大於255之元素截斷

    Unit Test: Github

Source:
Example
console.log(u16arr2b64(new Uint16Array([11, 79, 6])))
// => 'CwBPAAYA'

console.log(u16arr2b64('abc', { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid u16a' }
Parameters:
Name Type Attributes Default Description
u16a Uint16Array

輸入Uint16Array

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳base64字串,輸入非Uint16Array時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) u16arr2u8arr(u16a, optopt) → {Uint8Array|Object}

Description:
  • Uint16Array轉Uint8Array

    Unit Test: Github

Source:
Example
console.log(u16arr2u8arr(new Uint16Array([66, 97, 115])))
// => new Uint8Array([66, 97, 115])

console.log(u16arr2u8arr('abc', { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid u16a' }
Parameters:
Name Type Attributes Default Description
u16a Uint16Array

輸入Uint16Array

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Uint8Array,輸入非Uint16Array時回傳空Uint8Array;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Uint8Array | Object

(static) u8arr2ab(u8a, optopt) → {ArrayBuffer|Object}

Description:
  • Uint8Array轉ArrayBuffer

    Unit Test: Github

Source:
Example
console.log(u8arr2ab(new Uint8Array([66, 97, 115])))
// (new Uint8Array([66, 97, 115])).buffer

console.log(u8arr2ab('abc', { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid u8a' }
Parameters:
Name Type Attributes Default Description
u8a Uint8Array

輸入Uint8Array

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳ArrayBuffer,僅含該Uint8Array所視之範圍(依byteOffset與byteLength切出)而非其底層buffer全部,故輸入為大buffer之視圖(如nodejs之Buffer)時不會夾帶無關資料;輸入非Uint8Array時回傳空ArrayBuffer;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
ArrayBuffer | Object

(static) u8arr2b64(u8a, optopt) → {String|Object}

Description:
  • Uint8Array轉base64字串

    Unit Test: Github

Source:
Example
console.log(u8arr2b64(new Uint8Array([1, 2.3, '45', 'abc'])))
// => 'AQItAA=='

console.log(u8arr2b64('abc', { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid u8a' }
Parameters:
Name Type Attributes Default Description
u8a Uint8Array

輸入Uint8Array

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳base64字串,輸入非Uint8Array時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) u8arr2blob(u8a, optopt) → {Blob|Object}

Description:
  • Unit8Array資料轉Blob資料

    Unit Test: Github

Source:
Example
need test in browser

let u8a = new Uint8Array([1, 2.3, '45', 'abc'])
let bb = u8arr2blob(u8a)
console.log(bb)
// => Blob {size: 4, type: ""}

console.log(u8arr2blob('abc', { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid u8a' }
Parameters:
Name Type Attributes Default Description
u8a Unit8Array

輸入Unit8Array資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Blob資料,輸入非Unit8Array時回傳空Blob;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Blob | Object

(static) u8arr2bs(u8a, optopt) → {String|Object}

Description:
  • Unit8Array轉BinaryString,BinaryString為UTF-16編碼

    Unit Test: Github

Source:
Example
console.log(u8arr2bs(new Uint8Array([97, 98, 99])))
// => 'abc'

console.log(u8arr2bs('abc', { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid u8a' }
Parameters:
Name Type Attributes Default Description
u8a Unit8Array

輸入Unit8Array資料

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳BinaryString字串,輸入非Unit8Array時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) u8arr2obj(u8a, optopt) → {Object|Array}

Description:
  • Uint8Array轉物件或陣列資料

    Unit Test: Github

Source:
Example
let u8a = new Uint8Array([
    64, 24, 0, 0, 0, 0, 0, 0, 91, 54, 54, 44,
    51, 93, 123, 34, 97, 34, 58, 91, 49, 50, 51, 44,
    52, 53, 46, 54, 55, 44, 34, 116, 101, 115, 116, 228,
    184, 173, 230, 150, 135, 34, 93, 44, 34, 98, 34, 58,
    123, 34, 99, 34, 58, 34, 91, 66, 108, 97, 122, 101,
    70, 111, 114, 85, 105, 110, 116, 56, 65, 114, 114, 97,
    121, 93, 58, 58, 48, 34, 125, 125, 66, 97, 115
])
let data = u8arr2obj(u8a)
console.log(data)
// => { a: [ 123, 45.67, 'test中文' ], b: { c: Uint8Array [ 66, 97, 115 ] } }

console.log(u8arr2obj(new Uint8Array([1, 2, 3]), { returnWithStateAndMsg: true }))
// => {
//     state: 'error',
//     msg: 'invalid pks, head length must be a non-negative integer not exceeding the packet'
// }
Parameters:
Name Type Attributes Default Description
u8a Uint8Array

輸入Uint8Array

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳物件或陣列資料,其內二進位數據依標記所記之原型別還原,Uint8Array、Uint16Array、ArrayBuffer皆原型別原數值返回;輸入非Uint8Array或封包損毀時回傳空物件,封包標頭記載之分塊資訊區長度非非負整數或逾封包資料區長度(含封包短於8位元組)、分塊資訊非真陣列、含非負有限整數以外之項、總量逾封包長度、或分塊數量逾主區塊可容納之二進位標記數(每個分塊在主區塊內至少有一個25位元組之標記)亦視為損毀(msg為'invalid pks, <原因>');同一二進位索引被引用多次亦視為損毀;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Object | Array

(static) u8arr2str(u8a, optopt) → {String|Object}

Description:
  • Uint8Array轉字串

    以原生TextDecoder解碼UTF-8,保留開頭之BOM(U+FEFF),非合法UTF-8時視為轉換失敗(不靜默換成U+FFFD)

    Unit Test: Github

Source:
Example
console.log(u8arr2str(new Uint8Array([116, 101, 115, 116, 228, 184, 173, 230, 150, 135])))
// => test中文

console.log(u8arr2str('abc', { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid u8a' }
Parameters:
Name Type Attributes Default Description
u8a Uint8Array

輸入Uint8Array

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳一般字串,輸入非Uint8Array、非合法UTF-8或轉換失敗時回傳空字串;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
String | Object

(static) u8arr2u16arr(u8a, optopt) → {Uint16Array|Object}

Description:
  • Uint8Array轉Uint16Array

    Unit Test: Github

Source:
Example
console.log(u8arr2u16arr(new Uint8Array([66, 97, 115])))
// => new Uint16Array([66, 97, 115])

console.log(u8arr2u16arr('abc', { returnWithStateAndMsg: true }))
// => { state: 'error', msg: 'invalid u8a' }
Parameters:
Name Type Attributes Default Description
u8a Uint8Array

輸入Uint8Array

opt Object <optional>
{}

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
returnWithStateAndMsg Boolean <optional>
false

輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false

Returns:

回傳Uint16Array,輸入非Uint8Array時回傳空Uint16Array;若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件

Type
Uint16Array | Object

(static) urlParse(url) → {Object}

Description:
  • 切分網址取得參數物件

    Unit Test: Github

Source:
Example
console.log(urlParse('http://localhost:3000/index.html?a=12.34&b=xyz&abc=xyz123.456'))
// => { a: '12.34', abc: 'xyz123.456', b: 'xyz' }
Parameters:
Name Type Description
url String

輸入要切分的網址字串

Returns:

回傳資訊物件

Type
Object

(static) verifyValue(type, value) → {Object}

Description:
  • 驗證數值是否為指定類型

    Unit Test: Github

Source:
Example
console.log(verifyValue('12a5', 'isstr'))
// => r.err=false

console.log(verifyValue('12', 'isint'))
// => r.err=false

console.log(verifyValue('0', 'isp0int'))
// => r.err=false

console.log(verifyValue('12', 'isp0int'))
// => r.err=false

console.log(verifyValue('0', 'isn0int'))
// => r.err=false

console.log(verifyValue('-12', 'isn0int'))
// => r.err=false

console.log(verifyValue('12.34', 'isnum'))
// => r.err=false

console.log(verifyValue('12.34', 'isp0num'))
// => r.err=false

console.log(verifyValue('-12.34', 'isn0num'))
// => r.err=false
Parameters:
Name Type Description
type String | function

輸入判斷資料類型字串或判斷函式,資料類型可選為'isstr','isnum','isp0num','isn0num','isint','ispint','isp0int','isnint','isn0int','isfun'

value *

輸入任意資料

Returns:

回傳判斷結果,屬性value為回傳資料類型的取值,屬性err為是否資料類型有錯,屬性errmsg為類型有錯時之錯誤訊息

Type
Object

(async, static) waitFun(fun, opt) → {Promise}

Description:
  • 等待f函數回傳true

    Unit Test: Github

Source:
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(() => {})
Parameters:
Name Type Description
fun function

輸入判斷用函數

opt Object

輸入設定物件,預設{}

Properties
Name Type Attributes Default Description
attemptNum Integer <optional>
200

輸入最大嘗試次數,為正整數,預設200,以輪詢週期計(立即呼叫不計),計數超過attemptNum後於該次呼叫完成時仍未成立即reject,故fun最多被呼叫attemptNum+2次(立即1次與輪詢)

timeInterval Integer <optional>
1000

輸入嘗試時間週期,為正整數,單位為ms,預設1000;每個週期計1次,前一次呼叫未完成時該週期不呼叫fun(輪詢不重疊)但仍計入嘗試次數

onTrace function <optional>
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,resolve回傳為空代表f函數回傳true;fun拋錯或其Promise被reject時(立即呼叫與輪詢皆同)停止輪詢並reject該錯誤,超過最大嘗試次數時reject字串'exceeded attemptNum[最大嘗試次數]';fun非函數時reject字串'waitfunction需輸入函數f';fun回傳永不settle之Promise時亦不settle(不再累積呼叫),計數超過attemptNum後停止計時器(本函數仍不settle,但不使行程無法結束),需逾時者以pmTimeout包裝

Type
Promise

Documentation generated by JSDoc 4.0.5 on Thu Oct 29 2026 05:04:13 GMT+0800 (台北標準時間) using the docdash theme.