簡體   English   中英

使用等待和請求承諾的問題

[英]Issue with using await and request-promise

作為背景,我有一個nodeJs控制器,該控制器可以從3方網站上下載圖像。 為此,我需要一個Key和一個臨時令牌。 問題是有時令牌在我下載圖像之前已過期,在這種情況下,我的下載將是0字節大小的jpg。 因此,我沒有一堆0字節的文件,而是要在下載后檢查文件的大小,如果t為0,則刪除。 我正在使用請求承諾來等待將文件寫入系統的完成,但似乎無法正常工作。 在我看來,下載已完成,但文件尚未通過管道保存到驅動器中。 如果我去休息幾毫秒,一切正常。 因此,在檢查文件大小之前,如何確定文件是否已下載並保存(通過管道)到硬盤驅動器?

這是我當前代碼的一小段

const getImages = async (key, exk, size, pic_count, apn, mls ) => {
    let i;
    let fullPath = "";
    let fileName;
    const newPath = "./images/" + folderBySize(size);
    if (!fs.existsSync(newPath)) {fse.ensureDirSync(newPath); }
    for (i = 0; i < pic_count; i++) {
        fileName = uuid() + ".jpg";

        console.log("File Name : " + fileName);
        fullPath = newPath + fileName;

        console.log("Checking File: " + fullPath);

        const response = await rp.get(imageUrl + key + "&TableID=50&Type=1&Size=" + size + "&exk=" + exk + "&Number=" + i).pipe(fs.createWriteStream(fullPath));

        //await resolveAfter2Seconds(1)
        await getFilesizeInBytes(fullPath);

        console.log("done");
        }
  }

  const getFilesizeInBytes = async (filename) => {
    try {
    const stats = fs.statSync(filename);
    const fileSizeInBytes = stats.size;

    console.log("File: " + filename + " Size: " + fileSizeInBytes);
    if (fileSizeInBytes === 0) {

    console.log("Delete File: " + filename );
    fs.unlink( filename, (err) => {
      if (err) {
        console.log(err);
      }
    })
    }
    } catch (error) {
       console.log(error);
    }
  };

  getImages(316868841, "2897223e91f137e03714ec2bbce6212c", 2 , 5, 12345678, "OC123456" );

當您等待與您要等待的異步操作完全相關的諾言時, await僅會做一些有用的事情。 您在代碼中有很多地方,等待的不是承諾,因此它不必等待基礎異步操作完成。 這弄亂了代碼的時間。

首先一點背景...

async函數使您可以在返回諾言的操作上使用await 但是, async函數不包含關於並非基於承諾的異步操作的不可思議的手段。 因此,當您在getFilesizeInBytes()執行fs.unlink()時,這只是getFilesizeInBytes()函數不等待的隨機異步操作。 同樣, getFilesizeInBytes()沒有返回值,因此從該async函數返回的promise具有undefined解析值。 因此,當您await getFilesizeInBytes(fullPath) ,將得到一個undefined值。

因此,現在,您的getFilesizeInBytes()函數將在fs.unlink()操作完成之前返回,並返回一個可解析為undefined的promise。

為了進行正確的異步設計,我建議您將getFilesizeInBytes()更改為此:

const fsp = require("fs").promises;

const getFilesizeInBytes = async (filename) => {
    const stats = await fsp.stat(filename);
    const fileSizeInBytes = stats.size;

    console.log("File: " + filename + " Size: " + fileSizeInBytes);
    if (fileSizeInBytes === 0) {
        console.log("Delete File: " + filename );
        await fsp.unlink(filename);
    }
    return fileSizeInBytes;
};

它使用內置在較新版本的node.js中的fs模塊promise API,現在將正確等待(解決返回的promise之前),直到函數中的所有操作都完成,並且還將返回fileSizeInBytes。


此外,執行此操作時:

const response = await rp.get(imageUrl + key + "&TableID=50&Type=1&Size=" + size + "&exk=" + exk + "&Number=" + i).pipe(fs.createWriteStream(fullPath));

您實際上是在這樣做:

const response = await rp.get(...).pipe(...);

但是, .pipe()不會返回承諾。 它返回流。 因此,您正在等待沒有任何用處的流。 因此,您的await不會等待所有內容完成下載並保存到磁盤。

request-promise庫包含特別建議不要將.pipe()與request-promise庫一起使用。 它說對.pipe()使用常規請求庫。 要解決您的特定問題,您可能必須自己設置.pipe()或者僅使用流中的適當事件來知道何時完成以及何時應繼續執行其余代碼。


我不確定最好的方法是保證流的結束。 我可能需要做更多的研究。 這是一種監視寫流上的closeerror事件以在流完成時解析/拒絕承諾的方式。 看起來新的fs.promises接口尚未涵蓋流中這種類型的fs.promises使用。

const request = require('request');

const getImages = async (key, exk, size, pic_count, apn, mls ) => {
    let i;
    let fullPath = "";
    let fileName;
    const newPath = "./images/" + folderBySize(size);
    if (!fs.existsSync(newPath)) {fse.ensureDirSync(newPath); }
    for (i = 0; i < pic_count; i++) {
        fileName = uuid() + ".jpg";

        console.log("File Name : " + fileName);
        fullPath = newPath + fileName;

        console.log("Checking File: " + fullPath);

        await new Promise((resolve, reject) => {
            let writeStream = fs.createWriteStream(fullPath);
            writestream.on('close', resolve).on('error', reject);
            request.get(imageUrl + key + "&TableID=50&Type=1&Size=" + size + "&exk=" + exk + "&Number=" + i)
              .on('error', reject).pipe(writeStream);

        });


        //await resolveAfter2Seconds(1)
        await getFilesizeInBytes(fullPath);

        console.log("done");
     }
}

您可能還希望將fs.existsSync()fse.ensureDirSync()轉換為異步操作。 出於競爭條件的原因,通常不建議在任何類型的多用戶或多線程或群集系統中使用fs.existsSync()


僅供參考,這是一個可重用的包裝器函數,可“允許” request.get().pipe()操作。

// wrap a request.get().pipe() stream so you know when it's done
// pass the same args to this that you pass to request except no completion callback
// this monitors the read stream for errors and the write stream you pass to `.pipe()` for completion and errors
//
// Sample usage:
//     requestPipePromise(someUrl).pipe(fs.createWriteStream(myFile)).then(function() {
//         console.log(".pipe() is done successfully");
//     }).catch(function(e) {
//         // got some error in either the request or the pipe
//         console.log(e);
//     })
// 

const request = require('request');

function requestPipePromise(...args) {
    return new Promise(function(resolve, reject) {
        let r = request.get(...args);
        r.on('error', reject);
        r.pipeOrig = r.pipe;
        // replacement .pipe() function that hooks the writestream to monitor it for completion/errors
        r.pipe = function(writeStream) {
            writeStream.on('error', reject).on('close', resolve);
            r.pipeOrig(writeStream);
        };
    });
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM