aop

 aop  - 切面编程,主要用于执行一些与业务不相关的操作。

比如上报数据、日志输出等与业务不强相关的操作,把这些非业务操作与业务流程解耦。

还有其它使用情况,比如有一个通用弹层,打开弹层和关闭弹层都有一些操作,比如打开弹层需要把body的颜色变成灰色,然后关闭弹层需要把颜色还原等这样的操作。正常的解决方法是

需要增加beforeOpen,beforeClose这样的回调函数,需要接口提供对应的参数接口,如果需要在打开弹层之后还需要提供 afterOpen等这样的方法。但是如果弹层支持aop就可以直接对open方法进行修改了。

一般情况下,aop是用在同步函数中使用的,但是有时候异步函数也需要用到,比如:请求列表的数据,需要把数据列表的盒展示成loading状态,数据请求回来之后,再去掉loading状态。所以我写了个工具函数,也支持

返回Promise的函数、async函数、Generator函数

aop通用方法完整案例如下:

export function isFunction(arg) {
  return typeof arg === "function";
}
export function isGeneratorFunction(obj) {
  return obj && obj.constructor && "GeneratorFunction" === obj.constructor.name;
}
export function isAsyncFunction(obj) {
  return obj && obj.constructor && "AsyncFunction" === obj.constructor.name;
}
export function isPromise(obj) {
  return obj && "function" === typeof obj.then;
}
/**
 * 切面编程
 * @param {Function | GeneratorFunction | AsyncFunction} fn - 方法
 * @param {*} beforeFn - 前置方法
 * @param {*} afterFn - 后置方法
 */
export function aop(fn, beforeFn, afterFn) {
  if (isGeneratorFunction(fn)) {
    const newFn = function(...args) {
      const me = this;
      if (isFunction(beforeFn)) {
        beforeFn.apply(me, args);
      }
      let ret = fn.apply(me, args);
      let next = ret.next;
      ret.next = function(...arg) {
        const done = next.apply(ret, arg);
        if (done.done) {
          if (isFunction(afterFn)) {
            afterFn.apply(me, args);
          }
        }
        return done;
      };
      return ret;
    };
    for (let key in fn) {
      if (fn.hasOwnProperty(key)) {
        newFn[key] = fn[key];
      }
    }
    return newFn;
  }

  if (isAsyncFunction(fn)) {
    const newFn = async function(...args) {
      const me = this;
      if (isFunction(beforeFn)) {
        beforeFn.apply(me, args);
      }
      return await fn
        .apply(me, args)
        .then((...data) => {
          if (isFunction(afterFn)) {
            afterFn.apply(me, data);
          }
          return data;
        })
        .catch((...err) => {
          if (isFunction(afterFn)) {
            afterFn.apply(me, err);
          }
          throw err;
        });
    };
    for (let key in fn) {
      if (fn.hasOwnProperty(key)) {
        newFn[key] = fn[key];
      }
    }
    return newFn;
  }

  if (isFunction(fn)) {
    const newFn = function(...args) {
      const me = this;
      if (isFunction(beforeFn)) {
        beforeFn.apply(me, args);
      }
      const ret = fn.apply(me, args);
      if (isFunction(afterFn)) {
        if (isPromise(ret)) {
          ret
            .then((...data) => {
              afterFn.apply(me, data);
            })
            .catch((...err) => {
              afterFn.apply(me, err);
            });
        } else {
          afterFn.apply(me, args);
        }
      }
      return ret;
    };
    for (let key in fn) {
      if (fn.hasOwnProperty(key)) {
        newFn[key] = fn[key];
      }
    }
    return newFn;
  }
}
export function before(fn, callback) {
  return aop(fn, callback, null);
}
export function after(fn, callback) {
  return aop(fn, null, callback);
}
export function wrap(fn, beforeFn, afterFn) {
  return aop(fn, beforeFn, afterFn);
}
export function aopFactory(fn) {
  //非函数或者已有aop方法原样输出
  if (typeof fn !== "function" || fn.before || fn.after || fn.wrap) {
    return fn;
  }
  fn.before = function(callback) {
    const me = this;
    return before(me, callback);
  };
  fn.after = function(callback) {
    const me = this;
    return after(me, callback);
  };
  fn.wrap = function(beforeFn, afterFn) {
    const me = this;
    return wrap(me, beforeFn, afterFn);
  };
  return fn;
}

使用案例 - 普通方法调用:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>普通函数</title>
</head>

<body>
    <script src="./aop.js"></script>
    <script>
        function pureFn() {
            console.log('我是一个普通的函数');
        }
        const aopFn = aopFactory(pureFn);
        const fn = aopFn.before(function () {
            console.log('我是前置方法')
        })
            .after(function () {
                console.log('我是后置方法')
            });
        fn();
    </script>

</body>

</html>

 使用案例 - async方法调用 :  

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>普通函数</title>
</head>

<body>
    <script src="./aop.js"></script>
    <script>
        async function asyncFn() {
            const ret = await new Promise((resolve) => {
                setTimeout(() => {
                    console.log('异步函数内部操作')
                    resolve('我是async方法,返回值。')
                }, 1000);
            })
            console.log(`ret: ${ret}`)
            return ret;
        }
        const aopFn = aopFactory(asyncFn);
        const fn = aopFn.before(function () {
            console.log('我是前置方法')
        })
            .after(function () {
                console.log('我是后置方法')
            });
        fn()
            .then((data) => {
                console.log(data);
            })
            .finally(() => {
                console.log('finally');
            })
    </script>

</body>

</html>

结果:

使用案例 -  promise方法 :   

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>普通函数</title>
</head>

<body>
    <script src="./aop.js"></script>
    <script>
        function promiseFn() {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve('返回内容')
                }, 1000)

            })
        }
        const aopFn = aopFactory(promiseFn);
        const fn = aopFn.before(function () {
            console.log('前置方法')
        })
            .after(function () {
                console.log('后置方法')
            });
        fn().then((data) => {
            console.log(data);
        });    
    </script>

</body>

</html>

注意这里有个小问题是,这个after的方法是在异步之后执行的,但是是在其它then之前执行的。

原文地址:https://www.cnblogs.com/hellolol/p/11659921.html