美文网首页收藏Web前端之路首页投稿(暂停使用,暂停投稿)
抽象封装——JavaScript高阶函数运用的实例

抽象封装——JavaScript高阶函数运用的实例

作者: 写bug的猫 | 来源:发表于2017-03-15 14:21 被阅读165次

    这是一个对第三方js-sdk进行封装的小例子,希望整个思路过程对JS初学者有所启发。

    1. 背景

    某系统开发中引用第三方基于websocket的服务,需要对js-sdk进行封装,其最简单的demo如下:

    // 触发event
    window.doLogin()
    
    // event的回调实现
    window.cbLogin = res => {
        if (res.code === 0) {
            //doSomethingSuccess
        }
        else {
            //doSomethingFail
        }
    }
    

    先实现某个event的回调函数,然后触发某个event,第三方库会基于websocket发出请求,当有结果返回,会执行event的回调函数。

    2. 最初实现

    在业务代码 biz.js 中引入封装库 clink.js,然后注册回调函数,并且将业务的上下文传递回去:

    //biz.js 业务代码
    import clink from './clink'
    
    class Action {
        constructor () {
            super()
    
            // 注册回调函数
            clink.cbLogin(this)
            clink.cbLogout(this)
        }
    
        doLogin () {
            window.doLogin()
        }
    
        // 在window.onload时候执行
        componentDidMount() {
            // 触发login事件
            this.doLogin()
        }
    }
    
    //clink.js 对sdk的封装
    
    const clink = {}
    
    clink.cbLogin = ctx => {
        window.cbLogin = res => {
            if (res.code === 0) {
                //doSomethingSuccess
            }
            else {
                 //doSomethingFail
            }
        }
    }
    
    // 还存在其他event,代码结构类似,此处省略
    clink.cbLogout = ...
    clink.cbEvent1 = ...
    clink.cbEvent2 = ...
    

    biz.js是为了说明怎样来触发event的,我们主要来看clink.js:

    // 实现了一个个event的回调
    clink.cbLogin = ...
    clink.cbLogout = ...
    clink.cbEvent1 = ...
    clink.cbEvent2 = ...
    

    3. 需求改进

    当由于网络不稳定等因素,使得回调函数得到的返回结果是失败,则需要重试,默认重试次数是3次。简单地说需要一个重试机制。

    下面我们来实现这个重试,仅做到满足需求:

    //clink.js
    
    clink.cbLogin = ctx => {
        const MaxRetryCount = 3
        let retryCount = 0
    
        window.cbLogin = res => {
            if (res.code === 0) {
                //doSomethingSuccess
            }
            else {
                 if (retryCount < MaxRetryCount) {
                     // retry
                     ctx.doLogin()
                 }
                 else {
                     //doSomethingFail
                 }
            }
        }
    }
    

    我们应该已经发现,每个回调函数中 MaxRetryCount retryCount if...else...这些是重复的部分,既然是重复的部分,那我们应该做抽象和封装。
    而显然,这里的 变量doSomethingSuccessdoSomethingFail,因为JavaScript中的函数是一等公民(First Class Object),可以把Function当成参数传递,所以我们把这两个doSomething当成参数进行封装

    4. 开始封装

    4.1. 修改 if...else...语句

    首先,两层 if...else... 语句不够优雅,做如下修改:

    const success = parseInt(res.code, 10) === 0;
    
    // 未成功且没达到重试上限,执行重试
    if (!success && (retryCount < maxRetryCount)) {
        // retry
    }
    // 未成功且达到重试上限,执行失败回调
    else if (!success && (retryCount >= maxRetryCount)) {
          // doSomethingFail  
    }
    // 成功,执行成功回调
    else {
        // doSomethingSuccess
    }
    
    4.2. 定义高阶函数的参数

    如上所述,将 doSomethingSuccessdoSomethingFail 作为两个参数。另外还需要一个参数对象来做一下设置,如重试次数等,具体如下:

    /**
     * 高阶辅助函数
     * @param  {Object} args - 参数对象
     * @param  {Object} args.ctx - Action上下文
     * @param  {string} args.name - callback名称
     * @param  {number} args.retry - 重试次数
     * @param  {Function} args.doEvent - 触发event
     * @param  {Function} successCallback - 成功回调函数
     * @param  {Function} failCallback    - 失败回调函数
     *
     */
    const cHelper = (args, successCallback, failCallback) => {
    }
    
    3.3. 实现高阶函数

    实现cHelper,并且用cbLogincbLogout 等来说明:

    // clink.js
    
    /**
     * 高阶辅助函数
     */
    const cHelper = (args, successCallback, failCallback) => {
        let retryCount = 0
        const maxRetryCount = args.retry || 3
    
        return root[args.name] = res => {
            const context = args.ctx
            const success = parseInt(res.code, 10) === 0
    
            if (!success && (retryCount < maxRetryCount)) {
                args.doEvent && (args.doEvent instanceof Function) && args.doEvent()
                retryCount++
            }
            else if (!success && (retryCount >= maxRetryCount)) {
                failCallback(res)
            }
            else {
                successCallback(res)
            }
        }
    }
    
    /**
     * 登录回调函数 
     */
    clink.cbLogin = function (ctx) {
        let me = this
        let params = {
            ctx: ctx,
            name: 'cbLogin',
            doEvent() {
                me.doLogin()
            }
        }
    
        cHelper(params, res => {
            // doSomethingSuccess
        }, res => {
            // doSomethingFail
        })
    }
    
    /**
     * Event1回调函数(重试5次)
     */
    clink.cbEvent1 = function (ctx) {
        let me = this
        let params = {
            ctx: ctx,
            retry: 5,
            name: 'cbEvent1',
            doEvent() {
                me.doEvent1()
            }
        }
    
        cHelper(params, res => {
            // doSomethingSuccess
        }, res => {
            // doSomethingFail
        })
    }
    
    /**
     * Event2回调函数(不重试)
     */
    clink.cbEvent2 = function (ctx) {
        let params = {ctx: ctx, name: 'cbEvent2'}
    
        cHelper(params, res => {
            // doSomethingSuccess
        }, res => {
            // doSomethingFail
        })
    }
    

    5. 进一步抽象

    我们希望 clink.js 中回调的实现更加抽象,将其中业务逻辑 doSomthingForBiz(比如一些页面元素的操作)抽离出来到biz.js中,而clink.js只包含一些公共逻辑 doSomthingForCommon。所以我们在 biz.js 中写业务代码时,也实现回调函数,因此在clink.js中要做如下修改来实现方法的继承:

    //biz.js
    import clink from './clink'
    
    class Action {
        constructor () {
            super()
    
            clink.cbEvent1(this)
            clink.cbEvent2(this)
        }
        
        // 实现Event1回调函数
        cbEvent1 (res) {
            // doSomethingForBiz
        }
        
        // 实现Event2回调函数
        cbEvent2 (res) {
            // doSomethingForBiz
            if (res.code === 0) {
                // doSomethingSuccessForBiz
            }
            else {
                / /doSomethingFailForBiz
            }
        }
    }
    
    // clink.js
    
    const cHelper = (args, successCallback, failCallback) => {
        let retryCount = 0
        const maxRetryCount = args.retry || 3
    
        return root[args.name] = res => {
            const context = args.ctx
            const success = parseInt(res.code, 10) === 0
            // biz中是否有定义callback
            const isOverride = context && (context[args.name] instanceof Function)
    
            if (!success && (retryCount < maxRetryCount)) {
                args.doEvent && (args.doEvent instanceof Function) && args.doEvent()
                retryCount++
            }
            else if (!success && (retryCount >= maxRetryCount)) {
                failCallback(res)
                // 执行biz中的callback
                isOverride && context[args.name].call(context, res);
            }
            else {
                successCallback(res)
                // 执行biz中的callback
                isOverride && context[args.name].call(context, res)
            }
        }
    }
    
    /**
     * Event1回调函数(有公共逻辑)
     */
    clink.cbEvent1 = function (ctx) {
        let me = this
        let params = {ctx: ctx, name: 'cbEvent1'}
    
        cHelper(params, res => {
            // doSomethingSuccessForPauseCommon
        }, res => {
            // doSomethingFailForPauseCommon
        })
    }
    
    /**
     * Event2回调函数(无公共逻辑)
     */
    clink.cbEvent2 = function (ctx) {
        let me = this
        let params = {ctx: ctx, name: 'cbEvent2'}
    
        cHelper(params, res => true, res => true)
    }
    

    这样一来,执行完 doSomethingForCommon 后会执行 doSomethingForBiz

    6. 总结

    至此,已经完成了全部的重构封装。clink.js是对sdk的封装,服务于业务biz1, biz2, ...,其中cHelper函数是 clink.js中其他callback的高阶封装。

    相关文章

      网友评论

        本文标题:抽象封装——JavaScript高阶函数运用的实例

        本文链接:https://www.haomeiwen.com/subject/jpfknttx.html