我有一个包含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;
})
}
答案 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" /> Batch size:
<input id="batchsize" value="4" size="2"/> Tasks:
<input id="taskssize" value="10" size="2"/>
<pre id="result" />
答案 2 :(得分:1)
@jfriend00只需将async/await
与reduce
结合使用,即可添加答案:
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' });