鍍金池/ 問(wèn)答/HTML/ JS事件循環(huán)問(wèn)題

JS事件循環(huán)問(wèn)題

我的需求:

 我的需求可以簡(jiǎn)單描述為,對(duì)一個(gè)大文件進(jìn)行分片切割上傳。我實(shí)現(xiàn)的思路為,
 對(duì)一個(gè)大文件,按照設(shè)定的chunksSize切分為N = file.size/chunkSize塊,
 然后循環(huán)創(chuàng)建N個(gè)讀流讀取每個(gè)分片的內(nèi)容,然后發(fā)起N個(gè)http.request的Post請(qǐng)求去上傳文件。
 

代碼如下
(說(shuō)明: upload函數(shù)用來(lái)根據(jù)分塊的個(gè)數(shù)n,計(jì)算每塊起始標(biāo)志位和終止標(biāo)識(shí)位,并調(diào)用senddataPromise函數(shù)對(duì)每片進(jìn)行操作)

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; // 因讀取時(shí)包含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函數(shù)是對(duì)第i塊分片創(chuàng)建讀流讀取內(nèi)容,并調(diào)用doapost函數(shù)發(fā)送到后端

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結(jié)束")
            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函數(shù)發(fā)起post請(qǐng)求發(fā)送分片數(shù)據(jù)

function  doapost(data) {
    return new Promise(function (resolve,reject) {
        let i = data.n;
        console.log("第"+i+"份請(qǐng)求準(zhǔn)備發(fā)出")
        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+"份請(qǐng)求返回?cái)?shù)據(jù)")
            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+"份請(qǐng)求已發(fā)出")
    })
}

我的問(wèn)題:

  按照正常的思路,因?yàn)樽x取文件內(nèi)容為異步操作,后面發(fā)送請(qǐng)求也為異步操作,所以
  也就是說(shuō)會(huì)出現(xiàn)對(duì)于n個(gè)分片,讀取數(shù)據(jù)已經(jīng)讀取了p片,并且已經(jīng)有q(**q < p**)片
  已經(jīng)完成上傳完成返回?cái)?shù)據(jù)的情況,但是現(xiàn)在問(wèn)題是,***發(fā)現(xiàn)并沒(méi)有分片上傳完返回?cái)?shù)據(jù)的
  情況出現(xiàn),都是在n個(gè)分片讀取完成后,才開始統(tǒng)一執(zhí)行分片內(nèi)容上傳操作***
  圖片如下:(由于圖片無(wú)法上傳,我把程序運(yù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結(jié)束
第0份請(qǐng)求準(zhǔn)備發(fā)出
第0份請(qǐng)求已發(fā)出
第1塊 JSON開始
第1塊 JSON結(jié)束
第1份請(qǐng)求準(zhǔn)備發(fā)出
第1份請(qǐng)求已發(fā)出
第2塊 JSON開始
第2塊 JSON結(jié)束
第2份請(qǐng)求準(zhǔn)備發(fā)出
第2份請(qǐng)求已發(fā)出
第3塊 JSON開始
第3塊 JSON結(jié)束
第3份請(qǐng)求準(zhǔn)備發(fā)出
第3份請(qǐng)求已發(fā)出
第5塊 JSON開始
第5塊 JSON結(jié)束
第5份請(qǐng)求準(zhǔn)備發(fā)出
第5份請(qǐng)求已發(fā)出
第4塊 JSON開始
第4塊 JSON結(jié)束
第4份請(qǐng)求準(zhǔn)備發(fā)出
第4份請(qǐng)求已發(fā)出
第6塊 JSON開始
第6塊 JSON結(jié)束
第6份請(qǐng)求準(zhǔn)備發(fā)出
第6份請(qǐng)求已發(fā)出
第8塊 JSON開始
第8塊 JSON結(jié)束
第8份請(qǐng)求準(zhǔn)備發(fā)出
第8份請(qǐng)求已發(fā)出
第7塊 JSON開始
第7塊 JSON結(jié)束
第7份請(qǐng)求準(zhǔn)備發(fā)出
第7份請(qǐng)求已發(fā)出
第8份請(qǐng)求返回?cái)?shù)據(jù)
moran999
第4份請(qǐng)求返回?cái)?shù)據(jù)
moran999
第6份請(qǐng)求返回?cái)?shù)據(jù)
moran999
第1份請(qǐng)求返回?cái)?shù)據(jù)
moran999
第2份請(qǐng)求返回?cái)?shù)據(jù)
moran999
第0份請(qǐng)求返回?cái)?shù)據(jù)
moran999
第3份請(qǐng)求返回?cái)?shù)據(jù)
moran999
第7份請(qǐng)求返回?cái)?shù)據(jù)
moran999
第5份請(qǐng)求返回?cái)?shù)據(jù)
moran999
[ 'end', 'end', 'end', 'end', 'end', 'end', 'end', 'end', 'end' ]
all done
  可以看到其POST數(shù)據(jù)的發(fā)出并不是和讀流無(wú)關(guān)的,即任何一個(gè)POST都不會(huì)發(fā)出,
  直到到所有的讀流讀取完數(shù)據(jù),想問(wèn)一下各位碼友是什么原因尼??因?yàn)檎?  理解下當(dāng)?shù)趇個(gè)讀流讀的時(shí)候,前面已經(jīng)讀取完內(nèi)容的讀流完全可以進(jìn)行post操作
  了啊,但實(shí)際上并沒(méi)有。

  之所以會(huì)問(wèn)這個(gè)問(wèn)題是因?yàn)楫?dāng)我輸入的文件比較大時(shí),他執(zhí)行到《第12塊 JSON開始時(shí),
  就內(nèi)存溢出了》,而如果程序是post不用等待所有的讀流讀完時(shí),當(dāng)有一部分post執(zhí)行完之后,其對(duì)應(yīng)的數(shù)據(jù)就被回收了,釋放相應(yīng)的內(nèi)存,就不會(huì)出現(xiàn)內(nèi)存溢出了。
  
  
  
回答
編輯回答
淺時(shí)光

不是呀,這個(gè)是正常的呀,你看 第1份 post已經(jīng)發(fā)出 是在第2塊JSON開始之前呀,post發(fā)出和 http.requset并列,JSON開始是讀完文件之后,所以發(fā)送數(shù)據(jù)是在 文件讀完之前,只是讀后面塊的時(shí)候前面的請(qǐng)求還沒(méi)有執(zhí)行完,所以并沒(méi)有 第n份數(shù)據(jù)返回。 網(wǎng)絡(luò)延時(shí)比讀取文件大的多,所以文件讀完之前不會(huì)返回呀。

感覺(jué),這段代碼不至于在 12 的時(shí)候內(nèi)存溢出呀。

nodejs 上傳文件的話用 管道 更好一點(diǎn)兒吧。

2018年4月16日 10:23
編輯回答
陌璃

@zonxin 所說(shuō),代碼和你設(shè)想的大致相同。

補(bǔ)充為什么第12塊就已經(jīng)溢出

  1. let chunkjson = JSON.stringify(chunk);把本來(lái)1MBuffer轉(zhuǎn)成和數(shù)組樣式的字符串[104,101,...],內(nèi)存漲了x倍(就不用說(shuō)后面還有個(gè)queryString.stringify)。
  2. nodejs內(nèi)存受V8限制(64位系統(tǒng)下約為1.4GB,32位系統(tǒng)下約為0.7GB,Buffer除外),而樓主剛好把Buffer轉(zhuǎn)成string。
  3. 沒(méi)用pipe(轉(zhuǎn)成string也沒(méi)法用),以至于發(fā)送過(guò)的字節(jié)還保留在內(nèi)存,直到完整是字符串發(fā)送完,而完整的一塊缺有卻有x*1M。
2017年10月27日 05:41
編輯回答
心夠野
  • 網(wǎng)絡(luò)延遲肯定是大于你本地的io速率的,所以請(qǐng)求在所有文件分塊被讀完之前沒(méi)有返回是正常的
  • 你這里使用了readable stream,但是每一次都會(huì)創(chuàng)建一個(gè)新的steram并且還會(huì)完整地讀這個(gè)大文件,我覺(jué)的在第一點(diǎn)的基礎(chǔ)上,這是造成溢出的主要原因
  • 分片上傳在nodejs做的話,使用stream的管道做組合好一些吧,大體思路就是先創(chuàng)建一個(gè)readable stream,之后在pipe中傳入分片和上傳邏輯,這么做肯定比使用stream.on('data')要好,因?yàn)槟氵€可以和別的stream對(duì)象做組合,比如http的res等等。
  • nodejs中的readable stream本身就是一種分片讀取技術(shù),所以在其之上增加額外的變換邏輯就行了
2017年5月25日 08:47