The code will not all work as we expect, and there may be unexpected situations that require exception handling to ensure the robustness of the program.

For example, all methods of an object should do exception handling, but it would be too cumbersome to add a try catch to each method:

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

Is there a way to do exception handling for all methods without having to repeat it so many times?

Yes, the proxy mode.

The proxy mode is to provide a method with the same name as the target object by wrapping the target object. The final function is still to call the method of the target object, but it can add some additional responsibilities, such as logging, permissions, etc., to transparently expand the target object.

For example, high-order components in React implement the proxy mode, which transparently extends the functionality of wrapped components.

Obviously, exception handling here can also be done in a proxy manner. But instead of implementing it yourself, ES6 provides a Proxy that you can implement on top of.

Create a Proxy object, wrap the target object around it, and define the get/set handler:

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

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

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

        returntarget[prop]; }}Copy the code

Null if target does not contain prop, otherwise the corresponding property value target[prop] is returned.

If the attribute value is a function, wrap it:

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

The final functionality is to call target, pass in the argument, and return the result of the call as the result of the proxy method.

The purpose of wrapping this layer is to do exception handling, which is what Exceptionszone.run does:

class ExceptionsZone {
    static exceptionHandler = new ExceptionHandler();

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

Call the target method and do a try catch. When an exception occurs, use ExceptionHandler to handle it.

Here we simply print the log for exception handling:

class ExceptionHandler {
    handle(exception) {
        console.log('Error recording:',exception.message, exception.stack); }}Copy the code

This accomplishes the purpose of adding exception handling to all methods of the target object.

Under test:

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();
Copy the code

The coding and coding2 methods here both raise exceptions, but do not handle exceptions. We use the proxy to add:

We have successfully added exception handling to object methods through proxy mode!

For example, I changed the coding method to async.

So what to do? Can you broker asynchronous and synchronous methods uniformly?

There is no way, because there is no way to tell whether a method is synchronous or asynchronous, and both methods are called differently, but we can provide a separate Runner method to run the asynchronous logic:

class ExceptionsZone {
    static exceptionHandler = new ExceptionHandler();

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

Then run it like this:

(async function() {
    awaitExceptionsZone.asyncRun(proxy.coding2); }) ();Copy the code

This handles exceptions in asynchronous logic:

We add exception handling to all the object’s synchronous methods by proxy, and then provide runner functions that run asynchronous methods to handle asynchronous exceptions. Combining these two methods, we elegantly add exception handling to all the target object’s methods.

You might say, agent is agent, why do you define so many classes?

This is because I extracted this logic from the nest. js source code, which adds exception handling to the object like this:

Asynchronous logic also provides a separate method to run:

I thought transparency was an elegant way to add exception handling to an object and pulled it out of the nest.js source code.

The complete code is as follows:

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

function createExceptionProxy() {
    return (receiver, prop) = > {
        if(! (propin receiver)) {
            return;
        }

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

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

class ExceptionHandler {
    handle(exception) {
        console.log('Error recording:',exception.message, exception.stack); }}class ExceptionsZone {
    static exceptionHandler = new ExceptionHandler();
  
    static run(callback) {
      try {
        callback();
      } catch (e) {
        this.exceptionHandler.handle(e); }}static async asyncRun(callback) {
      try {
        await callback();
      } catch (e) {
        this.exceptionHandler.handle(e); }}}const obj = {
    name: 'guang'.say() {
        console.log('Hi, I\'m ' + this.name);
    },
    coding() {
        //xxx
        throw new Error('bug');
    },
    async coding2() {
        //xxx
        throw new Error('bug2'); }}const proxy = createProxy(obj);

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

(async function() {
    awaitExceptionsZone.asyncRun(proxy.coding2); }) ();Copy the code

conclusion

To ensure robustness, we need to add exception handling to all code that might fail, but it is too cumbersome to add a try catch to every method, so we use Proxy to implement the Proxy and transparently add exception handling to all methods of the object.

However, the proxy adds only synchronous exception handling and does not catch exceptions for asynchronous logic, so we can define a separate function to run asynchronous methods.

By combining a proxy with a runner that provides asynchronous methods, you can add exception handling to an object that doesn’t do any exception handling. Isn’t it very elegant