如何将多个可读流(来自多个API请求)导入单个可写流?

23

- 期望行为
- 实际行为
- 尝试过的方法
- 复现步骤
- 研究


期望行为

将从多个API请求中收到的多个可读流(pipe multiple readable streams)导入单个可写流(single writeable stream)。

API响应来自IBM-Watson的textToSpeech.synthesize()方法。

之所以需要多个请求,是因为该服务对文本输入有5KB的限制。

例如,18KB的字符串需要四个请求才能完成。

实际行为

可写流文件不完整且混乱。

应用程序似乎“挂起”了。

当我尝试在音频播放器中打开不完整的.mp3文件时,它会说它已损坏。

打开和关闭文件的过程似乎会增加其文件大小——好像打开文件会促使更多的数据流入其中。

对于较大的输入,例如四个小于4000字节的字符串,这种不良行为更为明显。

我尝试过的事情

我尝试了几种方法,使用npm包combined-stream, combined-stream2, multistreamarchiver将可读流导入单个可写流或多个可写流,但它们都会导致文件不完整。我的最后一次尝试没有使用任何包,详见下面的重现步骤部分。
因此,我质疑应用程序逻辑的每个部分:

01. Watson文本转语音API请求的响应类型是什么?

文本转语音文档中说,API响应类型为:
Response type: NodeJS.ReadableStream|FileObject|Buffer

我很困惑响应类型是三种可能的其中之一。

在尝试中,我一直假设它是一个可读流

02. 我可以在map函数中进行多个api请求吗?

03. 我可以在promise()中包装每个请求并解决response吗?

04. 我可以将生成的数组分配给promises变量吗?

05. 我可以声明var audio_files = await Promise.all(promises)吗?

06. 在此声明之后,所有响应是否都已“完成”?

07. 如何正确地将每个响应导入可写流?

08. 如何检测所有管道何时完成,以便我可以将文件发送回客户端?

对于问题2-6,我认为答案是“是”。

我认为我的失败与问题7和8有关。

重现步骤

您可以使用具有相应字节大小的四个随机生成的文本字符串的数组测试此代码,分别为3975386339743629字节 - 这里有一个pastebin的数组

// route handler
app.route("/api/:api_version/tts")
    .get(api_tts_get);

// route handler middleware
const api_tts_get = async (req, res) => {

    var query_parameters = req.query;

    var file_name = query_parameters.file_name;
    var text_string_array = text_string_array; // eg: https://pastebin.com/raw/JkK8ehwV

    var absolute_path = path.join(__dirname, "/src/temp_audio/", file_name);
    var relative_path = path.join("./src/temp_audio/", file_name); // path relative to server root

    // for each string in an array, send it to the watson api  
    var promises = text_string_array.map(text_string => {

        return new Promise((resolve, reject) => {

            // credentials
            var textToSpeech = new TextToSpeechV1({
                iam_apikey: iam_apikey,
                url: tts_service_url
            });

            // params  
            var synthesizeParams = {
                text: text_string,
                accept: 'audio/mp3',
                voice: 'en-US_AllisonV3Voice'
            };

            // make request  
            textToSpeech.synthesize(synthesizeParams, (err, audio) => {
                if (err) {
                    console.log("synthesize - an error occurred: ");
                    return reject(err);
                }
                resolve(audio);
            });

        });
    });

    try {
        // wait for all responses
        var audio_files = await Promise.all(promises);
        var audio_files_length = audio_files.length;

        var write_stream = fs.createWriteStream(`${relative_path}.mp3`);

        audio_files.forEach((audio, index) => {

            // if this is the last value in the array, 
            // pipe it to write_stream, 
            // when finished, the readable stream will emit 'end' 
            // then the .end() method will be called on write_stream  
            // which will trigger the 'finished' event on the write_stream    
            if (index == audio_files_length - 1) {
                audio.pipe(write_stream);
            }
            // if not the last value in the array, 
            // pipe to write_stream and leave open 
            else {
                audio.pipe(write_stream, { end: false });
            }

        });

        write_stream.on('finish', function() {

            // download the file (using absolute_path)  
            res.download(`${absolute_path}.mp3`, (err) => {
                if (err) {
                    console.log(err);
                }
                // delete the file (using relative_path)  
                fs.unlink(`${relative_path}.mp3`, (err) => {
                    if (err) {
                        console.log(err);
                    }
                });
            });

        });


    } catch (err) {
        console.log("there was an error getting tts");
        console.log(err);
    }

}

官方示例显示:

textToSpeech.synthesize(synthesizeParams)
  .then(audio => {
    audio.pipe(fs.createWriteStream('hello_world.mp3'));
  })
  .catch(err => {
    console.log('error:', err);
  });

这似乎对单个请求有效,但据我所知对于多个请求并不适用。
研究有关可读流和可写流、可读流模式(流动和暂停)、'data'、'end'、'drain'和'finish'事件、pipe()、fs.createReadStream()和fs.createWriteStream()的内容。
几乎所有的Node.js应用程序,无论多么简单,都会以某种方式使用流{{streams}}...
const server = http.createServer((req, res) => {
// `req` is an http.IncomingMessage, which is a Readable Stream
// `res` is an http.ServerResponse, which is a Writable Stream

let body = '';
// get the data as utf8 strings.
// if an encoding is not set, Buffer objects will be received.
req.setEncoding('utf8');

// readable streams emit 'data' events once a listener is added
req.on('data', (chunk) => {
body += chunk;
});

// the 'end' event indicates that the entire body has been received
req.on('end', () => {
try {
const data = JSON.parse(body);
// write back something interesting to the user:
res.write(typeof data);
res.end();
} catch (er) {
// uh oh! bad json!
res.statusCode = 400;
return res.end(`error: ${er.message}`);
}
});
});

https://nodejs.org/api/stream.html#stream_api_for_stream_consumers


可读流有两种主要模式,影响我们对其进行消费的方式...它们可以是暂停模式或流动模式。所有可读流默认都以暂停模式启动,但可以在需要时轻松切换到流动模式并返回暂停模式...只需添加data事件处理程序即可将暂停的流切换到流动模式,删除data事件处理程序则将流切换回暂停模式。

https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93


以下是使用可读和可写流时可以使用的重要事件和功能列表。

enter image description here

在可读流中最重要的事件包括:
- `data` 事件,每当流向使用者传递一块数据时就会触发该事件。 - `end` 事件,在从流中没有更多数据可供使用时触发。
在可写流中最重要的事件包括:
- `drain` 事件,表示可写流可以接收更多数据。 - `finish` 事件,在所有数据已经刷新到底层系统时触发。

https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93


.pipe() 方法会监听来自 fs.createReadStream() 的 'data' 和 'end' 事件。

https://github.com/substack/stream-handbook#why-you-should-use-streams


.pipe()是一个函数,它接收一个可读的源流src,并将输出连接到目标可写流dst

https://github.com/substack/stream-handbook#pipe


pipe()方法的返回值是目标流{{destination stream}}。

https://flaviocopes.com/nodejs-streams/#pipe


默认情况下,在源Readable流发出'end'事件时,目标Writable流上会调用stream.end(),以便目标不再可写。要禁用此默认行为,可以将选项end传递为false,从而使目标流保持打开状态:

https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options


'finish'事件在调用stream.end()方法并将所有数据刷新到底层系统后发出。

const writer = getWritableStreamSomehow();
for (let i = 0; i < 100; i++) {
  writer.write(`hello, #${i}!\n`);
}
writer.end('This is the end\n');
writer.on('finish', () => {
  console.log('All writes are now complete.');
});

https://nodejs.org/api/stream.html#stream_event_finish


如果您想读取多个文件并将它们导入可写流中,您需要将每个文件都导入可写流,并在执行此操作时传递end: false,因为默认情况下,当没有更多数据可读取时,可读流会结束可写流。以下是一个示例:
var ws = fs.createWriteStream('output.pdf');

fs.createReadStream('pdf-sample1.pdf').pipe(ws, { end: false });
fs.createReadStream('pdf-sample2.pdf').pipe(ws, { end: false });
fs.createReadStream('pdf-sample3.pdf').pipe(ws);

https://dev59.com/IIzda4cB1Zd3GeqPiByf#30916248


你想将第二个读取操作添加到第一个读取结束的事件监听器中...
var a = fs.createReadStream('a');
var b = fs.createReadStream('b');
var c = fs.createWriteStream('c');
a.pipe(c, {end:false});
a.on('end', function() {
  b.pipe(c)
}

https://dev59.com/LHzaa4cB1Zd3GeqPQXvR#28033554


Node.js流的简要历史 - 第部分和部分。


相关的谷歌搜索:

如何将多个可读流导入单个可写流?nodejs

涵盖相同或类似主题的问题,没有权威答案(或可能已过时):

如何将多个可读流导入单个可写流?

通过不同的可读流两次将数据导入同一可写流

将多个文件导入一个响应

从两个已导入的流创建Node.js stream


1
我认为你不能简单地将多个音频流连接在一起,就像你尝试的那样。每个流都有自己的头信息来定义每个片段。你会在最终文件中看到这些头信息交错出现,而第一个头信息并不会描述内容。你需要找到一个允许你合并音频文件的库。 - chughts
请问能否确认返回的响应类型是什么,例如 NodeJS.ReadableStream|FileObject|Buffer?那么我就可以更好地了解如何将它们连接并写入文件。谢谢。 - user1063287
你正在使用node.js,因此类型是流动的,但如果你通过SDK检查 - https://github.com/watson-developer-cloud/node-sdk/blob/master/text-to-speech/v1-generated.ts 和 https://github.com/IBM/node-sdk-core/blob/master/lib/requestwrapper.ts,那么它是一个流,你可以将其导入到写入流中 audio.pipe(fs.createWriteStream('hello_world.wav')); - chughts
@chughts - 你是在建议将每个可读流导入到自己的mp3文件中,然后在所有这些管道完成后合并音频吗?该方法已经在一个回答中被提出,但不幸的是产生了错误。我认为在第一次写入流时,某些东西出了问题。不确定是否相关,但在Postman中测试了对API的单个请求,输入约为4000字节 - 结果音频文件末尾有重复的声音块,原始的200 OK响应很快返回,但文件需要大约2分钟才能完成并准备好保存。 - user1063287
4个回答

5
这里要解决的核心问题是异步性。你几乎已经解决了:你发布的代码问题在于,你将所有源流同时且无序地导入目标流中。这意味着数据块将随机从不同的音频流中流出 - 即使你的end事件将优先于没有关闭目标流的pipe,这可能解释了为什么重新打开后它会增加。

你想要的是按顺序将它们导入——当你引用时,甚至发布了解决方案:

你想将第二个读取添加到第一个读取完成的事件监听器中...

或者作为代码:

a.pipe(c, { end:false });
a.on('end', function() {
  b.pipe(c);
}

这将按顺序将源流传输到目标流中。 根据您的代码,这意味着要用以下内容替换audio_files.forEach循环:
await Bluebird.mapSeries(audio_files, async (audio, index) => {  
    const isLastIndex = index == audio_files_length - 1;
    audio.pipe(write_stream, { end: isLastIndex });
    return new Promise(resolve => audio.on('end', resolve));
});

注意这里使用了 bluebird.js mapSeries
关于您的代码,进一步的建议:
  • 您应该考虑使用 lodash.js
  • 您应该使用 constlet 代替 var,并考虑使用 camelCase
  • 当您注意到“它可以处理一个事件,但是在处理多个事件时失败”时,始终要考虑:异步性、排列组合、竞争条件。
进一步阅读,结合原生节点流的限制:https://github.com/nodejs/node/issues/93

请注意,此解决方案可能无法解决您的方法的所有问题:例如,我不确定是否可以连接音频流以生成另一个有效的音频流。大多数数据格式都不允许这样做!因此,连接音频文件的另一种方法/解决方案可能对您更有价值,并且听起来更加稳定! - B M
尝试回答您的问题:1)并非所有问题都可能得到解决,但至少我在您的代码中发现了这些问题,因此逻辑上它“可能”会正常工作;2a)仅返回承诺,以便mapSeries在调用下一个函数之前等待每个流end。 承诺结果未被使用;2b)结果是resolve(=undefined)的返回值-未被使用;3)如在您的代码中所做的那样:write_stream.on('finish',…。 如果您将audio_files.forEach循环替换为使用mapSeries(audio_files循环,则可以更接近解决方案,如果数据格式允许这种方法。 - B M
你最后的代码示例看起来不错 - 你试过将它与第一个代码示例一起使用吗,也就是在audio_files.forEach循环之前的所有内容(包括text_string_array等)?我的建议的整个目的是将流按顺序排列,每个流在开始写入之前都要完成其所有的“data”事件。对于2b)的一个更正:结果将是audio.on的返回值(仍应为undefined)。 - B M
是的,只需将audio_files.forEach块替换为Bluebird.mapSeries块,如我在第一条评论中链接到的pastebin所示。当前的行为是第一个promise花费的时间太长了以至于应用程序似乎开始重新发送请求,从头开始,此时我只需使用Ctrl C终止应用程序即可。 - user1063287
如果第一个可读流永远不会结束 - 这似乎是 Text-to-Speech 库的问题,可能会保持流处于打开状态?您可以尝试一个最小的示例,只是为了查看可读流上是否发生 audio.on('end'。此外,您可以尝试在 Promise(resolve => audio.on('end', resolve)) 中添加 .catch(...),并编写记录错误的代码。 - B M
显示剩余2条评论

3

我来发表一下自己的意见,因为最近我看到了一个类似的问题!从我所测试和研究的情况来看,您可以将两个.mp3 / .wav流合并成一个。这将导致文件存在明显的问题,如您所提到的截断、故障等。

我认为唯一正确合并音频流的方法是使用专门设计用于连接声音文件/数据的模块。

我得到的最好结果是将音频合成为单独的文件,然后像这样组合:

function combineMp3Files(files, outputFile) {
    const ffmpeg = require("fluent-ffmpeg");
    const combiner = ffmpeg().on("error", err => {
        console.error("An error occurred: " + err.message);
    })
    .on("end", () => {
        console.log('Merge complete');
    });

    // Add in each .mp3 file.
    files.forEach(file => {
        combiner.input(file)
    });

    combiner.mergeToFile(outputFile); 
}

这里使用了node-fluent-ffmpeg库,需要安装ffmpeg

除此之外,我建议您向IBM支持团队咨询(因为根据您的说法,文档似乎没有说明),API调用者应该如何组合合成音频,因为这是一个非常常见的用例。

以下是创建文本文件的步骤:

// Switching to audio/webm and the V3 voices.. much better output 
function synthesizeText(text) {
    const synthesizeParams = {
        text: text,
        accept: 'audio/webm',
        voice: 'en-US_LisaV3Voice'
    };
    return textToSpeech.synthesize(synthesizeParams);
}


async function synthesizeTextChunksSeparateFiles(text_chunks) {
    const audioArray = await Promise.all(text_chunks.map(synthesizeText));
    console.log(`synthesizeTextChunks: Received ${audioArray.length} result(s), writing to separate files...`);
    audioArray.forEach((audio, index) => {
        audio.pipe(fs.createWriteStream(`audio-${index}.mp3`));
    });
}

然后像这样组合:
combineMp3Files(['audio-0.mp3', 'audio-1.mp3', 'audio-2.mp3', 'audio-3.mp3', 'audio-4.mp3'], 'combined.mp3');

我应该指出,我是通过两个独立的步骤来完成这个过程的(等待几百毫秒也可以),但是等待单个文件被写入然后将它们组合起来应该很容易。

下面是一个可以完成这个过程的函数:

async function synthesizeTextChunksThenCombine(text_chunks, outputFile) {
    const audioArray = await Promise.all(text_chunks.map(synthesizeText));
    console.log(`synthesizeTextChunks: Received ${audioArray.length} result(s), writing to separate files...`);
    let writePromises = audioArray.map((audio, index) => {
        return new Promise((resolve, reject) => {
            audio.pipe(fs.createWriteStream(`audio-${index}.mp3`).on('close', () => {   
                resolve(`audio-${index}.mp3`);
            }));
        })
    });
    let files = await Promise.all(writePromises);
    console.log('synthesizeTextChunksThenCombine: Separate files: ', files);
    combineMp3Files(files, outputFile);
}

1
我分两步进行,所以目前没有检测到流已完成,但是你可以创建一个 Promise 数组,在每个流的 end 回调上解决它们,然后使用 await all 进行处理。 - Terry Lennox
1
我添加了一个函数,用于创建临时音频文件,然后将它们组合在一起一旦它们都写入完毕。 - Terry Lennox
1
明天会测试,需要重构代码了,我快要睡着了,谢谢! - user1063287
1
在进行了4个文本字符串测试,每个字符串大小不超过4000字节后,synthesizeTextChunks: Received 4 result(s), writing to separate files...之后,可能需要30秒或更长时间才会显示synthesizeTextChunksThenCombine: Separate files: [ 'audio-0.mp3', 'audio-1.mp3', 'audio-2.mp3', 'audio-3.mp3' ]Merge complete,然后我打开最终的outputFile,发现音频有些故障并且不完整,关闭文件并重新打开它会导致文件显示的文件大小增加,并再次记录前两条消息。因此,似乎出现了一些问题。 - user1063287
1
哦,是的,当然!我只是想确保Watson所产生的音频是最好的! - Terry Lennox
显示剩余7条评论

0

这里有两个解决方案:

解决方案01

  • 使用Bluebird.mapSeries
  • 将每个响应写入临时文件
  • 将它们放在zip文件中(使用archiver)
  • 将zip文件发送回客户端进行保存
  • 删除临时文件

它利用了 BM answer 中的 Bluebird.mapSeries,但不仅仅是在响应上进行映射,而且请求和响应都在 map 函数中处理。此外,它会在可写流 finish 事件中解决 promises,而不是在可读流 end 事件中解决 promises。Bluebird 在map函数内部 暂停 迭代,直到接收并处理完响应,然后再进入下一次迭代。

鉴于 Bluebird 映射函数生成干净的音频文件,而非压缩文件,您可以使用像 Terry Lennox's answer 中的解决方案将多个音频文件合并为一个音频文件。我第一次尝试该解决方案时,使用了 Bluebirdfluent-ffmpeg,生成了单个文件,但质量稍低 - 毫无疑问,这可以在 ffmpeg 设置中进行调整,但我没有时间去做。
// route handler
app.route("/api/:api_version/tts")
    .get(api_tts_get);

// route handler middleware
const api_tts_get = async (req, res) => {

    var query_parameters = req.query;

    var file_name = query_parameters.file_name;
    var text_string_array = text_string_array; // eg: https://pastebin.com/raw/JkK8ehwV

    var absolute_path = path.join(__dirname, "/src/temp_audio/", file_name);
    var relative_path = path.join("./src/temp_audio/", file_name); // path relative to server root

    // set up archiver
    var archive = archiver('zip', {
        zlib: { level: 9 } // sets the compression level  
    });
    var zip_write_stream = fs.createWriteStream(`${relative_path}.zip`);
    archive.pipe(zip_write_stream);

    await Bluebird.mapSeries(text_chunk_array, async function(text_chunk, index) {

        // check if last value of array  
        const isLastIndex = index === text_chunk_array.length - 1;

        return new Promise((resolve, reject) => {

            var textToSpeech = new TextToSpeechV1({
                iam_apikey: iam_apikey,
                url: tts_service_url
            });

            var synthesizeParams = {
                text: text_chunk,
                accept: 'audio/mp3',
                voice: 'en-US_AllisonV3Voice'
            };

            textToSpeech.synthesize(synthesizeParams, (err, audio) => {
                if (err) {
                    console.log("synthesize - an error occurred: ");
                    return reject(err);
                }

                // write individual files to disk  
                var file_name = `${relative_path}_${index}.mp3`;
                var write_stream = fs.createWriteStream(`${file_name}`);
                audio.pipe(write_stream);

                // on finish event of individual file write  
                write_stream.on('finish', function() {

                    // add file to archive  
                    archive.file(file_name, { name: `audio_${index}.mp3` });

                    // if not the last value of the array
                    if (isLastIndex === false) {
                        resolve();
                    } 
                    // if the last value of the array 
                    else if (isLastIndex === true) {
                        resolve();

                        // when zip file has finished writing,
                        // send it back to client, and delete temp files from server 
                        zip_write_stream.on('close', function() {

                            // download the zip file (using absolute_path)  
                            res.download(`${absolute_path}.zip`, (err) => {
                                if (err) {
                                    console.log(err);
                                }

                                // delete each audio file (using relative_path) 
                                for (let i = 0; i < text_chunk_array.length; i++) {
                                    fs.unlink(`${relative_path}_${i}.mp3`, (err) => {
                                        if (err) {
                                            console.log(err);
                                        }
                                        console.log(`AUDIO FILE ${i} REMOVED!`);
                                    });
                                }

                                // delete the zip file
                                fs.unlink(`${relative_path}.zip`, (err) => {
                                    if (err) {
                                        console.log(err);
                                    }
                                    console.log(`ZIP FILE REMOVED!`);
                                });

                            });


                        });

                        // from archiver readme examples  
                        archive.on('warning', function(err) {
                            if (err.code === 'ENOENT') {
                                // log warning
                            } else {
                                // throw error
                                throw err;
                            }
                        });

                        // from archiver readme examples  
                        archive.on('error', function(err) {
                            throw err;
                        });

                        // from archiver readme examples 
                        archive.finalize();
                    }
                });
            });

        });

    });

}

解决方案02

我很想找到一种不使用库在map()迭代中“暂停”的解决方案,所以我:

  • map()函数替换为for of循环
  • 在api调用之前使用await,而不是将其包装在一个promise中,和
  • 不再使用return new Promise()来包含响应处理,而是使用await new Promise()(从this answer中获得)

最后一个更改神奇地暂停了循环,直到完成archive.file()audio.pipe(writestream)操作-我希望更好地理解它的工作原理。

// route handler
app.route("/api/:api_version/tts")
    .get(api_tts_get);

// route handler middleware
const api_tts_get = async (req, res) => {

    var query_parameters = req.query;

    var file_name = query_parameters.file_name;
    var text_string_array = text_string_array; // eg: https://pastebin.com/raw/JkK8ehwV

    var absolute_path = path.join(__dirname, "/src/temp_audio/", file_name);
    var relative_path = path.join("./src/temp_audio/", file_name); // path relative to server root

    // set up archiver
    var archive = archiver('zip', {
        zlib: { level: 9 } // sets the compression level  
    });
    var zip_write_stream = fs.createWriteStream(`${relative_path}.zip`);
    archive.pipe(zip_write_stream);

    for (const [index, text_chunk] of text_chunk_array.entries()) {

        // check if last value of array 
        const isLastIndex = index === text_chunk_array.length - 1;

        var textToSpeech = new TextToSpeechV1({
            iam_apikey: iam_apikey,
            url: tts_service_url
        });

        var synthesizeParams = {
            text: text_chunk,
            accept: 'audio/mp3',
            voice: 'en-US_AllisonV3Voice'
        };

        try {

            var audio_readable_stream = await textToSpeech.synthesize(synthesizeParams);

            await new Promise(function(resolve, reject) {

                // write individual files to disk 
                var file_name = `${relative_path}_${index}.mp3`;
                var write_stream = fs.createWriteStream(`${file_name}`);
                audio_readable_stream.pipe(write_stream);

                // on finish event of individual file write
                write_stream.on('finish', function() {

                    // add file to archive
                    archive.file(file_name, { name: `audio_${index}.mp3` });

                    // if not the last value of the array
                    if (isLastIndex === false) {
                        resolve();
                    } 
                    // if the last value of the array 
                    else if (isLastIndex === true) {
                        resolve();

                        // when zip file has finished writing,
                        // send it back to client, and delete temp files from server
                        zip_write_stream.on('close', function() {

                            // download the zip file (using absolute_path)  
                            res.download(`${absolute_path}.zip`, (err) => {
                                if (err) {
                                    console.log(err);
                                }

                                // delete each audio file (using relative_path)
                                for (let i = 0; i < text_chunk_array.length; i++) {
                                    fs.unlink(`${relative_path}_${i}.mp3`, (err) => {
                                        if (err) {
                                            console.log(err);
                                        }
                                        console.log(`AUDIO FILE ${i} REMOVED!`);
                                    });
                                }

                                // delete the zip file
                                fs.unlink(`${relative_path}.zip`, (err) => {
                                    if (err) {
                                        console.log(err);
                                    }
                                    console.log(`ZIP FILE REMOVED!`);
                                });

                            });


                        });

                        // from archiver readme examples  
                        archive.on('warning', function(err) {
                            if (err.code === 'ENOENT') {
                                // log warning
                            } else {
                                // throw error
                                throw err;
                            }
                        });

                        // from archiver readme examples  
                        archive.on('error', function(err) {
                            throw err;
                        });

                        // from archiver readme examples   
                        archive.finalize();
                    }
                });

            });

        } catch (err) {
            console.log("oh dear, there was an error: ");
            console.log(err);
        }
    }

}

学习经验

在这个过程中出现的其他问题如下所述:

使用node时,长时间请求超时(并重新发送请求)...

// solution  
req.connection.setTimeout( 1000 * 60 * 10 ); // ten minutes

请参考:https://github.com/expressjs/express/issues/2512


由于节点最大标头大小为8KB(查询字符串包含在标头大小中),导致400错误...
// solution (although probably not recommended - better to get text_string_array from server, rather than client) 
node --max-http-header-size 80000 app.js

请参见:https://github.com/nodejs/node/issues/24692


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接