为什么我在 NodeJS Express 应用程序中上传不完整

Why is my upload incomplete in a NodeJS express app

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

代码


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

你猜对了。 createHeapSnapshot() returns 是一个承诺,但该承诺与流何时完成完全没有关系。因此,当调用者对该承诺使用 await 时,承诺在流实际完成之前很久就已解决。 async 函数并没有什么魔力可以知道像 .pipe() 这样的非承诺异步操作何时完成。因此,您的 async 函数 returns 一个与流函数完全没有联系的承诺。

由于流对承诺的原生支持不多,您可以手动承诺流的完成和错误:

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

或者,您可以使用更新的 pipeline() function,它确实支持承诺(在 nodejs v15 中添加了内置承诺支持)并替换了 .pipe() 并具有内置错误监控以拒绝承诺:

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