将变量重用到循环中-NodeJS

时间:2018-08-14 13:25:33

标签: javascript node.js foreach

我对Node.JS有点陌生,并且正在尝试在Node.JS中编写一个“同步”循环,理论上该循环应如下:

  1. 按顺序遍历对象数组
  2. 对每个对象应用一个函数(实际上 在数据库中创建对象并返回其唯一ID)
  3. 创建第一个对象后,将其ID用作所有其他对象的parent_id。

问题是,我在回调和函数的异步/同步本质之间迷失了。例如。 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 );
    }

6 个答案:

答案 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);
     });