NodeJS -- 异步编程

NodeJS最大的卖点--事件机制和异步IO,对开发者并不透明

代码设计模式

异步编程有很多特有的代码设计模式,为了实现同样的功能,使用同步方式和异步方式编写代码会有很大差异,以下举例。
1、函数返回值
使用一个函数的输出作为另一个函数的输入是常见的需求,在同步方式下一般以下述方式编写代码:

var output = fn1(fn2('input'));
// Do something;

在异步方式下,由于函数执行结果不是通过返回值,而是通过回调函数传递,因此一般按以下方式编写代码:

fn2('input', function(output2) {
fn1(output2, function(output1) {
//Do something.
});
});
注:这种方式就是一个函数套一个函数,层级变多了,就很麻烦了
2、遍历数组
在遍历数组时,使用某个函数依次对数据成员做一些处理也是常见的需求,若函数是同步执行,一般以下列方式:

var len = arr.length,
i = 0;
for(;i < len; ++i) {
arr[i] = sync(arr[i]);
}
//All array items have processed.

若是异步执行,以上代码就无法保证循环结束后所有数组成员都处理完毕了,如果数组成员必须一个接一个串行处理,则按以下方式编写代码:

(function next(i, len, callback) {
if(i < len) {
async(arr[i], function(value) {
arr[i] = value;
next(i + 1, len, callback);
});
} else {
callback();
}
}(0, arr.length, function() {
// All array items have processed.
}));

可以看到,在异步函数执行一次并返回执行结果后才传入下一个数组成员并开始下一轮执行,直到所有数组成员处理完毕后,通过回调的方式触发后续代码执行。

若数组成员可以并行处理,但后续代码仍然需要所有数组成员处理完毕后才能执行的话,则异步代码调整成以下形式:

(function(i, len, count, callback) {
for(;i < len; ++i) {
(function(i) {
async(arr[i], function(value) {
arr[i] = value;
if(++count === len) {
callback();
}
});
}(i));
}
}(0, arr.length, 0, function() {
//All array items have processed.
}));

以上代码并行处理所有成员,并通过计数器变量来判断什么时候所有数组成员都处理完毕了

3、异常处理
JS自身提供的异常捕获和处理机制 -- try...catch...,只能用于同步执行的代码。
但,由于异步函数会打断代码执行路径,异步函数执行过程中以及执行之后产生的异常冒泡到执行路径被打断的位置时,如果一直没有遇到try语句,就作为一个全局异常抛出。例:

function async(fn, callback) {
// Code execution path breaks here.
setTimeout(function() {
callback(fn());
}, 0);
} try {
async(null, function(data) {
// Do something.
});
} catch(err) {
console.log('Error: %s', err.message);
}
-----------------------Console----------------------------
E:\Language\Javascript\NodeJS\try_catch.js:25
callback(fn());
^ TypeError: fn is not a function
at null._onTimeout (E:\Language\Javascript\NodeJS\try_catch.js:25:18)
at Timer.listOnTimeout (timers.js:92:15)

因为代码执行路径被打断了,我们就需要在异常冒泡到断点之前用try语句把异常捕获注,并通过回调函数传递被捕获的异常,改造:

function async(fn, callback) {
// Code execution path breaks here.
setTimeout(function() {
try {
callback(null, fn());
} catch(err) {
callback(err);
}
}, 0);
} async(null, function(err, data) {
if(err) {
console.log('Error: %s', err.message);
} else {
// Do something
}
})
----------------------Console----------------------
Error: fn is not a function
异常再次被捕获住。
在NodeJS中,几乎所有的异步API都按照以上方式设计,回调函数中第一个参数都是err,因此我们在编写自己的异步函数时,也可以按照这种方式来处理异常,与NodeJS的设计风格保持一致。
但,我们的代码通常都是做一些事情,调用一个函数,然后再做一些事情,调用一个函数,若使用同步代码,只需要在入口点写一个try...catch...语句即可捕获所有冒泡的异常,若使用异步代码,那就呵呵了,就像下面,只调用三次异步函数:

function main(callback) {
// Do something.
asyncA(function(err, data) {
if(err) {
callback(err);
} else {
// Do something.
asyncB(function(err, data) {
if(err) {
callback(err);
} else {
// Do something.
asyncC(function(err, data) {
if(err) {
callback(err);
} else {
// Do something.
callback(null);
}
});
}
});
}
});
}
main(function(err) {
if(err) {
// Deal with exception.
}
});

回调函数已经让代码变得复杂了,而异步之下的异常处理更加剧了代码的复杂度,幸好,NodeJS提供了一些解决方案。

NodeJS提供了domain模块,可以简化异步代码的异常处理。
域,简单讲就是一个JS运行环境,在一个运行环境中,如果一个异常没有被捕获,将作为一个全局异常抛出,NodeJS通过process对象提供了捕获全局异常的方法,例:

process.on('uncaughtException', function(err) {
console.log('Error: %s', err.message);
}); setTimeout(function(fn) {
fn();
});
------------------------Console--------------------
Error: fn is not a function
全局异常已被捕获,但大多数异常我们希望尽早捕获,并根据结果决定代码执行路径。
用HTTP服务器代码作例子:

function async(req, callback) {
// Do something.
asyncA(req, function(err, data) {
if(err) {
callback(err);
} else {
// Do something.
asyncB(req, function(err, data) {
if(err) {
callback(err);
} else {
// Do something.
asyncC(req, function(err, data) {
if(err) {
callback(err);
} else {
// Do something.
callback(null, data);
}
});
}
});
}
});
} http.createServer(function(req, res) {
async(req, function(err, data) {
if(err) {
res.writeHead(500);
res.end();
} else {
res.writeHead(200);
res.end();
}
});
});

以上将请求对象交给异步函数处理,再根据处理结果返回响应,这里采用了使用回调函数传递异常的方案,因此async函数内部若再多几个异步函数调用的话,代码就更难看了,为了让代码好看点,可以在没处理一个请求时,使用domain模块创建一个子域,在子域内运行的代码可以随意抛出异常,而这些异常可以通过子域对象的error事件统一捕获,改造:

function async(req, callback) {
// Do something.
asyncA(req, function(data) {
// Do something.
asyncB(req, function(data) {
// Do something.
asyncC(req, function(data) {
// Do something.
callback(data);
});
});
});
} http.createServer(function(req, res) {
var d = domain.create(); d.on('error', function() {
res.writeHead(500);
res.end();
}); d.run(function() {
async(req, function(data) {
res.writeHead(200);
res.end(data);
});
});
});
注意:
无论是通过process对象的uncaughtException事件捕获到全局异常,还是通过子域对象的error事件捕获到子域异常,在NodeJS官方文档均强烈建议处理完异常后应立即重启程序,而不是让程序继续运行,因为发生异常后程序处于一个不确定运行状态,若不退出,可能会发生严重内存泄漏,也可能表现得很奇怪

注:JS的throw...tyr...catch异常处理机制并不会导致内存泄漏和使程序执行出乎意料,而是因为NodeJS并不是纯粹的JS,NodeJS里大量的API内部是用C/C++实现的,因此NodeJS程序运行过程中,代码执行路径穿梭于JS引擎内外部,而JS异常抛出机制可能打断正常代码的执行流程,导致C/C++部分的代码表现异常,进而导致内存泄漏。

因此,使用uncaughtException或domain捕获异常,代码执行路径里涉及到了C/C++部分的代码时,若不能确定是否会导致内存泄漏等问题,最好在处理完异常后重启程序比较妥当,而使用try语句捕获的异常一般是JS本身的异常,不用担心上述问题

NodeJS学习之异步编程的更多相关文章

  1. nodejs学习笔记 —— 异步编程解决方案

    在js或者node编程中,由于异步的频繁和广度使用,使得回调和嵌套的深度导致编程的体验遇到一些挑战,如果写出优雅和好看的代码,本文主要针对异步编程的主流方案做一些总结 1.事件发布/订阅模式 事件监听 ...

  2. 深入理解nodejs中的异步编程

    目录 简介 同步异步和阻塞非阻塞 javascript中的回调 回调函数的错误处理 回调地狱 ES6中的Promise 什么是Promise Promise的特点 Promise的优点 Promise ...

  3. nodejs之async异步编程

    1.什么是异步编程? 异步编程是指由于异步I/O等因素,无法同步获得执行结果时, 在回调函数中进行下一步操作的代码编写风格,常见的如setTimeout函数.ajax请求等等. 示例:  for (v ...

  4. 《C#并发编程经典实例》学习笔记—异步编程关键字 Async和Await

    C# 5.0 推出async和await,最早是.NET Framework 4.5引入,可以在Visual Studio 2012使用.在此之前的异步编程实现难度较高,async使异步编程的实现变得 ...

  5. 09-Node.js学习笔记-异步编程

    同步API,异步API 同步API:只有当前API执行完成后,才能继续执行下一个API console.log('before'); console.log('after'); 异步API:当前API ...

  6. Java8学习之异步编程

    异步编程 所谓异步其实就是实现一个无需等待被调用函数的返回值而让操作继续运行的方法 创建任务并执行任务 无参创建 CompletableFuture<String> noArgsFutur ...

  7. 学习Promise异步编程

    JavaScript引擎建立在单线程事件循环的概念上.单线程( Single-threaded )意味着同一时刻只能执行一段代码.所以引擎无须留意那些"可能"运行的代码.代码会被放 ...

  8. 借助Q.js学习javascript异步编程。

    金字塔式 //add1 function step1(n, callback) { setTimeout(function(){ callback.call(null, n + 1); }, 100) ...

  9. nodejs 学习三 异步和同步

    同步函数 for (let i = 0; i < 10; i ++) { setTimeout(() => { console.log(`${i} ______ ${new Date}`) ...

随机推荐

  1. Java常见排序算法之冒泡排序

    在学习算法的过程中,我们难免会接触很多和排序相关的算法.总而言之,对于任何编程人员来说,基本的排序算法是必须要掌握的. 从今天开始,我们将要进行基本的排序算法的讲解.Are you ready?Let ...

  2. 【M30】代理类

    1.考虑二维数组,在栈上分配,必须在编译时确定大小,也就是大小是常量.另外一点,C++不支持在堆上分配二维数组.怎么解决这个问题? 二维数组可以看成,一维数组的数组.因此,可以使用代理类,Array2 ...

  3. PS-文字如何竖排版

    单击文字输入工具“T”按钮,点住鼠标左键不要松手,会在“T”按钮的右边显示出其它形式的文字工具,拖动鼠标指向“直排文字”工具就可以了.

  4. IOS 7 Study - UIDatePicker

    Picking the Date and Time with UIDatePicker effect: 1. declaring a property of type UIDatePicker #im ...

  5. 【android开发】Android防止内存溢出浅析

    近期项目做得差点儿相同了,測试出现了一些问题,当中一个就是内存溢出问题,在三星手机上測试最easy出现内存溢出,在其它手机上,比方华为就没有发生,也是比較郁闷.这个问题在之前的公司,做项目时也遇到过, ...

  6. Why the framework uses ruby instead of perl?[转]

    During the development of the framework, the one recurring question that the Metasploit staff was co ...

  7. cocos2d-html5 笔记5: 事件

    在cocos2d里面,通过Node的方式,将整个场景以及里面的object给组织起来,这样很容易画了,从root node开始遍历,把整棵树画出来就是了. 剩下就是animation,timer, 还 ...

  8. THD 变量存入threads中

    http://blog.csdn.net/gapaul/article/details/12047497 http://ourmysql.com/archives/930

  9. C#对Windows服务的操作

    一.安装服务: private void InstallService(IDictionary stateSaver, string filepath) { try { System.ServiceP ...

  10. SVM多分类

    http://www.matlabsky.com/thread-9471-1-1.htmlSVM算法最初是为二值分类问题设计的,当处理多类问题时,就需要构造合适的多类分类器.目前,构造SVM多类分类器 ...