如何捕捉request抛出的异常

request是用Node.js编写的一个HTTP客户端库,用于发出HTTP/HTTPS协议的请求,项目主页在此。它提供回调风格的API,例如下面的代码将访问example.com并将页面源码输出到终端

'use strict';

const request = require('request');

request('http://example.com/', function (error, response, body) {
  if (error) {
    console.error(error);
  } else {
    console.log(body);
  }
});

保存到文件request-example.js中,在命令行执行下列代码即可看到效果

node request-example.js

除了网页之外,request当然也可以请求HTTP协议的服务端API。但如果只是想要请求一下接口,而不需要获得响应结果时,可能会写出下面这样的代码

'use strict';

const request = require('request');

request('http://example.com/');

这样确实可以对服务端API发出请求,可以在本地启动一个Web服务器验证这一点,例如使用下列命令

python -m SimpleHTTPServer 8000

就可以启动一个监听8000端口的HTTP服务器了。将request-example.js文件中的目标地址改为http://127.0.0.1:8000/后再执行,就可以看到Web服务器在终端输出了一行访问日志。

可如果请求服务端API的过程中遇到了问题,上面的代码将会怎样呢?我最开始认为,既然回调函数的第一个参数是用于接收可能出现的错误的,那么如果没有回调函数,这个错误无处可去,这个它应该会被安静地丢掉。为了验证这个猜想,我把将本地的Web服务器关闭,然后再执行这段Node.js代码,结果在我的电脑上,node输出了如下信息

events.js:160
      throw er; // Unhandled 'error' event
      ^

Error: connect ECONNREFUSED 127.0.0.1:8000
    at Object.exports._errnoException (util.js:1022:11)
    at exports._exceptionWithHostPort (util.js:1045:20)
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1087:14)

出乎意料,request居然抛出了异常,这是为什么呢?阅读request的源代码后发现,如果调用request时提供了回调函数,那么下面这段代码会使用这个回调函数来处理error事件

  if (!self._callback && self.callback) {
    self._callback = self.callback
    self.callback = function () {
      if (self._callbackCalled) {
        return // Print a warning maybe?
      }
      self._callbackCalled = true
      self._callback.apply(self, arguments)
    }
    self.on('error', self.callback.bind()) // <-- 就是这一句
    self.on('complete', self.callback.bind(self, null))
  }

而如果没有传入回调函数,那么当请求对象(self.req)收到error事件时,会调用onRequestError方法,并在Request对象上主动发送'error'事件。由于没有处理'error'事件的回调函数,因此在onRequestError方法中抛出的错误,将会成为一个'uncaughtException',一直抛出到node进程这一级(因为对node的事件机制不是很清楚,这一部分只是我的猜测)。如果没有对'uncaughtException'事件进行处理,node进程就会被终止并输出上文所看到的错误信息。

因此,如果要在不传递回调函数的情况下忽略可能发生的网络错误,应当使用下面的代码

'use strict';

const request = require('request');

process.on('uncaughtException', function (error) {
  console.error('catch uncaughtException', error);
});

request('http://127.0.0.1:8000/');

老实说,上面的代码还不如写成

'use strict';

const request = require('request');

request('http://127.0.0.1:8000/', function (error) {
  console.error(error);
});

我的目的只在于请求服务端API而不在意失败,因此最终的代码如下

'use strict';

const request = require('request');

request('http://127.0.0.1:8000/', function () {});