【问题标题】:How to pipe multiple readable streams, from multiple api requests, to a single writeable stream?如何将多个可读流(从多个 api 请求)传输到单个可写流?
【发布时间】:2019-12-01 02:45:31
【问题描述】:

- 期望的行为
- 实际行为
- 我尝试过的内容
- 步骤复制
-研究


期望的行为

将从多个 api 请求接收到的多个可读流通过管道传输到单个可写流。

api 响应来自 ibm-watson 的 textToSpeech.synthesize() 方法。

需要多个请求的原因是服务对文本输入有5KB 限制。

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

实际行为

可写流文件不完整,乱码。

应用程序似乎“挂起”。

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

打开和关闭文件的过程似乎会增加文件的大小——就像打开文件会以某种方式提示更多数据流入一样。

如果输入越大,不良行为就越明显,例如 4000 字节或更少的四个字符串。

我的尝试

我已经尝试了几种方法来使用 npm 包 combined-streamcombined-stream2multistreamarchiver 将可读流通过管道传输到单个可写流或多个可写流,它们都会导致文件不完整.我的最后一次尝试没有使用任何包,如下面的Steps To Reproduce 部分所示。

因此,我质疑我的应用程序逻辑的每个部分:

01. watson text to speech api 请求的响应类型是什么?

text to speech docs,说api响应类型是:

Response type: NodeJS.ReadableStream|FileObject|Buffer

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

在我所有的尝试中,我一直假设它是readable stream

02.我可以在一个地图函数中发出多个api请求吗?

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

04. 我可以将结果数组分配给promises 变量吗?

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

06.在此声明之后,所有响应都“完成”了吗?

07. 如何正确地将每个响应通过管道传输到可写流?

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

对于问题 2 - 6,我假设答案是“是”。

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

复制步骤

您可以使用包含四个随机生成的文本字符串的数组来测试此代码,这些字符串的字节大小分别为 3975386339743629 字节 - here is a pastebin of that array

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

}

official example 显示:

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 应用程序,无论多么简单,都以某种方式使用流......

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


可读流有两种主要模式,它们会影响我们使用它们的方式……它们可以是paused 模式或flowing 模式。默认情况下,所有可读流都以暂停模式启动,但可以在需要时轻松切换到 flowing 并返回到 paused...只需添加 data 事件处理程序即可将暂停流切换到 flowing 模式并删除data 事件处理程序将流切换回paused 模式。

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


这里列出了可用于可读可写流的重要事件和函数

可读流上最重要的事件是:

data 事件,每当流将一大块数据传递给消费者时就会触发该事件 end 事件,当流中没有更多数据可供使用时触发。

可写流上最重要的事件是:

drain 事件,这是可写流可以接收更多数据的信号。 finish 事件,当所有数据都刷新到底层系统时触发。

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


.pipe() 负责监听来自fs.createReadStream() 的“数据”和“结束”事件。

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


.pipe() 只是一个函数,它采用可读源流 src 并将输出挂钩到目标可写流 dst

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


pipe()方法的返回值为目标流

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://stackoverflow.com/a/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://stackoverflow.com/a/28033554


节点流的简史 - 部分 onetwo


相关的谷歌搜索:

如何将多个可读流传输到单个可写流?节点

涉及相同或相似主题的问题,没有权威答案(或可能“过时”):

How to pipe multiple ReadableStreams to a single WriteStream?

Piping to same Writable stream twice via different Readable stream

Pipe multiple files to one response

Creating a Node.js stream from two piped streams

【问题讨论】:

  • 我认为您不能以您尝试的方式简单地连接多个音频流。每个流都有自己的标题信息来定义每个段。您将在最终文件中散布这些标题,而第一个根本不会描述内容。您需要找到一个允许您加入音频文件的库。
  • 能否请您确认返回响应类型是什么,即NodeJS.ReadableStream|FileObject|Buffer?然后我想我会更好地了解如何加入他们并写入文件。谢谢。
  • 您使用的是 node.js,因此类型是可变的,但如果您通过 SDK 检查 - github.com/watson-developer-cloud/node-sdk/blob/master/…github.com/IBM/node-sdk-core/blob/master/lib/requestwrapper.ts,那么它是一个流,您可以将其通过管道传输到写入流 @ 987654396@
  • @chughts - 您是否建议将每个可读流传输到其自己的 mp3 文件,然后在所有这些管道完成后加入音频?此后,该方法已在不幸产生错误的答案中提出。我认为首先编写流的管道出了问题。不确定是否相关,但在 Postman 中测试了对 api 的单个请求,输入大约 4000 字节 - 结果音频在文件末尾有重复的声音块,原始的 200 OK 响应也很快返回,但文件大约需要 2 分钟完成并准备保存。

标签: node.js express ibm-watson fs node-streams


【解决方案1】:

我会在这里给我两分钱,因为我最近看了一个类似的问题!根据我的测试和研究,您可以将两个 .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);
}

【讨论】:

  • 我分两步来做,所以我目前没有检测到流已经完成,但是你可以创建一个 Promises 数组,在每一个的 stream.end 回调上解析,然后为此做一个等待。
  • 我添加了一个创建临时音频文件的函数,然后在它们写入后将它们组合起来......
  • 明天测试,需要重构,睡着了,谢谢!
  • 测试了 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 和音频有一些故障并且不完整,关闭文件并重新打开它导致文件显示的文件大小增加,并且再次记录前两条消息。所以似乎出了点问题。
  • 我可以再次确认。我在 audio-n.mp3 文件中也看到了这些故障,所以在我看来,原始合成 生成的流到文件的持久性导致了问题。我可能会尝试使用 API 看看是否可以改进这一点..
【解决方案2】:

WebRTC 是解决上述问题的好选择。因为一旦你的文件生成完成,我会让客户听。

https://www.npmjs.com/package/simple-peer

【讨论】:

    【解决方案3】:

    这里要解决的核心问题是异步性。您几乎拥有它:您发布的代码的问题是您将所有源流并行且无序地输送到目标流中。这意味着data 块将随机从不同的音频流中流出——即使你的end 事件在没有end 过早关闭目标流的情况下也会超过pipes,这可能解释了为什么在你重新打开它后它会增加.

    您想要的是按顺序传递它们 - 您甚至在引用时发布了解决方案

    您想将第二次读取添加到事件侦听器中以完成第一次读取...

    或作为代码:

    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
    • 您应该使用const & let 而不是var 并考虑使用camelCase
    • 当您注意到“它对一个事件有效,但对多个事件失败”时,请始终思考:异步性、排列、竞争条件。

    延伸阅读,结合原生节点流的限制:https://github.com/nodejs/node/issues/93

    【讨论】:

    • 感谢您,我的实施尝试产生了各种问题,一些可能有助于解决这些问题的问题是:1)此解决方案是否与 OP 中提供的 text_string_array 值一起使用 - 测试行为大输入? 2) 我看到 map 函数的每次迭代都会返回一个新的承诺 - 2a) 这是否意味着每个可读流都返回一个 result 值? 2b) 每个承诺的result 值是什么? 3)我如何检测输出文件何时准备好发回?这是我最后一次尝试实施解决方案的粘贴箱:pastebin.com/PY8GWPmq
    • 请注意,此解决方案可能无法解决您的方法的所有问题:例如,我不确定是否可以加入音频流以产生另一个有效的音频流。大多数数据格式都不允许这样做!因此,加入音频文件的其他方法/解决方案可能对您更有价值,并且听起来更稳定!
    • 尝试回答您的问题:1) 并非所有问题都可能得到解决,但至少我在您的代码中发现的那些问题在逻辑上可能起作用 2a) 承诺仅返回,以便mapSeries 等待每个流到end,然后再调用下一个流上的pipe。不使用承诺结果。 2b) 结果是resolve (=undefined) 的返回值——它没有被使用。 3)正如您的代码中所做的那样:write_stream.on('finish', …。如果您将 audio_files.forEach 循环替换为 mapSeries(audio_files 循环,那么您应该更接近解决方案,如果数据格式允许这种方法
    • 您的最后一个代码示例看起来不错 - 您是否尝试使用整个第一个代码示例,因此使用 audio_files.forEach 循环之前的所有内容(text_string_array 等等)?我提议的全部目的是使流按顺序排列,每个流在下一个开始编写之前完成其所有data 事件。 2b) 的一项更正:结果将是audio.on 的返回值(仍应为undefined)。
    • 是的,刚刚将audio_files.forEach 块替换为Bluebird.mapSeries 块,如我在第一条评论中链接到的pastebin 所示。当前的行为是第一个承诺需要很长时间才能“完成”,以至于应用程序似乎开始重新发送请求,从头开始,此时我只需 Ctrl C 应用程序。
    【解决方案4】:

    这里有两个解决方案。

    解决方案 01

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

    它利用 BM 的 answer 中的 Bluebird.mapSeries,但不仅仅是映射响应,请求响应在 map 函数中处理。此外,它解决了可写流finish 事件上的承诺,而不是可读流end 事件。 Bluebird 很有帮助,因为它 pauses 在 map 函数中迭代,直到收到并处理了响应,然后继续进行下一次迭代。

    鉴于 Bluebird 映射函数生成干净的音频文件,而不是压缩文件,您可以使用像 Terry Lennox 的 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 loop
    • 在调用 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);
            }
        }
    
    }
    

    学习经历

    在此过程中出现的其他问题记录如下:

    使用节点时长请求超时(并重新发送请求)...

    // 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

    【讨论】:

      猜你喜欢
      • 2012-12-19
      • 2018-06-04
      • 1970-01-01
      • 2014-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-03
      相关资源
      最近更新 更多