在承诺的 then() 中打破 for 循环

Break for loop inside then() of a promise

我遇到了一个奇怪的情况,我想在收到 Rx Promise 的结果并进行一些检查后中断 for 循环。我有以下内容:

function getDrift(groups) {
    var drift = {};
    groups.forEach(function(group) {
        if(group.type === 'something') {
            for(var i = 0; i < group.entries.length; i++) {
                fetchEntry(group.entries[i].id)
                 .then(function(entry) {
                     if(entry.type === 'someType'){
                         drift[entry._id] = getCoordinates(entry);
                         // break;
                     }
                 });
            }
        }
    });
    return drift;
}

其中 fetchEntry 是 return 基于 id 的 mongodb 文档的 Promise。如果 if 检查得到满足,我想打破当前 group.entries 的循环并继续下一组。

这可能吗?

谢谢

编辑:根据要求,群组对象如下所示:

[
    {
        type: 'writing',
        entries: [{id: "someId", name: "someName"}, {id: "someId2", name: "someName2"}]
    },
    {
        type: 'reading',
        entries: [{id: "someId3", name: "someName3"}, {id: "someId4", name: "someName4"}]
    }
]

解决方案:我最终使用@MikeC 的建议进行递归和回调 return 所需的值。谢谢大家!

这是不可能的,因为 Promises 是异步的,这意味着 then won't execute until all other synchronous code completes.

如果您不想根据某些条件处理所有这些,我建议您创建一个函数,如果您想继续调用它。

(function process(index) {
  if (index >= group.entries.length) {
    return;
  }
  fetchEntry(group.entries[index])
    .then(function(entry) {
      if(entry.type === 'someType'){
        drift[entry._id] = getCoordinates(entry);
        // don't call the function again
      } else {
        process(index + 1);
      }
    });
})(0);

您传递给 then() 的函数不会在 for 循环内调用。它在 for 循环完成后被调用 (long)。这就是异步编程模型的本质。

您需要重新组织代码,以便不使用 for 循环。相反,您需要在回调中启动下一次提取,或者不适当地启动它。

PS。出于同样的原因,您也不能 return 由回调填充的对象:您的函数将 return 空对象 /before/ 调用回调


编辑: 演示,但代码未经测试:

function getDrift(groups) {
    var promise = ...;

    var drift = {};
    groups.forEach(function(group) {
        if(group.type === 'something') {
            var i = 0;

            var processEntry = (function(entry) {
                     if(entry.type === 'someType'){
                         drift[entry._id] = getCoordinates(entry);

                         // We are finished, so complete our promise with
                         // the collected data
                         promise.success(drift);
                         return;
                     }

                     // increment our position in the array
                     i += 1;

                     // check to see if we are at the end of the array
                     if (i >= group.entries.length)
                        { return; }

                     // now fetch the next entry from the array
                    fetchEntry(group.entries[i].id)
                            .then(processEntry);
                 });

            // fetch the first entry
            fetchEntry(group.entries[i].id)
                 .then(processEntry);

            } // end if

    }); // end forEach()

    return promise;
}

If the if check is satisfied, I want to break the loop on the current group.entries and continue on to the next group.

如果正确解释问题,请尝试使用 Promise.all()Array.prototype.map()

function getDrift(groups) {
  var drift = {};
  var m = 0;
  return Promise.all(groups.map(function(p) {
    // if `p` contains `5` return `null`
    // else multiply item within `p` by `10`
    return p.indexOf(5) === -1 ? p.map(function(k) {
      drift[m] = k * 10;
      ++m
    }) : null
  })).then(function(n) {
    return drift
  }, function(err) {
    console.log(err)
  })

}
// array containing `5` will not be processed
var res = getDrift([[1,2,3], [4,5,6], [7,8,9]])
.then(function(data) {
  console.log(data)
})


将上面的模式应用到 js 问题

function getDrift(groups) {
  var drift = {};
  return Promise.all(groups.map(function(group) {
    return group.type === "something" 
    ? Promise.all(group.entries.map(function(g, index) {
      return fetchEntry(g.id).then(function(entry) {
         if (entry.type === "someType") {
            drift[entry._id] = getCoordinates(entry);
            return Promise.reject(entry.type);
         }
      })
    })) 
    : null
  })).then(function() {
    return drift
  }, function(err) {
    console.log(err)
  })

}
// array containing `5` will not be processed
var res = getDrift(groups)
.then(function(data) {
  console.log(data)
}, function(err) {
  console.log(err)
})

这是可能的无需递归,但不是特别简单。

您可以组合使用:

  • forEach() 替换为 Array#map() 以将 groups 映射到一组承诺,
  • 将原来的 for 循环替换为 Array#reduce() 以构建 .then() 链,即 "breakable" 通过将其发送到错误路径。

到目前为止最简单的事情是按照问题中的代码坚持使用 drift 作为外部变量。通过 promise 链传递数据也是可能的,但代码会更复杂。

function getDrift(groups) {
    var drift = {};
    // Map groups to an array of promises.
    var promises = groups.map(function(group) {
        if(group.type === 'something') {
            // Here, replace the original `for` loop with `group.entries.reduce()` to build a .then() chain.
            return group.entries.reduce(function(p, e) {
                return p.then(function() {
                    return fetchEntry(e.id).then(function(entry) {
                        if(entry.type === 'someType') {
                            drift[entry._id] = getCoordinates(entry); // Yay!
                            throw false; // Throw almost anything here to skip the rest of the .then() chain.
                        } else {
                            return true; // Return almost anything here to carry on down the chain.
                        }
                    });
                });
            }, Promise.resolve()) // Resolved starter promise for the reduction.
            .catch(function() { // If there was a match, we end up on the error path, and need to convert to success.
                return true; // Return almost anything here.
            });
        } else {
            return true; // Return almost anything here to make a positive entry on the `promises` array.
        }
    });
    return Promise.all(promises) // Aggregate the promises.
    .then(function() {
        return drift; // and deliver the populated `drift` to the calling function
    });
}

作为 getDrift() returns 承诺,drift 仅在 .then() 回调中对调用者可用:

getDrift(groups).then(function(drift) {
    //do something with drift
});