【问题标题】:Iterating javascript object synchronously同步迭代javascript对象
【发布时间】:2026-01-25 19:25:02
【问题描述】:

我有一个像这样的对象:

let myObject = {
'db1': [db1_file1Id,db1_file2Id,db_1file3Id],
'db2': [db2_file1Id, db2_file2Id]
...
}

我遍历这个对象并在每次迭代中:我连接到数据库,检索文件,做一些事情并将文件保存回来。基本上是异步的东西。

  for (let prop in myObject) {
    if (myObject.hasOwnProperty(prop)) {
      doStuff(prop, myObject[prop]);
    }
  }

现在 doStuff 函数确保我有一个本地范围,因此没有不一致。但是,由于每个循环内的异步操作,执行仍然不是同步的。在继续下一个之前,我基本上需要一个数据库被完全处理。我该如何解决这个问题?

我想到的一种方法是使用递归循环。但根据我的理解,这将需要我广泛地改变我的数据结构,这在 imo 中是次优的。

let arr; //containing my data

process(x) {
 if (x < arr.length){
   //process(x+1) is called inside doStuff after asynchronous operations are complete
   doStuff(arr[x]);
 }
}

【问题讨论】:

  • @leaf 您可以使用递归或 Array.prototype.reduce 从数组创建串行承诺链。我添加为答案。
  • 您想按特定顺序处理数据库吗?
  • @leaf 排序无所谓
  • 如果顺序无关紧要,那为什么还要序列化呢?下一个 db 函数是否需要前一个的输出?
  • @HRM 预防任何类型的服务器超载

标签: javascript loops asynchronous promise synchronization


【解决方案1】:

以下将按照您的要求执行,它返回一个解析值数组。

如果其中任何一个拒绝,您要停止处理吗?如果您需要进行一些更改,现在它会拒绝如果其中任何一个拒绝并且不会继续处理它们在您的对象中的键(对象名为 myObject):

var myObject = {
  'one': ["one"],
  'two': ["two"]
};
var doStuff = arr =>
  console.log("starting:", arr[0]) ||
  Promise.resolve(arr[0]);

var [promise,result] = 
  Object.keys(myObject)
    .reduce(
      ([promise,results], key) =>
        [
          promise
          .then(
            resolve =>
              doStuff(myObject[key])
          )
          .then(
            resolve => results.push(resolve)&&resolve
          )
          .then(
            resolve => console.log("done:", resolve)
          )
          ,results
        ]
      , [Promise.resolve(), []]
    )
promise.then(
  _ => {
    console.log("done all",result)
  }
);

ayushgp 的答案使用递归,这是一个不需要更改 doSomething 的工作示例:

var myObject = {
  'one': ["one"],
  'two': ["two"]
};
var doStuff = arr =>
  console.log("starting:",arr[0]) ||
  Promise.resolve(arr[0])

var process = (arr,processFn) => {
  const rec = (arr,processFn,promise,results) =>
    arr.length === 0
      ? promise.then(_=>results)
      : promise
        .then(_ => processFn(arr[0][1]))
        .then(result=>results.push(result)&&console.log("resolved:",result))
        .then(_ => rec(arr.slice(1),processFn,promise,results));
  return rec(arr,processFn,Promise.resolve(),[]);
};
process(
  Object.keys(myObject).map(key=>[key,myObject[key]]),
  doStuff
)
.then(
  results => console.log("done all, results:",results)
);

【讨论】:

    【解决方案2】:

    一种解决方案是让doStuff 返回一个Promise,您可以通过调用then 来构建一个承诺链。

    Bluebird Promise 库通过 .each.mapSeries 提供此功能。

    您可以将其实现为:

    Promise.forEachSeries = function(array, action) {
      return array.reduce(function(prevPromise, value, index, array) {
        return prevPromise.then(function() {
          return action(value, index, array);
        });
      }, Promise.resolve());
    }
    

    你会这样使用它:

    Promise.forEachSeries(arr, doStuff);
    

    【讨论】:

      【解决方案3】:

      您可以使用Object.entries(obj) 最后提出的解决方案。例如,

      let arrProps = Object.entries(myObject);
      
      process(index) {
       if (index < arrProps.length){
         // Call the callback once you complete execution of doStuff
         doStuff(arrProps[index], () => process(index + 1));
       }
      }
      

      doStuff 内部:

      function doStuff(props, callback) {
          // Process props
          //finally in the promise of async call, on success call
          .then(callback)
      }
      

      如果您想使用 for ... in 循环,也可以使用生成器函数。

      【讨论】:

      • 您可以使用递归但无需更改doStruff,只需让流程函数应用递归即可。如果进程同时具有键和数组,您可以处理被拒绝的承诺(知道什么键被拒绝)并累积已解决的值。我认为更简单的方法是将数组简化为一个承诺。在我的答案中添加了这两种方法,但没有错误处理程序(一个拒绝,永远不会处理后的所有内容)并且解析值被忽略。
      【解决方案4】:

      以下代码可能与您的要求接近。我使用索引ij 分别循环遍历数据库和文件:

      var dbs = {
        db1: ["q", "w", "e", "r"],
        db2: ["t", "y"]
      };
      
      var names = Object.keys(dbs);
      var db, x, i = 0, j = 0;
      
      if (names.length > 0) {
        db = dbs[names[i]];
        x = db[j];
        console.log("start");
        asyncProcessing(x)
        .then(onSuccess)
        .catch(onFailure);
      }
      
      function onFailure (e) {
        console.log("[FAILURE]", e);
        console.log("end");
      }
      
      function onSuccess (xx) {
        console.log("[SUCCESS]", xx);
        j = (j + 1) % db.length; // next j
        if (j === 0) i = i + 1; // next i
        if (i < names.length) {
          db = dbs[names[i]];
          x = db[j];
          asyncProcessing(x)
          .then(onSuccess)
          .catch(onFailure);
        } else {
          console.log("end");
        }
      }
      
      function asyncProcessing (x) {
        return new Promise(function (resolve, reject) {
          setTimeout(function () {
            // force first two success then random
            if (x === "q" || x === "w" || Math.random() * 3 > 1) {
              resolve(x + x);
            } else {
              reject("Not lucky. Try again.");
            }
          }, 1000);
        });
      }

      【讨论】:

        【解决方案5】:

        Promise 对象表示异步操作的最终完成(或失败)及其结果值。你可以试试。

        $("#myPara").delay(4500).fadeOut().promise().done(function(){
         		  $("#myHeading").attr("style","display:none;")  ;
              for(var i=10;i<15;i++){
              console.log(i);
              }
        });
        console.log("Hello promise !");
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
        <p id="myPara"> Hello </p>
        <h1 id="myHeading">to be hide</h1>
          for (let prop in myObject) {
            if (myObject.hasOwnProperty(prop)) {
              var stuff= doStuff(prop, myObject[prop]).promise().done(function(){
              // Do whatever u want after completion of doStuff
             });
            }
           }
        

        Have a look at Mozila ref.

        【讨论】:

        • OP 已经在执行此操作,但现在还不是同步的,下一次 doStuff 调用将在此完成之前执行。
        • 他想停止下一次迭代,直到 doStuff 没有完成