Node.js 回调与循环的陷阱

来源:互联网 发布:卖耐克阿迪折扣店淘宝 编辑:程序博客网 时间:2024/05/01 23:55

1、回调与循环的陷阱解析

今天拜读BYVoid大神的《Node.js开发指南》小有收获,特地写篇博文出来给大家伙分享一下。

文中提到了一个Node.js的回调与循环的陷阱。
下面是书上的源码

var fs = require('fs');var files = ['a.txt', 'b.txt', 'c.txt'];for (var i = 0; i < files.length; i++) {    fs.readFile(files[i], 'utf-8', function(err, contents) {        console.log(files[i] + ': ' + contents);    });}

这三个文件的内容分别是 AAA,BBB,CCC
下面是实际运行起来的输出结果

undefined: AAAundefined: BBBundefined: CCC

很显然这跟我们想要的结果有很大的偏差,那到底是什么造成这种偏差呢。
可以看到上面的文件名都是undefined ,也可以说作根本没有获取到文件名,那为什么没有获取到文件名却可以获取到文件里的内容呢。

在书中作者只简单的讲解了为什么是Undefined,却没有说明为什么能够获取到文本的内容。这里我通过结合node 的异步机制给大家讲解一下,希望大家也有所收获。

为什么没有获取到文件名

首先我们必须思考第一个问题,为什么没有获取到文件名。
结合书上的代码,我直接贴出来。

var fs = require('fs');var files = ['a.txt', 'b.txt', 'c.txt'];for (var i = 0; i < files.length; i++) {    fs.readFile(files[i], 'utf-8', function(err, contents) {    console.log(files);    console.log(i);    console.log(files[i]);});}

同时打印数组,数组下标,数组的元素,下面是我们得到的结果

[ 'a.txt', 'b.txt', 'c.txt' ]3undefined[ 'a.txt', 'b.txt', 'c.txt' ]3undefined[ 'a.txt', 'b.txt', 'c.txt' ]3undefined

可以看见三次打印出来的结果都是3,这说明当打印数组下标的时候,整个循环已经结束了,由于js没有{}作用域,所以i是一直都没有被释放,因此我们打印除了3 次 3!因此也就可以解释为什么我们获取不到文件名了,因为数组越界了。
而之所以会数组越界,是因为node本身的异步机制起的作用。
在Node里,所有对文件的读取写入操作都有sync, async两个版本,上面我们用了async ,因此我们读取文件是一个异步事件。
在进入for循环之后,node将异步事件带到另一个地方去处理,并将处理完的结果进入事件队列,由事件循环取出,最后放入执行栈,调用回调函数。
便于理解,这里我又要放一次这张图了

这里写图片描述

所以,其实调用回调函数的时候,我们的同步事件已经全部执行完毕,而i一直没有被释放,所以打印出来是3。

为什么在没有获取到文件名的情况下却能获取到文件的实际内容

第一个问题解决了,我们就必须要解决第二个问题,为什么在没有获取到文件名的情况下,却能够获取到文件内容?

我相信答案已经显而易见了,这都是由node的异步机制决定的。
为什么这么说,下面我就给大家一行一行解释一下。
我先把上面的源码粘下来,方便大家看

var fs = require('fs');var files = ['a.txt', 'b.txt', 'c.txt'];for (var i = 0; i < files.length; i++) {    fs.readFile(files[i], 'utf-8', function(err, contents){        console.log(files[i] + ': ' + contents);    });}

从进入for循环开始,主线程就遇到了一个异步事件,解释器将它带去别的地方进行处理,注意这个操作!解释器只是将它带去别的地方处理,因为for指定循环三次,所以第一次的时候i的值是0,被获取到传入files[i]当中,所以我们这时其实是获取到了文件名,接下来它就被带去了别的地方进行处理,当然,整个操作中我理所当然的获取到了文件的实际内容,并且循环了三次,三次我们都获取到了。

fs.readFile(files[i], 'utf-8', function(err, contents){}); 

接下来这个就厉害了

在回调函数中的函数体中,我们打印了获取到了content,好像并没有什么错,可这才是陷阱的开始

console.log(files[i] + ': ' + contents);

我们根据文件名称获取到了文件实际的内容,因此获取到的内容会进入事件队列,待执行栈空闲的时候,事件循环把获取到的内容取出来,然后最后一步调用回调函数。

但是在我们调用回调函数的时候,整个循环都已经结束了,我们只能获取到循环过后的i,即3,但实际的files[3]已经数组下标越界,因此,我们只能打印出 undefined。

怎么处理这样的陷阱

经过了这么一番的折腾我们必须要来处理它啊。下面给出处理的方法:

同步解决

处理的方法有两种,第一种我们直接改成同步读取文件

var fs = require('fs');var files = ['a.txt', 'b.txt', 'c.txt'];var file;for (var i = 0; i < files.length; i++) {    file = readFileSync(files[i],"utf-8");    console.log(files[i] + ': ' + contents);}

闭包解决

第二种就是闭包
下面给出具体实现

var fs = require('fs');var files = ['a.txt', 'b.txt', 'c.txt'];for (var i = 0; i < files.length; i++) {    (function(i) {        fs.readFile(files[i], 'utf-8', function(err, contents) {            console.log(files[i] + ': ' + contents);        });    })(i);}

这里用了函数立即执行,将异步外再包裹住一个匿名函数,然后立即执行并传入i,由于回调函数还没执行,i的值不会在匿名函数内被释放,因此,我们便可以轻松的获取到文件名,而不用担心undefined的问题。

其实还有很多种可以绕过陷阱的方法,比如你还可以用一个数组暂时存储住i的值,回调的时候再在里面去取,这样的话,也能够轻松获取到。

0 0
原创粉丝点击