简体   繁体   中英

Javascript async/await execution order problem in for...of, for await...of and Promise.all

For each object (product) in an array (products), I'm getting the price from a mongoose database. That value (prodDB.price) is summed to the "amount" variable initialized as 0 before the loop.

I tried 3 solutions explained in other questions, with:

  • for of
  • for await of
  • Promise.all

--- for of ---

 let amount = 0;
     
 for (const product of products) {
     await Product.findById(product._id).exec((err, prodDB)=> {
         amount += product.count * prodDB.price;
         console.log("Current amount", amount);
     });
 };
 console.log("Amount total", amount);

--- for await of ---

 let amount = 0;
     
 for await (const product of products) {
     Product.findById(product._id).exec((err, prodDB)=> {
         amount += product.count * prodDB.price;
         console.log("Current amount", amount);
     });
 };
 console.log("Amount total", amount);

--- Promise.all ---

let amount = 0;

await Promise.all(products.map(async (product)=> {
    await Product.findById(product._id).exec((err, prodDB)=> {
    amount += product.count * prodDB.price;
    console.log("Current amount", amount);
    });
}));

 console.log("Amount total", amount);

The result of any of the previous versions of the code is always the same, and unexpected, particularly the order in which console.log happens:

Amount total 0
Current amount 10.29
Current amount 17.15
Current amount 18.29
Current amount 19.45
Current amount 43.2

Can you please help? Thank you very much!

The problem is that you are mixing "callback" mode and "await" mode. Either await the operation, or give it a callback, otherwise it gets messy.

for (const product of products) {
    let prodDB = await Product.findById(product._id).lean().exec(); // add lean() to get only JSON data, lighter and faster
    amount += product.count * prodDB.price;
    console.log("Current amount", amount);
};

However, this is very expensive, because if you have 10 products, you call your database 10 times. Better call it only once and fetch all _id in one go.

let allIds = products.map(p => p._id),
    prodDBs = await Product.find({
        _id: {
            $in: allIds
        }
    })
    .lean()
    .exec()

const amount = prodDBs.reduce((a,b) => a.price + b.price, 0)

I would use Promise.all so you can run all the DB request in parallel and wait for all them to complete, instead of running all of them in series. The only issue I think is with .exec() not returning a Promise , just use findById() that returns a Promise, try this code:

let amount = 0;

await Promise.all(products.map(async (product)=> {
  const prodDB = await Product.findById(product._id)
  amount += product.count * prodDB.price
}));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM