【问题标题】:Is this a proper async/await implementation when the nested promises are literally thousands?当嵌套的承诺实际上是数千时,这是一个适当的 async/await 实现吗?
【发布时间】:2021-11-19 20:19:37
【问题描述】:

关于:https://github.com/gvasquez95/binance-trader/issues/8

我担心实际的“内部”等待(在 getPriceChange 的每次调用中)不会阻止此方法在外部循环 (getPriceChanges) 上的大约 1.5k 调用的异步调用。好像它是阻塞的,那么循环将是串行处理。

从性能视图和时序来看,我想说它按预期工作,因为完成整个过程只需要几秒钟,但是,如果可能的话,我想要一个假设的编程 ACK。

为那些不想进入 GitHub 的人屏蔽代码,并且更喜欢这里的代码更明显:

外循环:

async function getPriceChanges (since, breakpoint) {
  const changes = []
  for (const [i, ticker] of tickers.entries()) {
    if (!ticker.includes('BIDR') && !ticker.includes('BIFI')) {
      changes.push(getPriceChange(ticker, since, breakpoint))
    }
  }
  const res = await Promise.all(changes)
  let symbol
  let maxChange = 0
  let slope = 0
  for (const ticker of res) {
    if (ticker.percentChange > maxChange && ticker.slope > Math.pow(10, -1 * NUM_DECIMALS)) {
      maxChange = ticker.percentChange
      symbol = ticker.symbol
      slope = ticker.slope
    }
  }
  return { symbol, maxChange: Math.round(100 * maxChange) / 100, slope: round(slope, NUM_DECIMALS) }
}

内部getPriceChange函数:

async function getPriceChange (symbol, since, breakpoint) {
  const params = {
    TableName: DYNAMODB_TABLE,
    ProjectionExpression: '#timestamp, price',
    KeyConditionExpression: 'symbol = :symbol and #timestamp > :timestamp',
    ExpressionAttributeNames: {
      '#timestamp': 'timestamp'
    },
    ExpressionAttributeValues: {
      ':symbol': symbol,
      ':timestamp': since
    }
  }
  const res = await documentClient.query(params).promise()
  const prev = []
  const recent = []
  const trendData = []
  for (const data of res.Items) {
    if (data.timestamp < breakpoint) {
      prev.push(data.price)
    } else {
      trendData.push({ x: trendData.length, y: data.price })
      recent.push(data.price)
    }
  }
  let sumPrev = 0
  let sumRecent = 0
  for (const price of prev) { sumPrev += price }
  for (const price of recent) { sumRecent += price }
  const avgPrev = sumPrev / prev.length
  const avgRecent = sumRecent / recent.length
  const trend = createTrend(trendData, 'x', 'y')
  return { symbol, percentChange: (100 * (avgRecent - avgPrev)) / avgRecent, slope: trend.slope }
}

更新: 在 Node.js 14.x AWS Lambda 环境中,函数执行大约需要 30 秒,配置有 624 MB RAM,从成本角度来看,DynamoDB 查询 (ReadRequests) 成本和 Lambda 执行都是实际 DynamoDB Batch 的 1/50放置操作(WriteRequest),因此优化查询应该是一个需要的目标。

【问题讨论】:

  • 我认为主要要做的是找到一种方法来进行 one 查询以返回记录列表,而不是进行数千次查询。仅网络延迟,即使查询花费 0 时间,也会很严重。
  • 您是否要在循环中顺序执行调用?我不确定你到底关心什么。
  • @Pointy 不幸的是,考虑到底层数据结构、数据存储在 DynamoDB 中的方式以及选择/需要的主键/排序键,使用单个查询对 bd 引擎来说是过度杀伤力,即使我甚至不能 100% 确定它可以完成,但既然你提到了它,我会看看我能做些什么。
  • 您的循环中没有await。所有这些电话都将开始。没有内在等待这样的东西。你必须await getPriceChange(....) 来序列化它。按照您的方式,每个 getPriceChange 调用都会运行到它的第一个 await 并返回一个承诺。结果是许多documentClient.query 电话在进行中,并且您有很多承诺。 (如你所知,因为你必须Promise.all 很多)
  • @gvasquez 你可能应该在你的 dynamodb 中添加一个二级索引。正如 Pointy 所说,一次处理 1.5k 个网络请求并不好。

标签: javascript node.js asynchronous async-await promise


【解决方案1】:

您的循环正确地是异步的。而且您似乎担心同时进行多少查询。在您的情况下,您打算进行许多并发查询,并且您已经这样做了。

说明

在订购 10 个披萨的简单示例中,考虑以下数组:

同时

这是一组披萨订单。我们可以等待所有 10 个订单来获得一组 10 个比萨饼。假设当晚至少有 10 名送货员在工作,那么您很有可能会在 30 分钟内拿到所有的比萨饼。

for (let i = 0; i < 10; ++i) {
   orders.push(orderPizza());
}
pizzas = await Promise.all(orders);

依次

这是一组比萨饼。每个订单在发出时都在等待。如果每个比萨饼在 30 分钟内送达或者它是免费的,这可能会导致等待最后一个比萨饼大约 5 小时(10 * ~30 分钟),因为下一个比萨饼要等到前一个比萨饼到达时才会订购。 (在这种情况下不需要最终的Promise.all,因为在继续循环之前每个承诺都已经解决了。)

for (let i = 0; i < 10; ++i) {
   pizzas.push(await orderPizza());
}

【讨论】:

  • 简单明了的解释,披萨参考的奖金;)
【解决方案2】:

不确定我是否完全理解您的问题。您对代码的假设似乎是正确的——如果 1500 个承诺改变了执行模型中的任何内容,我(以及我假设的许多其他人)会非常惊讶。

作为一般说明,如果您需要对 Promise 执行进行更多控制,我建议您查看 Sindre Sorhus' collection of promise helpers。您会找到用于限制并发、允许承诺失败、异步映射……等等的工具。

【讨论】:

  • 给那些承诺帮助者的小费,谢谢@panepeter
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-27
相关资源
最近更新 更多