JavaScript错误处理的“最佳”实践

3,441 阅读5分钟

最近在进行项目升级,新接口有不同错误码返回要进行各种形式的错误处理。但是旧项目对错误处理做的并不完善,结果完成之后,被领导code review 的时候说:代码没法看了。。。痛定思痛,认真学习一下到底合格的错误处理应该是什么样子的。

关于题目中的最佳只是个人目前觉得比较合适的错误处理,没错就是标题党,看官姥爷保持冷静,勿喷求放过。

一般异常情况都存在在接口请求的情况,当然任何操作都有可能发生异常情况,只是大部分在开发的时候就被我们解决了。一旦发生异常情况我们可能需要针对各种形式的错误码来进行各种处理。最佳实践第一条就是错误代码统一管理

假设有两个请求 getCookiesgetCakes他们都是通过getDessert接口进行请求,但是根据错误代码的不同,需要进行不同的错误提示。

将错误集中处理

我们如果将错误分散处理的话,很有可能造成错误处理覆盖不全以及各种奇怪的引入。

代码中cookie和cake就是集中处理,但是下面的代码还是有问题下一点会说。

对于项目中的错误码最好也是由一个文件一个方法统一维护,这样就能对错误有统一的处理,后期再对返回值进行二次处理就容易得多。

const COOKIE_OUT_OF_STOCK = 'COOKIE_OUT_OF_STOCK';
const NO_CAKE = 'NO_CAKE';
const COOKIE = 'COOKIE';
const CAKE = 'CAKE';

class Dessert{
    getDessert(type) {
        if(type === COOKIE){
            return COOKIE_OUT_OF_STOCK;
        } else if(type === CAKE){
            return NO_CAKE;
        }
    }
}

const dessert = new Dessert();
const errorCake = dessert.getDessert(CAKE);
if(errorCake === NO_CAKE){
    console.log('cake out of stoke');
}
if(errorCake === COOKIE_OUT_OF_STOCK) {
    console.log('cookie out of stoke');
}

抛出错误来替代返回错误码

上面的代码使用了返回错误码进行错误处理,但这个方法响亮但不文雅。使用try catch 这种错误抛出的方法会让代码看起来更简洁,而且能明确的指出此处是在错误处理。

const COOKIE_OUT_OF_STOCK = 'COOKIE_OUT_OF_STOCK';
const NO_CAKE = 'NO_CAKE';
const COOKIE = 'COOKIE';
const CAKE = 'CAKE';

class DESSERT{
    getDessert(type) {
        if(type === COOKIE){
            throw new  Error(COOKIE_OUT_OF_STOCK);
        } else if(type === CAKE){
            throw new  Error(NO_CAKE);
        }
    }
}

const dessert = new Dessert();

try{
    const errorCake = dessert.getDessert(CAKE);
} catch(e) {
    console.log(e)
}

我们将需要处理的代码放进try中,通过catch将对应的错误直接抛出,而不需要在业务代码中遍历所有的错误码,才做对应处理。

try之后的代码执行

try{
    const errorCake = dessert.getDessert(CAKE);
    console.log('Shop is closed');
} catch(e) {
    console.log(e);
}

如果我们在try 中 在错误之后有其他逻辑,这部分逻辑是不会执行到的。

finally

但是我们如果想执行后面的代码的话,比如销毁实例或者停止定时器等等,那我们可能需要使用finally 来实现。

try{
    const errorCake = dessert.getDessert(CAKE);
} catch(e) {
    console.log(e);
} finally{
    console.log('Shop is closed');
}

try中的代码如果是同步的代码,那么就是上面说的两种情况。

异步代码的错误处理

如果在try代码块中使用了异步请求,那promise之后的代码还是会被执行。

const COOKIE_OUT_OF_STOCK = 'COOKIE_OUT_OF_STOCK';
const NO_CAKE = 'NO_CAKE';
const COOKIE = 'COOKIE';
const CAKE = 'CAKE';

class Dessert{
    getDessert(type) {
        if(type === COOKIE){
            Promise.reject( COOKIE_OUT_OF_STOCK);
        } else if(type === CAKE){
            Promise.reject( NO_CAKE);
        }
    }
}

const dessert = new Dessert();

try{
     const errorCake = dessert.getDessert(CAKE);
    console.log('error')
} catch(e) {
    console.log(e)
}

如果想要使用catch捕获errorCake,那就要使用promise的catch方法,并且then要放到catch的后面。

const COOKIE_OUT_OF_STOCK = 'COOKIE_OUT_OF_STOCK';
const NO_CAKE = 'NO_CAKE';
const COOKIE = 'COOKIE';
const CAKE = 'CAKE';

class Dessert{
    getDessert(type) {
        if(type === COOKIE){
           return Promise.reject( COOKIE_OUT_OF_STOCK);
        } else if(type === CAKE){
           return Promise.reject( NO_CAKE);
        }
    }
}

const dessert = new Dessert();


dessert.getDessert(CAKE). catch(e => {
    console.log(e)
}).then(() => {
     console.log('error')
});

async / await 的错误抛出

但其实链式调用的方法还是和我们原来的代码有差距,那如果使用async/ await 来代替promise then 呢。

const COOKIE_OUT_OF_STOCK = 'COOKIE_OUT_OF_STOCK';
const NO_CAKE = 'NO_CAKE';
const COOKIE = 'COOKIE';
const CAKE = 'CAKE';

class Dessert{
    getDessert(type) {
        if(type === COOKIE){
           return Promise.reject( COOKIE_OUT_OF_STOCK);
        } else if(type === CAKE){
           return Promise.reject( NO_CAKE);
        }
    }
}

const dessert = new Dessert();

try{
 await dessert.getDessert(CAKE);
 console.log('closed');

} catch(e){
console.log(e)
}

此时我们惊奇的发现closed没有执行,直接进行了错误抛出。

所以还是要用finally 来处理

try{
 await dessert.getDessert(CAKE);
} catch(e){
 console.log(e)
} finally{
  console.log('closed');
}

捕获错误之后

上面的例子里面,所有的error都是使用console.log进行输出的,在控制台中可以看到,输出并没有很明显。

在捕获错误之后,我们希望能够明显的输出指出错误,而不是就让它抛出之后就没有然后了。在catch中如果没有一些必需的错误处理的话,还是建议使用console.error来进行错误抛出.

此外,个人还是不建议将错误的一些特殊处理放在(这样会让整个catch代码块变得臃肿和不简洁。接口层和业务层中间最好增加一个中间层,来批量的处理这些数据和错误。(没错,这是被摩擦之后的总结)

此外,错误最好不使用默认的抛出,一个好的错误抛出是明确告诉到底发生了什么错误,最好是完整的一句话,或者是让人易于理解的内容。

总结

try ,catch 会让错误处理变得更简洁一点(与直接处理各种错误情况 相比)。虽然之前掘金有关于try catch驳来驳去的文章,但是业务中的错误处理还是有必要进行的。 有一些try catch 的问题,可能只是因为不太了解try catch 的使用(比如catch之后的错误再处理),当然这也不是说try catch 是完美的,但错误处理还是有必要作为项目中的一部分的,甚至是比较重要的一部分。

如果文章中有任何不当的地方欢迎在评论区指正。