Как мне восстановиться после сбоя при загрузке нескольких файлов через мой сервер на S3? - PullRequest
0 голосов
/ 18 февраля 2020

Впервые для NodeJS и S3, я написал следующий ознакомительный код для загрузки файлов на S3 через мой NodeJS сервер без сохранения файла на диск или в память:

var express = require('express');
var Busboy = require('busboy');
var S3 = require('../utils/s3Util');
var router = express.Router(); // mounted at /uploads

router.post("/", function (req, res, next) {
    let bb = new Busboy({ headers: req.headers });
    const uploads = [];
    bb.on('file', (fieldname, stream, filename, encoding, mimeType) => {
        console.log(`Uploaded fieldname: ${fieldname}; filename: ${filename}, mimeType: ${mimeType}`);
        uploads.push(S3.svc.upload({ Bucket: 'my-test-bucket', Key: filename, Body: stream }).promise());
    });
    bb.on('finish', () => {
        console.log("# of promises:", uploads.length);
        Promise.all(uploads).then(retVals => {
            for (let i = 0; retVals && i < retVals.length; i++) {
                console.log(`File ${i + 1}::`, retVals[i]); 
            }
            res.end();
        }).catch(err => {
            console.log("Error::", err);
            res.status(500).send(`${err.name}: ${err.message}`);
        });
    });
    req.pipe(bb);
});

module.exports = router;

При общем сбое В случае, как мне справиться со сценарием, в котором загрузка 1 или более из x загружаемых файлов не удалась? Некоторые загрузки были бы успешными, некоторые - неудачными. Однако в предложении catch я бы не знал, какие из них потерпели неудачу ...

Было бы хорошо иметь возможность сделать этот процесс загрузки несколько транзакционным (т. Е. Либо все загрузки успешны, или ни один не делает). Когда происходят ошибки, в идеале я смог бы "откатить" подмножество успешных загрузок.

Ответы [ 2 ]

1 голос
/ 20 февраля 2020

Я наконец-то пошел со следующим кодом, который является расширением ответа @ JoshWulf:

function handleUpload(req, res, bucket, key) {
    let bb = new Busboy({ headers: req.headers });
    const uploads = [];
    bb.on('file', (fieldname, stream, filename, encoding, mimeType) => {
        console.log(`Uploaded fieldname: ${fieldname}; filename: ${filename}, mimeType: ${mimeType}`);
        const params = { Bucket: bucket, Key: key, Body: stream, ContentType: mimeType };
        uploads.push({ filename, result: S3.svc.upload(params).promise().then(data => data).catch(err => err) });
    });
    bb.on('finish', async () => {
        const results = await Promise.all(uploads.map(async (upload) => ({ ...upload, result: await upload.result })));
        // handle success/failure with their respective objects
    });
    req.pipe(bb);
}

Разница здесь от @ Jo sh * Wulf ответа в том, что в моем загрузить обещание Я возвращаю возвращенный объект данных (в случае успеха) и возвращенный объект ошибки (в случае сбоя) как есть. Это позволяет мне позже использовать их по мере необходимости.

1 голос
/ 18 февраля 2020

Вы можете сделать это следующим образом:

Pu sh объект в загрузках, с данными, которые необходимо повторить, так:

uploads.push({ 
  fieldname, 
  filename, 
  mimeType,
  uploaded: S3.svc.upload({ Bucket: 'my-test-bucket', Key: filename, Body: stream })
    .promise()
    .then(() => true)
    .catch(() => false)
});

...

const failed = await 
  (Promise.all(uploads.map(async upload => ({...upload, uploaded: await upload.uploaded})))).then(u => u.filter(upload => !upload.uploaded))

const failedFiles = failed.join(', ')

console.log(`The following files failed to upload: ${failedFiles}`);

Вам нужно сделать ваше событие обработчики async для использования await внутри них, например:

bb.on('file', async (fieldname, stream, filename, encoding, mimeType) => {
...