什么是 node 的异步编程

241 阅读3分钟

前言

此文承接上文 什么是 nodejs 的异步 I/O,继续讲述自己阅读了深入浅出 nodejs 的相关章节后的理解,这次是第四章 异步编程

函数式编程

高阶函数

高级函数可以将函数当成输入或返回值

偏函数用法

创建一个有预置变量或参数的函数

var isType = function (type) { 
    return function (obj) {
        return toString.call(obj) == '[object ' + type + ']'; };
};

var isString = isType('String'); 
var isFunction = isType('Function');

如上图的例子,先创建一个 isType 函数,里面返回另一个函数可以通过传入的数据来判断是否符合一开始传入的预置变量 type。

异步编程的优势与难点

异步的使用带来的优势,其实上一篇已经有讲了,node 使用单线程非阻塞 I/O 让资源得到更好的利用。
但带来的也并非全是好处,其中也有一些难点。

  • 异常的处理
  • 函数嵌套过深
  • 没有 sleep 这样的线程沉睡功能
    当然可以用 setTimeout 代替
  • 多线程编程不方便
    在浏览器中有 Web Worker,node 也有类似的 child_processcluster 模块。

异步编程的解决方法

事件/发布订阅模式

这个其实比较好理解,在执行异步时,监听一个该异步的事件,当异步返回时,触发该事件即可,当然同时可以监听相关的 error 事件,这样可以解决上面提到的异常的处理和函数嵌套过深的问题。

var after = function (times, callback) {
    var count = 0, results = {};
    return function (key, value) {
        results[key] = value; count++;
        if (count === times) {
            callback(results);
        }
    };
};

var emitter = new events.Emitter(); // 基于 node events 模块
var done = after(3, render); // render 是异步执行完后传递给的函数

emitter.on("done", done); 
emitter.on("error", function(err) {
    render(err); // 写的比较粗糙 只要一个报错就直接返回报错
}); 

fs.readFile(template_path, "utf8", function (err, template) { 
    if (err) {
       emitter.emit("error", err) 
    }
    emitter.emit("done", "template", template);
});

db.query(sql, function (err, data) {
    if (err) {
       emitter.emit("error", err) 
    }
    emitter.emit("done", "data", data); 
});

l10n.get(function (err, resources) { 
    if (err) {
       emitter.emit("error", err) 
    }
    emitter.emit("done", "resources", resources);
});

还有一些额外要提的点。
node 对事件监听的有初始的限制,如果对一个事件添加了超过十个监听器,将会得到一条警告,当然这个可以通过设置 emitter.setMaxListeners,把十条的限制提高,设置为 0 将取消该限制。

雪崩问题,这个问题,前端应该不会遇到,一般都是后端常见,就是在高访问量、大并发的情况下,缓存失效的问题,可以通过事件队列加状态锁解决。

Promise/Deferred 模式

我想,对于前端来说,Promise 不用过多介绍,那么这个跟 Promise 有啥区别呢,多了一个中间状态,then 方法是这样的

then(fulfilledHandler, errorHandler, progressHandler)

除此以外,其实跟我们理解的 Promise 没什么区别,考虑到这本书的年代离现在是有丶久的,也是可以理解。

流程控制库

主要介绍了 asyncstep 两个库,通过库里的方法,让异步任务串行、并行以及有依赖关系(类似有 a、b、c 三个异步任务,c 会需要 a 的结果作为入参)来执行,可以看一个有依赖关系的例子。
下面这个嵌套逻辑大家应该都看得懂。

fs.readFile('file1.txt', 'utf-8', function (err, data1) { 
    // data1 => file1
    if (err) {
        return callback(err); 
    }
    fs.readFile(data1, 'utf-8', function (err, data2) {
        // data2 => file2
        if (err) {
            return callback(err);
        }
        fs.readFile(data2, 'utf-8', function (err, data3) {
            // data3 => file3
            if (err) {
                return callback(err);
            }
            callback(null, data3); 
        });
    }); 
});

使用 async 库之后。

async.waterfall([ 
    function (callback) {
        fs.readFile('file1.txt', 'utf-8', function (err, content) {
            callback(err, content);
        });
    },

    function (arg1, callback) {
        // arg1 => file1
        fs.readFile(arg1, 'utf-8', function (err, content) {
            callback(err, content); 
        });

    },
    function(arg1, callback){
        // arg1 => file2
        fs.readFile(arg1, 'utf-8', function (err, content) {
            callback(err, content); 
         });
    }
], function (err, result) {
    // result => file3
});

异步的并发控制

其实也是跟前面说的解决雪崩问题类似,通过队列,来解决大量异步的并发。

总结

这个章节里介绍的异步编程,其实在今天看来是有丶过时了,原因也是之前说过的,这本书距离现在有也有快十年了,但是我觉得思路还是不错的,例如雪崩问题,和 Promise/Deferred 模式,我觉得是对我有帮助的,好了,就到这了~