首页 > 解决方案 > 使用 await 和 request-promise 的问题

问题描述

作为背景,我有一个 nodeJs 控制器,它可以从 3 方网站下载图像。为此,我需要一个 Key 和一个我拥有的临时令牌。问题是有时令牌在我下载图像之前已过期,在这种情况下,我的下载将是 0 字节大小的 jpg。因此,我不想在下载文件后检查一堆 0 字节的文件,如果 t 为 0,则删除。我正在使用 request-promise 来等待将文件写入系统的完成,但它似乎不起作用。在我看来,下载已完成,但文件尚未通过管道保存到驱动器。如果我去放一个几毫秒的卧铺,一切都很好。那么在我检查文件大小之前,如何确定文件是否已下载并保存(通过管道传输)到硬盘驱动器?

这是我当前代码的片段

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" );

标签: node.js

解决方案


await仅当您等待与您尝试等待的异步操作完全相关的承诺时才会做一些有用的事情。您的代码中有许多地方,您正在等待的不是承诺,因此它不会等待底层异步操作完成。这会弄乱代码中的时间。

先说一点背景...

async函数允许您await在返回承诺的操作上使用。但是,一个async函数不包含关于不基于 promise 的异步操作的魔法 juju。因此,当您执行fs.unlink()in时getFilesizeInBytes(),这只是函数getFilesizeInBytes()不会等待的随机异步操作。类似地,没有返回值,getFilesizeInBytes()因此从该async函数返回的 Promise 具有已undefined解析的值。因此,当您这样做时await getFilesizeInBytes(fullPath),您将获得一个undefined价值。

因此,现在您的getFilesizeInBytes()函数在fs.unlink()操作完成之前返回,并返回一个解析为undefined.

要进行正确的异步设计,我建议您更改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接口还没有涵盖这种类型的 Promise 与流一起使用。

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()通常不鼓励在任何类型的多用户或多线程或集群系统中使用。


仅供参考,这是一个可重用的包装函数,它“承诺”.pipe()request.get().

// 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);
        };
    });
}

推荐阅读