Node.js:具有顺序+异步输入/输出的并行处理

时间:2018-11-14 04:12:23

标签: javascript node.js asynchronous promise stream

我正在尝试在Node.js中为可能包含数万个项目的数组构建高效的异步处理管道。管道始于对Web API的调用(使用node-fetch包),经过多个解析/转换步骤,最后以附加到磁盘上的文件结束。

但是,将某些要求放在一起,将使这变得困难:

  1. Web API每分钟允许的请求数量有限,因此我必须能够限制/设置每次初始fetch调用之间的延迟。这意味着该阶段必须是异步顺序的。

  2. 所有结果都写入同一文件,并且必须按照初始数组指定的顺序添加到附加文件中,因此此阶段也必须是连续的。

  3. 否则,为了提高性能,应该尽可能并行运行。例子:

    a。应当能够处理较早的项目,包括文件写入步骤(假设满足要求2),同时尚未提取较晚的项目(由于第1点的限制)。

    b。在最后的文件写入步骤(为了满足要求2),仅应将较晚的项目延迟较早的项目(例如,如果一个API响应主体特别大或解析时间特别长)。中间步骤的项目之间应该没有顺序依赖性。

应该注意,我正在使用节点10,因此我确实有异步迭代器/ for await。我最接近的尝试看起来像这样(假设它在异步函数上下文中):

const valuePromises = [];

const delaySequence = itemArray.reduce(async (sequence, item) => {
  await sequence;
  const valuePromise = fetch(item.url)
    .then(step1)
    .then(step2)
    .then(step3);
  valuePromises.push(valuePromise);
  return sleep(1000);  // promisified setTimeout
}, Promise.resolve());

// If I don't do this the valuePromises array won't be fully populated:
await delaySequence;

for await (const value of valuePromises) {
  await appendToFile(value);
}

除了违反上面的“ a”点之外,这是可行的,因为它必须等到所有获取操作都被触发后才能开始追加到文件中。

我曾尝试使用异步生成器,但无法提出更好的选择。

我已经考虑过使用流,它似乎适合这种任务。他们将解决顺序问题(先进先出)并允许一定程度的并行性。但是,它们的局限性在于,某个项目不能在更早的项目之前通过管道的中间阶段,这违反了“ b”。我也不知道让流与基于Promise的API交互有多么容易。

有人知道如何实现吗?

2 个答案:

答案 0 :(得分:1)

我认为这可以实现您想要的...为“ appendToFile”序列提供单独的Promise“链”

let writeSequence = Promise.resolve();

const delaySequence = itemArray.reduce(async (sequence, item) => {
  await sequence;
  const valuePromise = fetch(item.url)
    .then(step1)
    .then(step2)
    .then(step3);
  writeSequence = writeSequence.then(() => valuePromise.then(appendToFile));
  return sleep(1000);  // promisified setTimeout
}, Promise.resolve());

很抱歉,其中有一个流浪无效的await-现在消失了

答案 1 :(得分:1)

我遇到了同样的问题,因此决定创建自己的框架,使这种转换变得简单。它叫做scramjet,可以满足您的需求。

您的代码看起来像这样:

DataStream.from(itemArray).
    .setOptions({maxParallel: 8}) // so many executions will run in parallel
    .map(item => fetch(item.url)) // promises are automatically resolved
    .map(step1)                   // map works like on array
    .map(step2)
    .map(step3)
    .map(async x => (await sleep(1000), x))
    .stringify(someSerializer)    // I guess you would stringify your data here
    .pipe(fs.createWriteStream(path, {flags: "a+"}))
;

这是您的整个程序。

Scramjet会在可能的情况下生成承诺链,但会在转换流接口中公开它-因此您可以将其直接管道传输到某个位置的文件,甚至直接传输到S3。

希望有帮助。 :)