Express源码系列之错误处理

620 阅读5分钟

本系列目录

1. 全局 点我去看

2. 路由匹配 点我去看

3. 错误处理 (本文)

4. 视图模板 点我去看

5. 设计思想 点我去看


1. 常用语言的错误捕获

1.1 C:约定发生错误时的返回值,比如 api函数返回NULL,-1,'\0' 等

FILE *fp;
if( (fp=fopen("D:\\demo.txt","rb") == NULL ){
    printf("Fail to open file!\n");
    exit(0);  //退出程序(结束程序)
}

1.2 c++:try...catch...catch... throw

try {
    语句组
}
catch(异常类型) {
    异常处理代码
}
...
catch(异常类型) {
    异常处理代码
}

1.3 java: try...catch...catch...finally throw

try{
    
}catch(异常类型) {
    异常处理代码
}
...
catch(异常类型) {
    异常处理代码
}
finally{
    
}

1.4. Go语言:放在返回值的特定位

func test(n float64) (float64, error) {
    if n < 0 {
        return 0, errors.New("error")
    }
    // 实现
}
result, err:= test(-1)
if err != nil {
   fmt.Println(err)
}

​ 为什么我要看别的语言是怎么进行错误捕获的呢?一方面是尝试在别的语言上找到错误捕获的经验,更重要的是,javaScript本来就是解释型语言,底层是V8也就是c/c++,了解他们的错误捕获对研究javaScript本身的错误出现的原因,怎么去捕获是非常有必要的,即使在语法层面可以用try...catch。


2. 哪些地方可能报错?

​ 当js出错的时候,一个error对象就会被建立,本来这小节是想在v8源码里找找到底js的错误是怎么从c++底层通知到js语言层的,奈何国内在v8源码这方面的资料实在是很难找,自己这蹩脚英语看国外文章有慢的一批,找来源码看看,看了几小时,看不出个所以然来,就此放下,等日后有时间再回过头来,攻克v8,于是只能说说js层面可能出错的地方

​ 在js语言层面上可能出现错误的地方还是挺统一的,主要在以下几个方面

  • JS 语法错误、代码异常
    • 编译阶段出错
    • 执行阶段出错
  • AJAX 请求异常
  • 静态资源加载异常
  • Promise 异常
  • Iframe 异常
  • 跨域 Script error
  • 崩溃和卡顿

对于一个服务器框架来说,对错误的处理可以说是至关重要,回到主题,看看Express内部的错误是怎么处理的


3. Express的错误处理

​ 首先我们要清楚,对于一个框架来说,错误更多的并不是语言本身出错,或者一些意想不到的地方报错,而是用户,也就是开发人员不合法使用框架,框架本身报的自定义错误,这种错误使用一般会对框架本身产生毁灭性的影响,所以不得不抛出一个错误告诉开发人员。

​ Express内部的错误有以下几种

3.1 自定义错误,尝试引用不存在的属性、方法、对象

  • 一些废弃的中间件
var removedMiddlewares = [
  'bodyParser',
  'compress',
  'cookieSession',
  'session',
  'logger',
  'cookieParser',
  'favicon',
  'responseTime',
  'errorHandler',
  'timeout',
  'methodOverride',
  'vhost',
  'csrf',
  'directory',
  'limit',
  'multipart',
  'staticCache'
]
removedMiddlewares.forEach(function (name) {
  Object.defineProperty(exports, name, {
    get: function () {
      throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.');
    },
    configurable: true
  });
});
  • 对象上废弃的属性
Object.defineProperty(this, 'router', {
   get: function () {
      throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.');
    }
});

这错报的无可厚非,都不存在了,肯定引用失败

3.2 自定义错误,不合语法的使用

  • router.use没有传入中间件函数
var callbacks = flatten(slice.call(arguments, offset));
  if (callbacks.length === 0) {
    throw new TypeError('Router.use() requires a middleware function')
  }
//...
if (typeof fn !== 'function') {
    throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
}
  • route.all没有传入callback
if (typeof handle !== 'function') {
      var type = toString.call(handle);
      var msg = 'Route.all() requires a callback function but got a ' + type
      throw new TypeError(msg);
 }

3.3 使用拓展包时可能报的错

  • decodeURIComponent
try {
   return decodeURIComponent(val);
} catch (err) {
   if (err instanceof URIError) {
      err.message = 'Failed to decode param \'' + val + '\'';
      err.status = err.statusCode = 400;
   }
   throw err;
}

还遵循只catch明确知道的错误原则,代码质量杠杠的

  • parseUrl
// get pathname of request
function getPathname(req) {
  try {
    return parseUrl(req).pathname;
  } catch (err) {
    return undefined;
  }
}

3.4 中间件执行过程中的报错

​ 中间件执行过程是Express最复杂的过程,报错的原因有很多,比如说中间件函数执行出错,匹配layer出错等,在内部,统一用一个layerError变量表示这种错误,每次执行下一个中间件之前都要检查有没有错误,有的话终止执行,并将错误向下传递,直到被专门的错误处理函数处理,就像是promise链一样

流程的错误捕获

 function next(err) {
    var layerError = err === 'route' ?
      null :
      err;
    // remove added slash
    if (slashAdded) {
      req.url = req.url.substr(1);
      slashAdded = false;
    }
    // restore altered req.url
    if (removed.length !== 0) {
      req.baseUrl = parentUrl;
      req.url = protohost + removed + req.url.substr(protohost.length);
      removed = '';
    }

    // signal to exit router
    if (layerError === 'router') {
      setImmediate(done, null)
      return
    }
	//codes ....
 }

专门处理错误的layer

Layer.prototype.handle_error = function handle_error(error, req, res, next) {
  var fn = this.handle;

  if (fn.length !== 4) {
    // not a standard error handler
    return next(error);
  }

  try {
    fn(error, req, res, next);
  } catch (err) {
    next(err);
  }
};

4. 总结

​ Express的错误捕获分为框架相关错误和框架无关错误,框架相关错误会使框架直接崩溃,比如说用不合语法,框架无关错误一般是中间件流执行的时候发生的意料之外的错误,交由专门的error-handle layer处理,比如如下

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

参考

  1. C/C++ 对错误的处理
  2. 如何优雅处理前端异常?