【问题标题】:How to optimize this block of code since its async自异步以来如何优化此代码块
【发布时间】:2019-04-14 14:03:48
【问题描述】:
var orderItems = userData.shoppingcart;

var totalPrice = 0;
userData.shoppingcart.forEach(function(itemName, i){
   _data.read('menuitems', itemName, function(err, itemData){
       if(!err && itemData)
       {
          totalPrice += itemData.price;
          if(++i == userData.shoppingcart.length){

              // Only here when all the itemNames have been read I should continue

          }
       }
    });
});

如您所见,对 _data.read 的调用是异步的,因为我正在从文件中读取数据。

但我需要等待读取所有文件,以便计算总价格。这就是为什么我设置这个条件 [ ++i == userData.shoppingcart.length ]。

一般来说,我是 javascript 和 nodejs 的新手,从来都不是很擅长编程,但无论如何我的观点是,我确信这不是一个好方法,如果同时读取两个文件会怎样,然后该条件从未运行过或 totalPrice 的计算做得不好?

有人可以给我一些指导吗? 提前谢谢!

【问题讨论】:

  • "如果同时读取两个文件会怎样" - 这不正是您想要的吗?
  • 基本上,是的,这就是想法 - 计算完成了多少任务,然后继续。但是有很多陷阱,例如正确考虑错误或 0 次迭代的边缘情况(例如在空输入上),因此您最好使用一些经过验证的现有代码,而不是重新发明轮子。了解承诺和Promise.all
  • 是的,它们都可以同时读取,但我希望通量仅在全部读取后才能继续,因此我拥有正确且准确的 totalPrice 变量数量。是的,我听说过 Promise,但不知道如何使用它,将对此进行一些搜索,谢谢@Bergi

标签: javascript node.js asynchronous conditional


【解决方案1】:

鉴于您没有指定这是在什么上下文中,我将做一些假设:

  1. 我认为_data.read() 尚不支持返回承诺。
  2. 我假设此代码需要调用回调函数或返回承诺。

我(有点天真)的方法是:

  1. orderItems 映射到该项目的每个价格的 Promise 中。
  2. 将结果映射到总数中
  3. 返回结果承诺或调用回调。

这是一个带注释的示例,说明您可以如何做到这一点:

// Promise.all takes an array of promises and returns
// a new promise that completes when all the promises in the array are complete.
const promiseOfPrices = Promise.all(
    // Here we map all the items in the shopping cart into promises for each of their prices
    userData.shoppingcart.map(

        // The Promise object takes a single function that it will immediatly call with functions to resolve or
        // reject the promise. I suggest reading up on Promises if you're not familiar with them.
        itemName => new Promise((resolve, reject) => {
            // Here, we have a `reject` and `resolve` function that will each complete the new promise,
            // either in success or error respectfully.

            // Do the actual read of your file or database or whatever
            _data.read('menuitems', itemName, (err, itemData) => {
                // If there was an error, reject this promise.
                if (err) reject(err);

                // Otherwise, we're successful and we resolve with the price of the item
                else resolve(itemData.price);
            });
        })
    )
);

// Now, we have a promise (promiseOfPrices) for all the prices of the items in the cart. We use `then` which will
// perform a transform on the result, much like the `map` function on an Array.

const promiseOfTotal = promiseOfPrices.then(
    // Here we use the `Array.reduce` function to succinctly sum the values in the array.
    arrayOfCartItemPrices => arrayOfCartItemPrices.reduce(
        // For each item, reduce calls our function with the current sum and an item in the array. We produce a new
        // sum by adding the sum to the item price.
        (sum, itemPrice) => sum + itemPrice,

        // This is the initial value for sum, 0.
        0
    )
);

如果你可以返回一个promise,而你只想返回总数,那么

return promiseOfTotal;

如果你有一个期望 (err, result) 的回调,那么做这样的事情:

promiseOfTotal.then(
    result => callback(null, result),
    error => callback(error, null),
)

如果你需要对结果做更多​​的工作,你可以用另一个 then 来做:

promiseOfTotal.then(
    priceSum => {
        // Do work here
    },
    // Optionally handle errors here:
    error => {
        // Do error handling here.
    }
)

请注意,通过使用承诺、箭头函数和数组推导(mapreduce),我们可以避免复杂且难以跟踪的变量和循环突变。这是一种“函数式”编程风格,虽然有点难学,但通常比其他方法更安全、更干净。我建议花点时间了解它是如何工作的,因为它可以帮助您编写在处理异步等复杂问题时不太可能出现错误的代码。

最后,我还没有运行这段代码。它很可能有一两个错误。随时要求澄清或如果它不起作用。

祝你好运!

附:我没有使用async/await,因为我认为它不如直接使用 Promises 清晰,并且无论如何都需要使用Promise.all 来实现并行性。在这里完全可以使用它们来获得良好的效果,但我将把它作为练习留给 OP。

【讨论】:

  • 嗨@YonaAppletree 非常感谢您的详细回答,从未使用过承诺,现在是学习它们的好时机!我相信我明白了逻辑并大致理解了您的代码。我刚收到一个问题,promiseOfPrices 将是所有价格的数组,对吗?在这种情况下,如果有一个拒绝,我会知道,因为数组“promiseOfPrices”的长度将与我期望的不同,对吗?澄清一下,我现在接受你的详细回答:)
  • @TiagoM Promises 是一个非常棒的抽象,我确实建议阅读文档,因为在一篇 SO 帖子中我能说的比我多。回答你的问题:没有。 Promise.all 产生一个新的 Promise,当 all Promise 传递给它时将 resolve 解决(成功)或 reject任何一个他们 拒绝(失败)。因此,promiseOfPrices 将是一个 Promise,它resolves 包含所有价格的数组。它永远不会用不同长度的数组解析。如果任何一个失败,promiseOfPrices 承诺将拒绝(失败)该第一个错误
  • 关于 Promises 的 google 入门教程看起来不错:developers.google.com/web/fundamentals/primers/promises
  • 换一种说法,如果你想知道是否出了什么问题,看看我给出的关于做更多工作的例子,.then() 有两个参数,或者你可以使用.catch() 来捕获错误.
  • 您好@YonaAppletree 只是想路过并感谢您提供的文档和解释!我终于按照我需要的方式工作了,使用 promises.All 和 Then 子句,我也会阅读你发给我的文档,因为它听起来非常有据可查!再次感谢您的所有帮助和时间!一切顺利;)
【解决方案2】:

这是您可以使用 Promise (async/await flavor) 按顺序读取项目的方式:

var orderItems = userData.shoppingcart;

let totalPrice = 0;
for (let itemName of userData.shoppingcart) {
  const itemData = await _data.read('menuitems', itemName);
  totalPrice += itemData.price;
}

此示例假定_data.read 支持async/await。但是,如果没有,它可以使用 nodejs 的 util 模块中的 promisify 函数“承诺”

【讨论】:

  • 是的,这是按顺序运行的,而不是并行运行,这可能是不可取的。我对async/await 的最大问题之一是它似乎鼓励更少的并行代码。
  • @bergi 是的,在这种情况下假定async/await,将其添加到答案中。
  • @YonaAppletree 我经常发现优化可读性而不是性能是可取的。
  • @Christian 当然。但是有表现,也有表现。在这种情况下,您将采用可能是 O(1) 的东西并将其转换为 O(N)。这与使用推导时创建的中间数组不同。 O(1) vs O(N) 是巨大的,特别是如果有网络或文件读取或其他什么。至少值得仔细考虑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-09
  • 2021-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-29
相关资源
最近更新 更多