串联执行一批承诺。完成Promise.all后,转到下一批

时间:2016-05-13 14:57:52

标签: javascript node.js promise batching

我有一个包含promises数组的数组,每个内部数组可以有4k,2k或500个promise。

总共有大约60k的承诺,我也可以用其他值来测试它。

现在我需要执行Promise.all(BigArray[0])

完成第一个内部数组后,我需要执行下一个Promise.all(BigArray[1]),依此类推等等。

如果我尝试执行Promise.all(BigArray)投掷:

fatal error call_and_retry_2 allocation failed - process out of memory

我需要将每个promises串行执行,而不是并行执行,我认为这就是Node所做的。我不应该使用新的库,但我愿意考虑答案!。

编辑:

以下是一段代码示例:

function getInfoForEveryInnerArgument(InnerArray) {
    const CPTPromises = _.map(InnerArray, (argument) => getDBInfo(argument));
    return Promise.all(CPTPromises)
        .then((results) => {
            return doSomethingWithResults(results);
        });
}
function mainFunction() {
    BigArray = [[argument1, argument2, argument3, argument4], [argument5, argument6, argument7, argument8], ....];
    //the summ of all arguments is over 60k...
    const promiseArrayCombination = _.map(BigArray, (InnerArray, key) => getInfoForEveryInnerArgument(InnerArray));

    Promise.all(promiseArrayCombination).then((fullResults) => {
        console.log(fullResults);
        return fullResults;
    })
}

6 个答案:

答案 0 :(得分:10)

您的问题有点错误,这可能会使这个问题以及此问题的上一版本中的某些人感到困惑。您正在尝试串行执行一批异步操作,一批操作,然后在完成后执行另一批操作。使用promises跟踪这些异步操作的结果。 Promise本身代表已经启动的异步操作。 “承诺”不是自己执行的。所以从技术上讲,你不会“连续执行一批承诺”。您执行一组操作,使用promises跟踪其结果,然后在第一批完成后执行下一批操作。

无论如何,这是一个序列化每批操作的解决方案。

您可以创建一个我通常称为next()的内部函数,它允许您处理每次迭代。当承诺通过处理一个innerArray结算时,您再次调用next()

function mainFunction() {
    return new Promise(function(resolve, reject) {
        var bigArray = [[argument1, argument2, argument3, argument4], [argument5, argument6, argument7, argument8], ....];
        //the summ of all arguments is over 60k...
        var results = [];

        var index = 0;
        function next() {
            if (index < bigArray.length) {
                getInfoForEveryInnerArgument(bigArray[index++]).then(function(data) {
                    results.push(data);
                    next();
                }, reject);
            } else {
                resolve(results);
            }
        }
        // start first iteration
        next();
    });
}

这也将所有子结果收集到结果数组中,并返回一个主声明,其解析值是此结果数组。所以,您可以使用它:

mainFunction().then(function(results) {
    // final results array here and everything done
}, function(err) {
    // some error here
});

您还可以使用.reduce()设计模式连续迭代数组:

function mainFunction() {
    var bigArray = [[argument1, argument2, argument3, argument4], [argument5, argument6, argument7, argument8], ....];
    return bigArray.reduce(function(p, item) {
        return p.then(function(results) {
            return getInfoForEveryInnerArgument(item).then(function(data) {
                results.push(data);
                return results;
            })
        });
    }, Promise.resolve([]));
}

这比第一个选项创建了更多的同时承诺,我不知道这对于如此大的一组承诺是否是一个问题(这就是我提供原始选项的原因),但是这个代码更清晰,概念是方便用于其他情况。

仅供参考,我们为您提供了一些承诺附加功能。在Bluebird promise library(这是一个使用promises进行开发的很棒的库)中,它们为此Promise.map()创建了:

function mainFunction() {
    var bigArray = [[argument1, argument2, argument3, argument4], [argument5, argument6, argument7, argument8], ....];
    return Promise.map(bigArray, getInfoForEveryInnerArgument);

}

答案 1 :(得分:1)

此外,如果原始数组不是promises而是应该处理的对象,那么使用Array.prototype.map()Array.prototype.slice()Promise.all()的组合可以在没有外部依赖的情况下完成批处理:

// Main batch parallelization function.
function batch(tasks, pstart, atonce, runner, pos) {
  if (!pos) pos = 0;
  if (pos >= tasks.length) return pstart;
  var p = pstart.then(function() {
    output('Batch:', pos / atonce + 1);
    return Promise.all(tasks.slice(pos, pos + atonce).map(function(task) {
      return runner(task);
    }));
  });
  return batch(tasks, p, atonce, runner, pos + atonce);
}

// Output function for the example
function output() {
  document.getElementById("result").innerHTML += Array.prototype.slice.call(arguments).join(' ') + "<br />";
  window.scrollTo(0, document.body.scrollHeight);
}

/*
 * Example code.
 * Note: Task runner should return Promise.
 */
function taskrunner(task) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      output('Processed:', task.text, 'Delay:', task.delay);
      resolve();
    }, task.delay);
  });
}

var taskarray = [];
function populatetasks(size) {
  taskarray = [];
  for (var i = 0; i < size; i++) {
    taskarray.push({
      delay: 500 + Math.ceil(Math.random() * 50) * 10,
      text: 'Item ' + (i + 1)
    });
  }
}

function clean() {
  document.getElementById("result").innerHTML = '';
}

var init = Promise.resolve();
function start() {
  var bsize = parseInt(document.getElementById("batchsize").value, 10),
    tsize = parseInt(document.getElementById("taskssize").value, 10);
  populatetasks(tsize);
  init = batch(taskarray.slice() /*tasks array*/ , init /*starting promise*/ , bsize /*batch size*/ , taskrunner /*task runner*/ );
}
<input type="button" onclick="start()" value="Start" />
<input type="button" onclick="clean()" value="Clear" />&nbsp;Batch size:&nbsp;
<input id="batchsize" value="4" size="2"/>&nbsp;Tasks:&nbsp;
<input id="taskssize" value="10" size="2"/>
<pre id="result" />

答案 2 :(得分:1)

@jfriend00只需将async/awaitreduce结合使用,即可添加答案:

function runPromisesInSeries(bigArray, getInfoForEveryInnerArgument) {
  try {
    return bigArray.reduce(async (acc, cItem) => {
      const results = await acc
      const data = await getInfoForEveryInnerArgument(cItem)
      results.push(data)
      return results
    }, Promise.resolve([]))
  } catch (err) {
    throw err
  }
}

答案 3 :(得分:1)

从2020年10月开始的答案。Async / await简短地说:仅10个代码行+ JSDoc。

/**
 * Same as Promise.all(), but it waits for the first {batchSize} promises to finish
 * before starting the next batch.
 *
 * @template A
 * @template B
 * @param {function(A): B} task The task to run for each item.
 * @param {A[]} items Arguments to pass to the task for each call.
 * @param {int} batchSize
 * @returns {B[]}
 */
async promiseAllInBatches(task, items, batchSize) {
    let position = 0;
    let results = [];
    while (position < items.length) {
        const itemsForBatch = items.slice(position, position + batchSize);
        results = [...results, ...await Promise.all(itemsForBatch.map(item => task(item)))];
        position += batchSize;
    }
    return results;
}

答案 4 :(得分:0)

你可以递归地做,例如在这里我需要在mongo中放入大约6万个文件,但它太大了,一步完成它,因此我拿了1k文件,发送到mongo,之后是我完成了另外1k文件等。

exports.rawRecursive = (arr, start) => {
        //ending condition
        if (start > arr.length) {
            return;
        }

        Rawmedicament.insertManyAsync(_.slice(arr, start, start + 1000)).then(() => {
            //recursive
            exports.rawRecursive(arr, start + 1000);
        });
};

如果你想注意,当一切都完成后,你可以在结束条件下进行回调,或者如果你喜欢Promises,你可以在那里调用resolve()。

答案 5 :(得分:0)

动态地处理更多的诺言

一个简单的实现,您可以批量执行任务以并行运行并动态添加任务:

class TaskQueue {
  constructor ({
    makeTask,
    initialData = [],
    getId = data => data.id,
    batchSize = 15,
    onComplete = () => {},
  }) {
    if (!makeTask) throw new Error('The "makeTask" parameter is required');

    this.makeTask = makeTask;
    this.getId = getId;
    this.batchSize = batchSize;
    this.onComplete = onComplete;
    this.queue = new Map();

    this.add(initialData);
  }

  add(...data) {
    data.forEach(item => {
      const id = this.getId(item);
      if (this.queue.has(id)) return;

      this.queue.set(id, item);
    });

    // running automatically on create or additional items added
    this.runNextBatch();
  }

  runNextBatch () {
    if (this.queueStarted) return;
    if (this.queue.size === 0) return;

    this.queueStarted = true;
    const currentBatchData = Array.from(this.queue.values()).slice(0, this.batchSize);

    const tasks = currentBatchData.map(data => {
      const id = this.getId(data);

      // Have some error handling implemented in `makeTask`
      this.makeTask(data)
        .finally(() => this.queue.delete(id));
    });

    return Promise.all(tasks)
      .then(() => {
        this.queueStarted = false;
        this.runNextBatch();
      })
      .finally(() => {
        this.queueStarted = false;
        if (this.queue.size === 0) this.onComplete();
      });
  }
}

// Usage
const lotOfFilesForUpload = [{ uri: 'file://some-path' }, { uri: 'file://some-other-path' }];

const upload = (file) => console.log('fake uploading file: ', file);

const taskQueue = new TaskQueue({
  initialData: lotOfFilesForUpload,
  getId: file => file.uri,
  makeTask: file => upload(file),
  onComplete: () => console.log('Queue completed'),
});

// You can add more tasks dynamically
taskQueue.add({ uri: 'file://yet-another-file' });