node.js - 使用await和request-promise的问题

标签 node.js

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

这是我当前代码的片段

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 仅当您等待与您尝试等待的异步操作完全相关的 promise 时,才会执行一些有用的操作。您的代码中有很多地方您等待的不是 promise ,因此它不会等待底层异步操作完成。这会扰乱代码中的时间。

首先介绍一下背景...

async 函数允许您对返回 Promise 的操作使用 await。但是,async 函数不包含关于非基于 Promise 的异步操作的魔法。因此,当您在 getFilesizeInBytes() 中执行 fs.unlink() 时,这只是函数 getFilesizeInBytes() 不会执行的随机异步操作等待。同样,getFilesizeInBytes() 中没有返回值,因此从该 async 函数返回的 Promise 具有 undefined 解析值。因此,当您await getFilesizeInBytes(fullPath)时,您将获得一个未定义值。

所以,现在您的 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() 不会返回 promise 。它返回流。因此,您正在等待一个没有做任何有用事情的流。因此,您的 await 不会等待所有内容完成下载并保存到磁盘。

request-promise 库特别建议不要将 .pipe() 与 request-promise 库一起使用。它表示使用 .pipe() 的常规请求库。要解决您的特定问题,您可能必须自己 promisify .pipe() 或仅使用流上的正确事件来知道它何时完成以及何时应该继续执行其余代码。

<小时/>

我不确定 promise 流结束的最佳方法是什么。我可能需要做更多调查。这是监视写入流上的 closeerror 事件以在流完成时解析/拒绝 promise 的一种方法。看起来新的 fs.promises 接口(interface)尚未涵盖此类与流一起使用的 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()

<小时/>

仅供引用,这是一个可重用的包装函数,它“ promise ”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);
        };
    });
}

关于node.js - 使用await和request-promise的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57486165/

相关文章:

ajax - 通知系统 - Socket.io 还是 Ajax?

Node.js 异步并行 - 后果是什么?

javascript - Grunt Watch 任务不触发 Uglify 任务

node.js - Strongloop同时用(or)和(and)查询数据

node.js - mongoose 连接后如何获取数据库?

javascript - promise 加入变革 promise 一切

javascript - 使用 {{#each}} 迭代 (express-)handlebars 模板中 helper 的字符串数组

javascript - 在 Node.js 中,如何使用 node-seq 进行嵌套回调

javascript - Node.js、IIS、Angular 和 Visual Studio

node.js - NodeJS HTTP/2 ERR_CONNECTION_REFUSED