【问题标题】:Don't we need something better than Promise.all?难道我们不需要比 Promise.all 更好的东西吗?
【发布时间】:2019-06-19 20:46:54
【问题描述】:

这是我使用 await/async 的漂亮代码

monthlyBuckets(req, res) {
  const monthlyBuckets = []
  const now = DateTime.local()
  let date = config.beginningOfTime
  while (date < now) {
    monthlyBuckets.push({
      epoch: date.toMillis(),
      month: date.month,
      year: date.year,
      actions: await redis.get(`actions_monthly_${date.year}_${date.month}`),
      interested: await redis.scard(`sinterested_monthly_${date.year}_${date.month}`),
      adventurous: await redis.scard(`sadventurous_monthly_${date.year}_${date.month}`),
      active: await redis.scard(`sactive_monthly_${date.year}_${date.month}`),
    })
    date = date.plus({month: 1})
  }
  res.status(200).json(monthlyBuckets)
}

我喜欢它,但是不并行发出这么多请求会导致请求时间接近 3 秒。

所以,这是我没有 async/await 的丑陋解决方案,只是承诺:

monthlyBuckets(req, res) {
    const monthlyBuckets = []
    const actions = []
    const interested = []
    const adventurous = []
    const active = []
    const now = DateTime.local()
    let date = config.beginningOfTime
    let entryCount = 0
    while (date < now) {
      monthlyBuckets.push({
        epoch: date.toMillis(),
        month: date.month,
        year: date.year,
      })
      actions.push(redis.get(`actions_monthly_${date.year}_${date.month}`))
      interested.push(redis.scard(`sinterested_monthly_${date.year}_${date.month}`))
      adventurous.push(redis.scard(`sadventurous_monthly_${date.year}_${date.month}`))
      active.push(redis.scard(`sactive_monthly_${date.year}_${date.month}`))
      date = date.plus({month: 1})
      entryCount++
    }
    const data = await Promise.all(actions.concat(interested).concat(adventurous).concat(active))
    for (let i = 0; i < entryCount; i++) {
      monthlyBuckets[i].actions = data[i]
      monthlyBuckets[i].interested = data[entryCount + i]
      monthlyBuckets[i].adventurous = data[entryCount * 2 + i]
      monthlyBuckets[i].active = data[entryCount * 3 + i]
    }
    res.status(200).json(monthlyBuckets)
  }
}

这不是很漂亮,但它可以在 200 毫秒内完成工作

我能有漂亮有效率吗?

【问题讨论】:

  • 你可以用await Promise.all(...)就好了。
  • 这使它的丑陋减少了 2%

标签: async-await es6-promise


【解决方案1】:

上面代码的问题是你试图:

  1. 对所有承诺使用一个 Promise.all()
  2. 一个回调中处理所有响应的输出

虽然这不是一个错误,但它可能会导致难以“阅读”代码。

代码可以写成:

while (date < now) {
  let dateData = {
    epoch: date.toMillis(),
    month: date.month,
    year: date.year,
  };
  let promiseData = Promise.all([
      dateData, // dataData is cast(made to) automatically into a promise
      redis.get(`actions_monthly_${date.year}_${date.month}`),
      redis.scard(`sinterested_monthly_${date.year}_${date.month}`),
      redis.scard(`sadventurous_monthly_${date.year}_${date.month}`),
      redis.scard(`sactive_monthly_${date.year}_${date.month}`)
  ]).then([data, actions, interested, adventurous, active] => {
      // process the data here for each month
      data.actions = actions;
      data.interested = interested;
      data.adventurous = adventurous;
      data.active = active;
      return data;
});
  monthlyBuckets.push(promiseData);
  date = date.plus({month: 1});
}

const data = await Promise.all(monthlyBuckets);
res.status(200).json(data);

改变的是

  • 对每个月的承诺进行分组
  • 处理一个月的一组承诺,而不是一起处理所有承诺并根据需要返回数据。

将 Promise 分组并没有错,例如:

Promise.all([
       Promise.all([ ...]),
       Promise.all([ ...]),
       singlePromise,
       ...
]);

处理承诺,例如:

promiseProcessed1 = promise1.then(callback1);
promiseProcessed12 = Promise.all([promise1, promise2]).then(callback2);

或重用承诺,例如:

promiseProcessed1 = promise1.then(callback1);
promiseProcessed12 = Promise.all([promise1, promise2]).then(callback2);
resultDatapromise = Promise.all([promise1, promise2, promiseProcessed1, promiseProcessed12]).then(callback2);

参考文献

【讨论】:

  • 这更容易阅读。但是会运行得更慢,它不会在上个月被处理之前检索下个月的数据,但没有真正的理由这样做。 Promise.all().then 中返回的数组参数不符合 ES6 语法的一般审美。这就是引发这个问题的原因。我认为 ES6 承诺是一个好的开始,但我意识到它还有很多需要改进的地方。
  • 它不会运行得更慢,并且“直到上个月处理完毕才检索下个月的数据”是不正确的。至于“ES6 语法的一般美学”,也许您可​​以使用“回调参数中的解构”:例如,就像这里的答案:stackoverflow.com/questions/42750254/…
  • 没错,你的代码中没有await。是的可能会写 ...then([actions,interested,adventurous,active] => ({actions,interested,adventurous,active})) 以避免索引数组
  • const data = await Promise.all(monthlyBuckets);这行是多余的
  • @JonathanR 感谢您指出这一点,您是对的。更新了代码。
【解决方案2】:

在这种情况下,分开不同的步骤可能会有所帮助。示例:

function createBucket(date, ops){
    const b = {
        epoch: date.toMillis(),
        month: date.month,
        year: date.year,
        actions: redis.get(`actions_monthly_${date.year}_${date.month}`),
        interested: redis.scard(`sinterested_monthly_${date.year}_${date.month}`),
        adventurous: redis.scard(`sadventurous_monthly_${date.year}_${date.month}`),
        active: redis.scard(`sactive_monthly_${date.year}_${date.month}`),
    }

    const promised = ['actions','interested', 'adventurous', 'active'];
    promised.forEach(p => ops.push(async () => {b[p] = await b[p]}));
}

async function monthlyBuckets(req,res){
    const monthlyBuckets = []
    const now = DateTime.local()
    let date = config.beginningOfTime

    const ops = [];
    while (date < now) {
      monthlyBuckets.push(createBucket(date,ops));
      date = date.plus({month: 1})
    }

    await Promise.all(ops);
    res.status(200).json(monthlyBuckets)
}

【讨论】:

    猜你喜欢
    • 2016-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-22
    相关资源
    最近更新 更多