AOP

为什么要在 JavaScript 中应用面向方面的编程?

有一些代码逻辑,需要和业务逻辑分离出来,所以应用面向方面的编程比较自然。

比如记录日志发送通知拦截等等。除了这两种场景,我还在实际的 CRM 系统中, 应用面向方面编程来控制用户的状态迁移。

记录日志 很容易理解,我们实际的使用场景是对于某些操作,会发送信息到 fundebug 平台。

发送通知拦截,是指在发送邮件、微信模板消息时,需要检查当前是否为正式环境,该用户是否允许接收通知等等情况。

用户状态迁移,也即系统中一个用户的整个生命周期。

以上这些场景,使用面向方面编程能够实现更好的关注点分离,第二个好处是,不需要对原有代码进行修改,而是添加新的文件,在引用原有文件后增加新的代码,并在入口文件里调用新文件即可。代码迭代的方式比较优雅。

如何应用?

在网上搜索之后,发现比较好的实现方式是通过在 Functionprototype 里增加通用的前置或者后置函数。然后,就是把原有方法使用这样的前置或者后置函数包装,这个前置或者后置函数接收一个函数参数,在这个函数参数中,可以添加自定义的代码,以实现在原来的函数执行前或者后,需要额外做的逻辑。

在实际应用中,需要针对 ES 6 以及 asyncawait 专门写一个版本。

代码示例

// AOP.js
export default class AOP {
    static setBefore() {
        /* eslint-disable */
        Function.prototype.before = function (func) {
            const self = this
            return function () {
                if (func.apply(this, arguments) === false) {
                    return false;
                }

                return self.apply(this, arguments)
            }
        };

        Function.prototype.beforeAsync = function (asyncFunc) {
            const self = this
            return async function () {
                if (await asyncFunc.apply(this, arguments) === false) {
                    return false
                }

                return await self.apply(this, arguments)
            }
        }
        /* eslint-enable */
    }

    static setAfter() {
        /* eslint-disable */
        Function.prototype.afterAsync = function (asyncFunc) {
            const self = this;
            return async function () {
                let result = await self.apply(this, arguments)

                if (result === false) {
                    return false;
                }

                await asyncFunc.apply(self, [result, ...arguments]);
                return result;
            }
        }
        /* eslint-enable */
    }
}

应用:

import AOP from './AOP'

// 原有代码:
function doSomething(){}

// 加 
AOP.setBefore()
doSomething = doSomething.before(()=>{
    logger.info('will do something...')
})
// 原有通知代码 wechat.js:
export default class wechat{
    static sendTemplateMessage(wechat_openid){}
}
import wechat from './wechat.js'
import AOP from './AOP'

// 通知拦截
AOP.setBefore()
wechat.sendTemplateMessage = wechat.sendTemplateMessage.beforeAsync(async (wechat_openid)=>{
    if(!inProduction || !(await getUserByOpenId(wechat_openid)).allowReceivingNotification){
        return false
    } 
    
    return true
})

// 把引用原来的 wechat 的地方,改成引用这个新的 wechat
module.exports = wechat
// 原代码
async doSomething(userId){
    await xxx()
}

// 新代码
import AOP from './AOP'

AOP.setAfter()
doSomething = doSomething.after(async (userId)=>{
    await tagUserAsState(userId, 'new state')
})

待做事项

AOP.js 封装成一个 npm 包,方便不同的项目使用。