我正在用Node.js和mongoose写一个web应用程序。如何对我从.find()调用得到的结果进行分页?我想要一个功能可比的“限制50,100”在SQL。


当前回答

有一些很好的答案给出了使用skip()和limit()的解决方案,但是,在某些情况下,我们还需要文档计数来生成分页。以下是我们在项目中所做的:

const PaginatePlugin = (schema, options) => {
  options = options || {}
  schema.query.paginate = async function(params) {
    const pagination = {
      limit: options.limit || 10,
      page: 1,
      count: 0
    }
    pagination.limit = parseInt(params.limit) || pagination.limit
    const page = parseInt(params.page)
    pagination.page = page > 0 ? page : pagination.page
    const offset = (pagination.page - 1) * pagination.limit

    const [data, count] = await Promise.all([
      this.limit(pagination.limit).skip(offset),
      this.model.countDocuments(this.getQuery())
    ]);
    pagination.count = count;
    return { data, pagination }
  }
}

mySchema.plugin(PaginatePlugin, { limit: DEFAULT_LIMIT })

// using async/await
const { data, pagination } = await MyModel.find(...)
  .populate(...)
  .sort(...)
  .paginate({ page: 1, limit: 10 })

// or using Promise
MyModel.find(...).paginate(req.query)
  .then(({ data, pagination }) => {

  })
  .catch(err => {

  })

其他回答

以上回答是正确的。

只是一个插件,任何人谁是异步等待而不是 承诺! !

const findAllFoo = async (req, resp, next) => {
    const pageSize = 10;
    const currentPage = 1;

    try {
        const foos = await FooModel.find() // find all documents
            .skip(pageSize * (currentPage - 1)) // we will not retrieve all records, but will skip first 'n' records
            .limit(pageSize); // will limit/restrict the number of records to display

        const numberOfFoos = await FooModel.countDocuments(); // count the number of records for that model

        resp.setHeader('max-records', numberOfFoos);
        resp.status(200).json(foos);

    } catch (err) {
        resp.status(500).json({
            message: err
        });
    }
};

我发现了一种非常有效的方法并亲自实施,我认为这种方法是最好的,原因如下:

它不使用跳过,这使得时间复杂度不能很好地扩展; 它使用id来查询文档。在MongoDB中,id默认情况下是索引的,这使得查询它们非常快; 它使用精益查询,这些被认为是非常具有执行力的,因为他们从Mongoose中删除了很多“魔法”,并返回一个来自MongoDB的“原始”文档; 它不依赖于任何可能包含漏洞或具有易受攻击依赖项的第三方包。

唯一需要注意的是,Mongoose的一些方法,比如.save()在精益查询中不能很好地工作,这些方法在这篇很棒的博客文章中列出了,我真的推荐这个系列,因为它考虑了很多方面,比如类型安全(防止严重错误)和PUT/ PATCH。

我将提供一些上下文,这是一个Pokémon存储库,分页工作如下:API从req接收unsafeId。Express的body对象,我们需要将其转换为字符串以防止NoSQL注入(它可以是一个带有邪恶过滤器的对象),这个unsafeId可以是一个空字符串或上一页最后一项的ID,它是这样的:

 /**
   * @description GET All with pagination, will return 200 in success
   * and receives the last ID of the previous page or undefined for the first page
   * Note: You should take care, read and consider about Off-By-One error
   * @param {string|undefined|unknown} unsafeId - An entire page that comes after this ID will be returned
   */
  async readPages(unsafeId) {
    try {
      const id = String(unsafeId || '');
      let criteria;
      if (id) {
        criteria = {_id: {$gt: id}};
      } // else criteria is undefined

      // This query looks a bit redundant on `lean`, I just really wanted to make sure it is lean
      const pokemon = await PokemonSchema.find(
          criteria || {},
      ).setOptions({lean: true}).limit(15).lean();

      // This would throw on an empty page
      // if (pokemon.length < 1) {
      //  throw new PokemonNotFound();
      // }

      return pokemon;
    } catch (error) {
      // In this implementation, any error that is not defined by us
      // will not return on the API to prevent information disclosure.
      // our errors have this property, that indicate
      // that no sensitive information is contained within this object
      if (error.returnErrorResponse) {
        throw error;
      } // else
      console.error(error.message);
      throw new InternalServerError();
    }
  }

现在,为了消费它并避免前端的off - by - 1错误,你可以像下面这样做,考虑到pokemons是从API返回的Pokémons文档的数组:

// Page zero
const pokemons = await fetchWithPagination({'page': undefined});
// Page one
// You can also use a fixed number of pages instead of `pokemons.length`
// But `pokemon.length` is more reliable (and a bit slower)
// You will have trouble with the last page if you use it with a constant
// predefined number 
const id = pokemons[pokemons.length - 1]._id;

if (!id) {
    throw new Error('Last element from page zero has no ID');
} // else

const page2 = await fetchWithPagination({'page': id});

这里需要注意的是,Mongoose ID总是连续的,这意味着任何新的ID总是比旧的ID大,这是这个答案的基础。

这种方法已经针对Off-By-One错误进行了测试,例如,页面的最后一个元素可能会作为下一个页面的第一个元素返回(重复),或者位于上一页最后一个元素和当前页面第一个元素之间的元素可能会消失。

当您处理完所有页面并在最后一个元素(一个不存在的元素)之后请求一个页面时,响应将是一个200 (OK)的空数组,这太棒了!

app.get("/:page",(req,res)=>{
        post.find({}).then((data)=>{
            let per_page = 5;
            let num_page = Number(req.params.page);
            let max_pages = Math.ceil(data.length/per_page);
            if(num_page == 0 || num_page > max_pages){
                res.render('404');
            }else{
                let starting = per_page*(num_page-1)
                let ending = per_page+starting
                res.render('posts', {posts:data.slice(starting,ending), pages: max_pages, current_page: num_page});
            }
        });
});

你可以像这样串起来:

var query = Model.find().sort('mykey', 1).skip(2).limit(5)

使用exec执行查询

query.exec(callback);

使用这个简单的插件。

https://github.com/WebGangster/mongoose-paginate-v2

安装

NPM安装mongoose- pagate -v2 使用 添加插件到一个模式,然后使用模型paginate方法:

Const mongoose = require('mongoose'); const mongoosePaginate = require(' mongoosePaginate -v2'); const mySchema =新的猫鼬。模式({ /*你的模式定义*/ }); mySchema.plugin (mongoosePaginate); const myModel =猫鼬。模型(SampleModel, mySchema); myModel.paginate().then({}) //使用方法