首页 > 解决方案 > 为什么我在 NodeJS express 应用中的上传不完整

问题描述

我需要在生成后将 v8 堆转储上传到 AWS S3 存储桶中,但是上传的文件为 0KB 或 256KB。服务器上的文件大小超过 70MB,因此请求似乎没有等到堆转储没有完全刷新到磁盘。我猜想通过管道传输的可读流是以 fs.createWriteStream异步方式发生的,并且await对函数的调用实际上并没有等待。我正在使用 v3 版本的 AWS NodeJS 开发工具包。我做错了什么?

代码


async function createHeapSnapshot (fileName) {
    const snapshotStream = v8.getHeapSnapshot();
    // It's important that the filename end with `.heapsnapshot`,
    // otherwise Chrome DevTools won't open it.

    const fileStream = fs.createWriteStream(fileName);
    snapshotStream.pipe(fileStream);
}

async function pushHeapSnapshotToS3(fileName)
{
    const heapDump = fs.createReadStream(fileName);

    const s3Client = new S3Client();
    const putCommand = new PutObjectCommand(
        {
            Bucket: "my-bucket",
            Key: `heapdumps/${fileName}`,
            Body: heapDump
        }
    )
    
    return s3Client.send(putCommand);
}

app.get('/heapdump', asyncMiddleware(async (req, res) => {
    const currentDateTime = Date.now();
    const fileName = `${currentDateTime}.heapsnapshot`;

    await createHeapSnapshot(fileName);
    await pushHeapSnapshotToS3(fileName);
    res.send({
        heapdumpFileName: `${currentDateTime}.heapsnapshot`
    });
}));

标签: node.jsasync-awaitheap-dump

解决方案


你的猜测是正确的。返回一个承诺,但该createHeapSnapshot()承诺与流完成时完全没有联系。因此,当调用者使用await该承诺时,承诺在流实际完成之前很久就解决了。 async函数没有任何魔力,无法以某种方式知道何时完成了非承诺的异步操作.pipe()。因此,您的async函数返回一个与流函数完全没有联系的承诺。

由于流对 Promise 没有太多的原生支持,因此您可以手动承诺流的完成和错误:

function createHeapSnapshot (fileName) {
    return new Promise((resolve, reject) => {
        const snapshotStream = v8.getHeapSnapshot();
        // It's important that the filename end with `.heapsnapshot`,
        // otherwise Chrome DevTools won't open it.
    
        const fileStream = fs.createWriteStream(fileName);
        fileStream.on('error', reject).on('finish', resolve);

        snapshotStream.on('error', reject);        
        snapshotStream.pipe(fileStream);
    });
}

或者,您可以使用支持 Promise 的较新pipeline()功能(nodejs v15 中添加的内置 Promise 支持)并替换.pipe()并具有内置错误监控以拒绝 Promise:

const { pipeline } = require('stream/promises');

function createHeapSnapshot (fileName) {
    const snapshotStream = v8.getHeapSnapshot();
    // It's important that the filename end with `.heapsnapshot`,
    // otherwise Chrome DevTools won't open it.

    return pipeline(snapshotStream, fs.createWriteStream(fileName))
}

推荐阅读