5

如何优雅地给对象的所有方法添加异常处理

 2 years ago
source link: https://www.fly63.com/article/detial/11304
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
更新日期: 2022-03-26阅读量: 8标签: 异常分享

扫一扫分享

代码不会全部按照我们的预期运行,可能会有意料之外的情况,为了保证程序的健壮性,要进行异常处理。

比如一个对象的所有方法,都应该做异常处理,但是,如果每个方法都加 try catch 又太麻烦:

const obj = {
    aaa() {
        try {
            // aaa
        } catch(e) {
            // xxxx
        } 
    },
    bbb() {
        try {
            // bbb
        } catch(e) {
            // xxxx
        } 
    },
    ccc() {
        try {
            // ccc
        } catch(e) {
            // xxxx
        } 
    }
}

有没有一种方式既能对所有方法做异常处理,又不用重复写这么多次呢?

有,就是代理模式。

代理模式是通过对目标对象做一层包装,提供和目标对象同名的方法,最终的功能实现还是调用目标对象的方法,但可以额外添加一些职责,比如日志、权限等,透明地对目标对象做一些扩充。

比如 react 里的高阶组件就是代理模式的实现,可以透明的扩展被包装的组件的功能。

很明显,这里的异常处理,也可以用代理的方式来做。但不用完全自己实现,ES6 提供了 Proxy,可以基于它来实现。

定义 createProxy 方法来实现代理,创建一个 Proxy 对象,对目标对象 target 做一层包装,定义 get、set 时的处理:

function createProxy(target) {
    const proxy = createExceptionProxy();
    return new Proxy(target, {
        get: proxy,
        set: proxy
    });
}

function createExceptionProxy() {
    return (target, prop) => {
        if (!(prop in target)) {
            return;
        }

        if (typeof target[prop] === 'function') {
            return createExceptionZone(target, prop);
        }

        return target[prop];
    }
}

如果 target 不包含 prop,就返回空,否则返回对应的属性值 target[prop]。

如果属性值是函数,则做一层包装:

function createExceptionZone(target, prop) {
    return (...args) => {
        let result;
        ExceptionsZone.run(() => {
          result = target[prop](...args);
        });
        return result;
    };
}

最终的功能实现还是调用 target,传入参数,把调用结果作为代理方法的结果返回。

包装这一层的目的是为了做异常处理,也就是 ExceptionsZone.run 做的事情:

class ExceptionsZone {
    static exceptionHandler = new ExceptionHandler();

    static run(callback) {
      try {
        callback();
      } catch (e) {
        this.exceptionHandler.handle(e);
      }
    }
}

调用目标方法,并做 try catch,当出现异常的时候,用 ExceptionHandler 来处理。

这里的异常处理我们就简单打印下日志:

class ExceptionHandler {
    handle(exception) {
        console.log('记录错误:',exception.message, exception.stack);
    }
}

这样就实现了给目标对象的所有方法添加异常处理的目的。

const obj = {
    name: 'guang',
    say() {
        console.log('Hi, I\'m ' + this.name);
    },
    coding() {
        //xxx
        throw new Error('bug');
    }
    coding2() {
        //xxx
        throw new Error('bug2');
    }
}

const proxy = createProxy(obj);

proxy.say();
proxy.coding();

这里的 coding、coding2 方法都会抛出异常,但并没有做异常处理,我们用代理给它加上:

624266d96a370.jpg

我们成功地通过代理模式给对象方法添加了异常处理!

但是现在这样还是有问题的,比如我把 coding 方法改为 async 的就不行了:

624266de2af3f.jpg

那怎么办呢?能不能统一对异步和同步方法做代理呢?

确实没办法,因为没法区分方法是同步还是异步,而且这两种方法的调用方式也不同,但我们可以单独提供一个 runner 方法来运行这些异步逻辑:

class ExceptionsZone {
    static exceptionHandler = new ExceptionHandler();

    static async asyncRun(callback) {
      try {
        await callback();
      } catch (e) {
        this.exceptionHandler.handle(e);
      }
    }
}

然后这样运行:

(async function() {
    await ExceptionsZone.asyncRun(proxy.coding2);
})();

这样就能处理异步逻辑中的异常了:

624266e38e49e.jpg

我们通过代理的方式给对象的所有同步方法添加了异常处理,然后又提供了运行异步方法的 runner 函数,对异步的异常做了处理,结合这两种方式,优雅地给目标对象的所有方法加上了异常处理。

可能你会说,代理就代理,你定义这么多 class 干啥?

因为这段逻辑是我从 Nest.js 源码里摘出来的,它源码里就是这样来给对象添加异常处理的:

624266e91cffc.jpg

异步逻辑也是单独提供了个方法来运行:

624266ef38380.jpg

我觉得这个透明给对象添加异常处理的方式很优雅,就把它从 Nest.js 源码里抽了出来。

总结

为了保证健壮性,我们要对所有可能报错的代码添加异常处理,但是每个方法都添加 try catch 又太麻烦,所以我们利用 Proxy 实现了代理,透明的给对象的所有方法都添加上了异常处理。

但是,代理添加的只是同步的异常处理,并没有捕获异步逻辑的异常,我们可以单独一个一个函数来运行异步方法。

结合代理 + 提供运行异步方法的 runner 这两种方式,就能给一个没有做任何异常处理的对象加上异常处理。是不是很优雅~

本文分享自微信公众号 - 神光的编程秘籍(gh_831b7475367e),作者:神说要有光

链接: https://www.fly63.com/article/detial/11304


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK