JS事件循环问题

我的需求:

 我的需求可以简单描述为,对一个大文件进行分片切割上传。我实现的思路为,
 对一个大文件,按照设定的chunksSize切分为N = file.size/chunkSize块,
 然后循环创建N个读流读取每个分片的内容,然后发起N个http.request的Post请求去上传文件。
 

代码如下
(说明: upload函数用来根据分块的个数n,计算每块起始标志位和终止标识位,并调用senddataPromise函数对每片进行操作)

function  upload(username,filepath,file_id,filelength,n,alreadychunks,chunkSize) {
    return new Promise(function (resolve,reject) {
            var start = 0,end = 0;
            var promiseall = [];
            for (let curindex = 0;curindex < n;curindex++) {
                if(filelength - start <= chunkSize) {
                    end  =  filelength - 1;
                }else {
                    end = start+chunkSize - 1; // 因读取时包含start和end位
                }
                if(alreadychunks.indexOf(curindex) == -1) {
                    let options = {
                        flags: 'r',
                        highWaterMark: chunkSize,
                        start: start,
                        end: end
                    };
                    promiseall.push(senddataPromise(filepath,options,username,file_id,curindex,end-start+1));
                }
                start = end + 1;
            }
            let timer = setInterval(() => {
                if(promiseall.length == n) {
                    clearInterval(timer);
                    Promise.all(promiseall).then(values=>{
                        console.log(values);
                        console.log("all done");
                        resolve(true)
                    }).catch(err => {
                        console.log(err);
                        reject(err);
                    })
                }
            },500)
    })
}

senddataPromise函数是对第i块分片创建读流读取内容,并调用doapost函数发送到后端

function senddataPromise(path,options,username,summary,curindex,length) {
    return new Promise(function (resolve,reject) {
        let readSteam = fs.createReadStream(path,options);
        readSteam.on("data",(chunk) => {
            console.log("第"+curindex+"块 JSON开始")
            let chunkjson = JSON.stringify(chunk);
            console.log("第"+curindex+"块 JSON结束")
            let tempcell = {
                data: chunkjson,
                n: curindex,
                file_id: summary,
                username: username,
                length: length
            };
            chunk = null;
            chunkjson = null;
            doapost(tempcell).then(values=>{
                resolve(values)
            }).catch(err=>{
                reject(err);
            });
        })
    })
}

doapost函数发起post请求发送分片数据

function  doapost(data) {
    return new Promise(function (resolve,reject) {
        let i = data.n;
        console.log("第"+i+"份请求准备发出")
        let contents = queryString.stringify(data);
        data = null;
        let options = {
            host: "localhost",
            path: "/nodepost/",
            port: 8000,
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': contents.length
            }
        };
        let req = http.request(options, function (res) {
            console.log("第"+i+"份请求返回数据")
            res.on("data", function (chunk) {
                console.log(chunk.toString());
            });
            res.on("end", function (d) {
                resolve("end");
            });
            res.on("error", function (e) {
                reject(e);
            })
        });
        req.write(contents);
        req.end();
        contents = null;
        console.log("第"+i+"份请求已发出")
    })
}

我的问题:

  按照正常的思路,因为读取文件内容为异步操作,后面发送请求也为异步操作,所以
  也就是说会出现对于n个分片,读取数据已经读取了p片,并且已经有q(**q < p**)片
  已经完成上传完成返回数据的情况,但是现在问题是,***发现并没有分片上传完返回数据的
  情况出现,都是在n个分片读取完成后,才开始统一执行分片内容上传操作***
  图片如下:(由于图片无法上传,我把程序运输出拷贝一下)
{ 
 kind: 'upload',
username: 'moran999',
filepath: 'F:/my_upload_test/NowTest.pdf',
file_id: '-196987878-472217752177633040957425519',
alreadychunks: [],
chunkSize: 1048576,
n: 9 }
第0块 JSON开始
第0块 JSON结束
第0份请求准备发出
第0份请求已发出
第1块 JSON开始
第1块 JSON结束
第1份请求准备发出
第1份请求已发出
第2块 JSON开始
第2块 JSON结束
第2份请求准备发出
第2份请求已发出
第3块 JSON开始
第3块 JSON结束
第3份请求准备发出
第3份请求已发出
第5块 JSON开始
第5块 JSON结束
第5份请求准备发出
第5份请求已发出
第4块 JSON开始
第4块 JSON结束
第4份请求准备发出
第4份请求已发出
第6块 JSON开始
第6块 JSON结束
第6份请求准备发出
第6份请求已发出
第8块 JSON开始
第8块 JSON结束
第8份请求准备发出
第8份请求已发出
第7块 JSON开始
第7块 JSON结束
第7份请求准备发出
第7份请求已发出
第8份请求返回数据
moran999
第4份请求返回数据
moran999
第6份请求返回数据
moran999
第1份请求返回数据
moran999
第2份请求返回数据
moran999
第0份请求返回数据
moran999
第3份请求返回数据
moran999
第7份请求返回数据
moran999
第5份请求返回数据
moran999
[ 'end', 'end', 'end', 'end', 'end', 'end', 'end', 'end', 'end' ]
all done
  可以看到其POST数据的发出并不是和读流无关的,即任何一个POST都不会发出,
  直到到所有的读流读取完数据,想问一下各位码友是什么原因尼??因为正常
  理解下当第i个读流读的时候,前面已经读取完内容的读流完全可以进行post操作
  了啊,但实际上并没有。

  之所以会问这个问题是因为当我输入的文件比较大时,他执行到《第12块 JSON开始时,
  就内存溢出了》,而如果程序是post不用等待所有的读流读完时,当有一部分post执行完之后,其对应的数据就被回收了,释放相应的内存,就不会出现内存溢出了。
  
  
  
阅读 2.9k
3 个回答

@zonxin 所说,代码和你设想的大致相同。

补充为什么第12块就已经溢出

  1. let chunkjson = JSON.stringify(chunk);把本来1MBuffer转成和数组样式的字符串[104,101,...],内存涨了x倍(就不用说后面还有个queryString.stringify)。
  2. nodejs内存受V8限制(64位系统下约为1.4GB,32位系统下约为0.7GB,Buffer除外),而楼主刚好把Buffer转成string
  3. 没用pipe(转成string也没法用),以至于发送过的字节还保留在内存,直到完整是字符串发送完,而完整的一块缺有却有x*1M

不是呀,这个是正常的呀,你看 第1份 post已经发出 是在第2块JSON开始之前呀,post发出和 http.requset并列,JSON开始是读完文件之后,所以发送数据是在 文件读完之前,只是读后面块的时候前面的请求还没有执行完,所以并没有 第n份数据返回。 网络延时比读取文件大的多,所以文件读完之前不会返回呀。

感觉,这段代码不至于在 12 的时候内存溢出呀。

nodejs 上传文件的话用 管道 更好一点儿吧。

  • 网络延迟肯定是大于你本地的io速率的,所以请求在所有文件分块被读完之前没有返回是正常的
  • 你这里使用了readable stream,但是每一次都会创建一个新的steram并且还会完整地读这个大文件,我觉的在第一点的基础上,这是造成溢出的主要原因
  • 分片上传在nodejs做的话,使用stream的管道做组合好一些吧,大体思路就是先创建一个readable stream,之后在pipe中传入分片和上传逻辑,这么做肯定比使用stream.on('data')要好,因为你还可以和别的stream对象做组合,比如http的res等等。
  • nodejs中的readable stream本身就是一种分片读取技术,所以在其之上增加额外的变换逻辑就行了
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题