简体   繁体   中英

How to await inside 2 map functions and retrieve documents from mongoDB

I need to perform some modification on an API that returns something like this:

[
  {
    "_id": "0000000000000000001",
    "name": "Random name",
    "categories": [
        "5eb8548330550e017f8902e6",
        "5eb2548630550e117f8909eb",
        "5eb6548930550e017f8909e9"
    ]
  },
  {...},
]

each results' categories are returned as ids of their respective documents. I need to manipulate the result so each object's categories field has an array of objects and each object has the id and the name of its category. I make an example of what the outcome should be:

[
  {
    "_id": "0000000000000000001",
    "name": "Random name",
    "categories": [
      {"_id": "5eb8548330550e017f8902e6",
      "name": "Category 1"},
      {"_id": "5eb2548630550e117f8909eb",
      "name": "Category 2"},
      {"_id": "5eb6548930550e017f8909e9",
      "name": "Category 3"},
    ]
  },
  {...},
]

I need to do this using plain JS and for so far this is what I have done but it returns an array of undefined:

const resultArray = await Promise.all(
  searchResults.map(async (item) => {
    item.categories.map(async (categoryId) => {
      return await CategoryType.find({ _id: categoryId});
    });
  })
);

For the moment I am trying to get each category document for each id in the categories field. I bet the reason why I am getting an array of undefined is that I am handling async in the wrong way but cannot figure out how.

To strictly answer your question: you are missing synchronization (because Array.prototype.map 'ignores' async):

const resultArray = await Promise.all(
  searchResults.map(async (item) => {
    const promises = item.categories.map(async (categoryId) => {
      // you dont want find, but findOne btw
      return await CategoryType.findOne({ _id: categoryId});
    });
    const categories = await Promise.all(promises)
    item.categories = categories
    return item
  })
);

This can be simplified down as

const resultArray = await Promise.all(
  searchResults.map(async item => {
    item.categories = await Promise.all(item.categories.map(categoryId => {
      return CategoryType.findOne({ _id: categoryId})
    }))
    return item
  })
);

But the proper way of doing it is likely to use populate

const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost:27017/dummy')

const Category = mongoose.model('Category', {
  name:String,
}, 'categories');
const X = mongoose.model('X', {
  name:String,
  categories: [{type: mongoose.Types.ObjectId, ref: 'Category'}]
}, 'xs');

;(async()=>{
  try {
  mongoose.set('debug', true)

  const xs = await X.find().populate('categories')

  console.log('xs : ', xs[0])
  } finally {
    mongoose.disconnect()
  }
})()

You will notice by the way, that mongoose, under the hood uses find({ _id: {$in:[]}}) which makes only one request (so better) than doing multiple findOne (as you do)

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