pmHook.mjs

import get from 'lodash-es/get.js'
import genPm from './genPm.mjs'
import isfun from './isfun.mjs'
import isnull from './isnull.mjs'
import isundefined from './isundefined.mjs'


/**
 * 掛勾非同步(Promise)函數,可監聽或修改Promise的輸出入訊號
 *
 * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/pmHook.test.mjs Github}
 * @memberOf wsemi
 * @param {Function} fun 輸入非同步Promise函數
 * @param {Function} [cb=() => {}] 輸入回調函數,預設()={},cb函數之輸入為監聽到的資訊物件,欄位有mode與data,mode可為'before'、'afterThen'、'afterCatch'字串,而data則代表非同步函數的輸入或輸出資訊。若想於cb函數修改回傳,則由cb函數的輸入修改完回傳即可。例如收到msg={mode:'before',data:'123'},將msg.data='abc',再return msg.data
 * @returns {Promise} 回傳為Promise,resolve為回傳成功結果,reject為回傳失敗訊息
 * @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(() => {})
 *
 */
function pmHook(fun, cb = () => {}) {

    //check
    if (!isfun(fun)) {
        return null
    }
    if (!isfun(cb)) {
        cb = () => {}
    }

    function getData(newData, oriData) {
        let r
        if (!isnull(newData) && !isundefined(newData)) {
            r = get(newData, 'data', null)
            if (r) {
                return r //若回傳是包成原始物件格式, 則取出data才回傳
            }
            return newData //若回傳是有效的新數據, 直接回傳
        }
        return oriData //若回傳為無效數據, 則回傳原始數據
    }

    return function() {
        let r

        //pm
        let pm = genPm()

        //callback before
        let input = cb({
            mode: 'before',
            data: arguments,
        })

        //check
        if (isnull(input) || isundefined(input)) {
            input = arguments
        }

        //call fun with input
        fun(...input)
            .then((output) => {
                r = cb({
                    mode: 'afterThen',
                    data: output,
                })
                output = getData(r, output)
                pm.resolve(output)
            })
            .catch((output) => {
                r = cb({
                    mode: 'afterCatch',
                    data: output,
                })
                output = getData(r, output)
                pm.reject(output)
            })

        // //pxy
        // let pxy = new Proxy(fun(input), {
        //     get(target, prop, receiver) {
        //         //console.log('target=', target, ', prop=', prop, ', receiver=', receiver)
        //         let value = Reflect.get(...arguments)
        //         target
        //             .then((output) => {
        //                 cb({
        //                     mode: 'afterThen',
        //                     data: output,
        //                 })
        //             })
        //             .catch((output) => {
        //                 cb({
        //                     mode: 'afterCatch',
        //                     data: output,
        //                 })
        //             })
        //         return typeof value === 'function' ? value.bind(target) : value
        //     }
        // })

        return pm
    }
}


export default pmHook