我对Node.JS有点陌生,并且正在尝试在Node.JS中编写一个“同步”循环,理论上该循环应如下:
问题是,我在回调和函数的异步/同步本质之间迷失了。例如。 ids.push无法提供预期的结果。
编辑:此外,由于项目限制,我目前绑定到Node 6.9。
代码暂定如下:
function processAll( objectArray, callback ) {
let ids = [];
// Loop on all data
objectArray.forEach( ( obj ) => {
// From the second iteration on,
// objects are children of first obj
if( ids.length ) {
obj.parent_id = ids[0];
}
someLib.doSomething( obj, ( err, result ) => {
if( err ) {
return callback( err );
}
// This won't work, of course
ids.push( result );
});
});
return callback( null, ids );
}
答案 0 :(得分:1)
另一种回应,但方法略有不同。
正如您正确提到的那样,正如前面的一些回答中所述,Array.prototype.forEach不是异步感知的。而不是等待项目准备就绪再跳入下一个迭代,它只是尽快调用所有项目。
使用promise是一个很好的方法,但是它需要您知道如何使用promise,以及如何将旧式回调函数转换为promise。这已经存在于另一个答案中,因此我将不作解释。
我可以看到您的代码没有使用Promise,因此提供Promise方法比帮助更令人困惑;取而代之的是,我会为您提供一个已使用多年且经过实战测试的库的选项:Async。它使您可以非常轻松地轻松地执行异步操作,而无需费心处理这些情况。
使用npm install async
安装异步后,您实际上可以使用此代码段在终端中查看结果。另外,我在模拟您的someLib.doSomething并假设它是异步操作。
// Saved this code to a file named sync-loop.js for tests.
const async = require('async');
const items = [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }];
const someLib = {
doSomething(obj, callback) {
console.log('someLib.doSomething', obj);
const randomValue = parseInt(Math.random() * 1000, 10);
setTimeout(() => {
callback(null, randomValue);
}, randomValue);
},
};
function processAll(objectArray, processAllCallback) {
async.reduce(objectArray, [], (ids, item, reduceCallback) => {
if (ids[0]) {
item.parent_id = ids[0];
}
someLib.doSomething(item, (err, result) => {
if (err) {
reduceCallback(err);
}
ids.push(result);
reduceCallback(null, ids);
});
},
processAllCallback
);
}
processAll(items, (err, ids) => console.log(ids));
运行此命令可以使我对此做出类似的响应:
$ node sync-loop.js
someLib.doSomething { name: 'foo' }
someLib.doSomething { name: 'bar', parent_id: 145 }
someLib.doSomething { name: 'baz', parent_id: 145 }
[ 145, 179, 816 ]
答案 1 :(得分:0)
我想我知道出什么问题了。我假设函数someLib.doSomething是异步的,这意味着JS在继续下一行之前不会等待它完成。这意味着您的函数将在代码有时间从数据库获取所有值之前返回。查看Promises,它们对于清理异步代码非常有帮助。如果您提供更多源代码,我也许可以帮助您重构它。
答案 2 :(得分:0)
forEach循环可能已经在完成数据库回调之前完成,这意味着您将不会获得父ID来设置为子对象的属性。为确保这一点,您必须在数据库回调后开始循环其余数组。有多种方法可以实现此目的,下面是一种快速而肮脏的方法。
function processAll( objectArray, callback ) {
let ids = [];
let parent = objectArray[0]; // the first item in the array
someLib.doSomething( parent, ( err, result ) => {
if( err ) {
return callback( err );
}
// now you have an ID from your callback and can be pushed into ids variable
ids.push( result );
// start an oldskool for loop from index 1 instead of forEach
for(let i=1; i < objectArray.length; i++){
objectArray[i].parent_id = ids[0]
}
return callback( null, ids );
});
}
希望这会有所帮助
答案 3 :(得分:0)
如果 library(data.table)
setDT(df_red)
all_eans <- df_red[, unique(ean)]
k <- lapply(all_eans, function(x) {
df_red[basket_nr %in% df_red[ean == x, unique(basket_nr)],
.N,
by = ean][order(-N)][2:13]
}
)
names(k) <- all_eans
k <- k[sapply(k, nrow) == 12]
是同步,则您的代码有效。异步时可能会有问题,因为它可能在第一个元素完成之前处理第二个元素。
如果您不想在插入其他项之前先确保已处理完第一项,则必须单独添加它,然后将其他项添加到其回调方法中。伪代码看起来像这样:
someLib.doSomething()
有效的同步代码示例:
someLib.doSomething(firstElem, processOtherElems);
function processOtherElems() {
object.forEach(obj => someLib.doSomething(obj, callbackForEachObjectCreated);
}
答案 4 :(得分:0)
您可以考虑使用以下构造代替objectArray.forEach(),因为这样可以避免使用回调:
for(let obj of objectArray){
//..do stuff..
}
此外,如果您的someLib.doSomething也是异步的并且返回Promise-您可以考虑使整个方法异步,并只是 await 以求结果。这样,您将确保迭代会一次又一次地进行。
async function processAll( objectArray, callback ) {
let ids = [];
for(let obj of objectArray){
if( ids.length ) {
obj.parent_id = ids[0];
}
let resultDoSomething = await someLib.doSomething( obj );
if( resultDoSomething.err ) {
return callback( resultDoSomething.err );
}
ids.push( resultDoSomething.result );
}
return callback(null, ids);
}
答案 5 :(得分:0)
使用异步/等待和promisify
const util = require('util');
async function processAll([parent, ...children]) {
let childIds = [];
const doSomething = util.promisify(someLib.doSomething.bind(someLib));
const parentId = await doSomething(parent);
for (const child of children) {
child parent_id = parentId;
const childId = await doSomething(child);
childIds.push(childId);
}
return [parentId, ...childIds];
}
请注意,代码已简化为多余的注释。请注意,回调已删除-我们可以简单地返回结果,承诺的doSomething
引发的任何错误将自动传播到调用方。
说到哪,调用代码将更改为
processAll(objects, (err, ids) => {
if (err) {
console.error(err);
} else {
console.log(ids);
}
});
收件人
processAll(objects)
.then(ids => {
console.log(ids);
})
.catch(err => {
console.error(err);
});