【问题标题】:Creating Promises in for...of loop instead forEach?在 for...of 循环中创建 Promise 而不是 forEach?
【发布时间】:2017-12-09 10:14:52
【问题描述】:

我想并行执行一个 Promises 数组,然后等到所有 Promise 都执行完毕。

这行得通:

var promises = [];

objects.forEach(function(object) {

    let promise = new Parse.Promise.as()
    .then(
        function() {
            return destroy(object);
        }
    );

    promises.push(promise);
});

return Parse.Promise.when(promises);

但是,如果我使用 for (object of objects) {...} 而不是 objects.forEach(function(object) {...}); 它将不起作用。对于数组中的每个 Promise,destroy(object); 在数组中的第一个对象上执行:

var promises = [];

for (object of objects) {

    let promise = new Parse.Promise.as()
    .then(
        function() {
            return destroy(object);
        }
    );

    promises.push(promise);
});

return Parse.Promise.when(promises);

这是为什么呢?

【问题讨论】:

  • 显示不起作用的代码

标签: javascript foreach promise parse-server for-of-loop


【解决方案1】:

是的,您忘记将 object 变量声明为循环体的本地变量(另请参阅 canonical explanation):

var promises = [];
for (let object of objects) {
//   ^^^
    promises.push(new Parse.Promise.as().then(function() {
        return destroy(object);
    }));
}
return Parse.Promise.when(promises);

当然你也不应该这样做,你应该使用map

var promises = objects.map(function(object) {
    return new Parse.Promise.as().then(function() {
        return destroy(object);
    });
});
return Parse.Promise.when(promises);

【讨论】:

  • 为什么我也不应该使用 for..of 循环?
  • @Manuel 因为.map 是通过转换从旧数组创建新数组的规范方法(只要需要 1:1 映射)
  • @Manuel map 更简洁,更容易出错,更具可读性
  • 太棒了,谢谢大家,很好的链接@Bergi,它完全解释了这个概念。
【解决方案2】:

编辑: 本来我有点迷茫,复制粘贴了一些我写的东西以备不时之需。对于那个很抱歉。

就像@Bergi 所说,如果您需要来自objects 数组的Promise 数组,那么通常最好使用.map() 转换现有数组。如前所述,这看起来像:

const getPromises = (objects) => {
  return objects.map(object => new Parse.Promise.as()
    .then(() => {
      return destroy(object);
    })
  );
}
// No 'temp' array needed, and most important: promise variables aren't 'lost'

// Then you can similarly use this pattern:
return Parse.Promise.when(getPromises(objects));
// Or this:
return Promise.all(getPromises(objects));

我最初的答案(如下)本身就有点模棱两可,希望我上面的答案能给它更多的背景。 :)


我避免使用for 循环或Array.forEach。当我发现.forEach() 时,我将它用于一切,假设它是我for loops 混乱的答案。我开始了解到两者在 99.99% 的情况下都是代码气味。原因:它们通常需要临时数组,并使嵌套循环非常尴尬。

虽然.map() 在您的数组大小相同时(1:1 或 20:20)在很大程度上解决了这个问题,但其他 Array 方法对于 20:1 的非对称变换很有用(例如:将 20 个产品的成本总计为1 个数字,或找到列表中最大的交易):

.map    - use for 1:1 array transforms, as @bergi suggests.
.reduce - useful for transforming 1 array into ANYTHING else. Need a sum or subtotal? Or results grouped by day? Use .reduce().
.filter - return only items which result in a `true` result
.find   - use to avoid full array scans when only 1 item must be returned. 
.some   - exit array scan returning true at first opportunity

let downloadedMsgs  = emails.map(m => downloadBody(m))
let recipientsCount = emails.reduce((count, m) => count + m.to.length, 0)
let onlyRecentMsgs  = emails.filter(m => m.isNew)
let aRecentMsg      = emails.find(m => m.isNew)
let hasNewMessage   = emails.some(m => m.isNew)
// (Notice last 3 identical predicate fn's with different uses: aka pluripotency)

代码中的另一个问题是丢失 Promise 的风险!这一点很重要,如果您的数组中可能有几十个或几百个对象,那么在循环中触发 HTTP 请求将非常不可靠(最终您会耗尽可用的套接字)。限制这种情况的最佳方法是返回你的 Promise。

【讨论】:

  • 嗨,Dan,您所说的“99.99% 的时间都闻到代码味”到底是什么意思?
  • 好问题......从一个简单的例子来看并不明显,但方法很简单......见编辑。
  • 你的意思是'destroy'在'Parse.Promise.when'之前执行?
  • 是的。 Destroy 应该被设计成一个 clousure 来完成对象的工作,也许返回 destroy() 方法,这样调用代码就可以在完成后运行它:users.map(u => u.destroy())
  • 为什么你认为destroy 没有被Parse.Promise.when() 调用?据我了解,destroy 仅在链中的第一个 Promise 调用它时调用,即Parse.Promise.when();。所以顺序应该是Parse.Promise.when() -> Parse.Promise.as() -> destroy()
猜你喜欢
  • 2018-11-14
  • 2018-11-21
  • 2012-01-27
  • 2020-07-13
  • 2013-07-20
  • 2018-08-20
  • 2016-07-05
相关资源
最近更新 更多