4、Node异步编程的难点

Node借助异步I/O模型及V8高性能引擎,突破单线程的性能瓶颈,让JavaScript在后端达到实用价值,一方面也统一了JavaScript的编程模型。

异步编程的难点

1. 异常处理

通常我们处理异常时,使用try/catch/final语句块进行异常捕获:

try{
  JSON.parse(json)
}catch(e){
  // TODO
}

但是对于异步编程而言并不一定适用,因为异步I/O的实现主要包含两个阶段:提交请求和处理结果。这两个阶段中间有事件循环的调度,两者彼此不关联。异步方法则通常在第一个阶段是提交请求后立即返回,因为异常并不一定发生在这个阶段。


try{
    async(callback)
  }catch(err){
   //TODO
    }

在调用异步方法执行后,callback被存放起来,知道下一个事件循环(Tick)才会取出来执行,所以尝试对异步方法进行try/catch操作只能不做档次事件循环内的异常,对callback执行抛出的异常将无能为力。

Node在处理异常上形成了一种约定,将异常作为回调函数的第一个实参传回,如果为空值则表明没有异常抛出:

async(function(err,results){
   //TODO
});

我们自行编写的异常方法上,也需要遵循这样一些原则:

原则一: 必须执行调用者传入的回调函数;
原则二: 正确传递回异常提供调用者判断;
示例如下:
var async = function (callback) {
    process.nextTick(function () {
        var results = something;
        if (error) {
            return callback(error)
        }
        callback(null, results)
    });
};

在异步的方法编写中,另一个容易犯的错误是对用户传递的回电函数进行异常捕获,如下:

try {
    req.body = JSON.parse(buf, option.reviver);
    callback();
} catch (err) {
    err.body = buf;
    err.status = 400;
    callback(err);
}

上面代码中,本意是捕获 JSON.parse中可能出现的异常,但是当callback()抛出异常的时候,意味着进入catch中执行,callback()被执行了两次。我们可以把代码改成如下:

try {
    req.body = JSON.parse(buf, option.reviver);
} catch (err) {
    err.body = buf;
    err.status = 400;
    return callback(err);
}
callback();

编写异步方法的时候,我们只需要将异常正确的传递给用户的回调方法即可,无需过多的处理。

2. 函数嵌套过深(回调地狱)

应该是node被人诟病最多的地方,事务中存在多个异步调用的场景比比皆是。

fs.readdir(path.join(_dirname, '..'), function (err, files) {
    files.forEach(function (filename, index) {
        fs.readfile(filename, 'utf8', function (err, file) {
            //TODO  
        });
    });
});

上面的两次操作存在依赖关系,函数嵌套的行为情有可原,这也是异步的优势所在。且看后面怎么解决。

3. 阻塞代码

对于JavaScript而言,没有sleep()这样的线程沉睡功能,唯独能用于演示操作的只有setInterval()setimeout()这两个函数。然人惊讶的是,这两个函数并不能阻塞后续代码的持续执行。所以很多开发者会写出下属这样的代码来实现sleep(1000)的效果:

var start = new Date();
while(new Date() - start < 1000){
   //TODO
}
// 需要阻塞的代码

这段代码会持续占用CPU进行判断,预期真正的线程沉睡相去甚远,破坏了事件循环的调度。由于node单线程的原因,CPU资源全都会用于为这段代码服务,导致其与任何请求都会得不到响应。

4. 多线程编程

我们往往聊JavaScript是单一线程上执行的代码,这是在浏览器中指的是JavaScript执行线程与UI渲染公用的一个线程;在node中,只是没有UI渲染的部分,模型基本相同。

5. 异步转同步

Node上绝大部分的异步API和少量的同步API,偶尔出现的同步需求将会因为没有同步API让我们无所适从。但良好的流程控制,还是能够将逻辑梳理成顺序式的形式。

你可能感兴趣的:(4、Node异步编程的难点)