我正在用Node.js和mongoose写一个web应用程序。如何对我从.find()调用得到的结果进行分页?我想要一个功能可比的“限制50,100”在SQL。
当前回答
这是一个版本,我附加到我所有的模型。为了方便,它依赖于下划线,为了性能,它依赖于异步。opts允许使用mongoose语法进行字段选择和排序。
var _ = require('underscore');
var async = require('async');
function findPaginated(filter, opts, cb) {
var defaults = {skip : 0, limit : 10};
opts = _.extend({}, defaults, opts);
filter = _.extend({}, filter);
var cntQry = this.find(filter);
var qry = this.find(filter);
if (opts.sort) {
qry = qry.sort(opts.sort);
}
if (opts.fields) {
qry = qry.select(opts.fields);
}
qry = qry.limit(opts.limit).skip(opts.skip);
async.parallel(
[
function (cb) {
cntQry.count(cb);
},
function (cb) {
qry.exec(cb);
}
],
function (err, results) {
if (err) return cb(err);
var count = 0, ret = [];
_.each(results, function (r) {
if (typeof(r) == 'number') {
count = r;
} else if (typeof(r) != 'number') {
ret = r;
}
});
cb(null, {totalCount : count, results : ret});
}
);
return qry;
}
将它附加到您的模型模式。
MySchema.statics.findPaginated = findPaginated;
其他回答
你可以像这样串起来:
var query = Model.find().sort('mykey', 1).skip(2).limit(5)
使用exec执行查询
query.exec(callback);
您可以使用skip()和limit(),但效率非常低。更好的解决方案是对索引字段加上limit()进行排序。 我们在Wunderflats发布了一个小库:https://github.com/wunderflats/goosepage 它用了第一种方法。
**//localhost:3000/asanas/?pageNo=1&size=3**
//requiring asanas model
const asanas = require("../models/asanas");
const fetchAllAsanasDao = () => {
return new Promise((resolve, reject) => {
var pageNo = parseInt(req.query.pageNo);
var size = parseInt(req.query.size);
var query = {};
if (pageNo < 0 || pageNo === 0) {
response = {
"error": true,
"message": "invalid page number, should start with 1"
};
return res.json(response);
}
query.skip = size * (pageNo - 1);
query.limit = size;
asanas
.find(pageNo , size , query)
.then((asanasResult) => {
resolve(asanasResult);
})
.catch((error) => {
reject(error);
});
});
}
我发现了一种非常有效的方法并亲自实施,我认为这种方法是最好的,原因如下:
它不使用跳过,这使得时间复杂度不能很好地扩展; 它使用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)的空数组,这太棒了!
实现这一点的可靠方法是使用查询字符串从前端传递值。假设我们想要获得第2页,并将输出限制为25个结果。 page=2&limit=25 //这将被添加到您的URL: http:localhost:5000?= 2限制= 25页
让我们看看代码:
// We would receive the values with req.query.<<valueName>> => e.g. req.query.page
// Since it would be a String we need to convert it to a Number in order to do our
// necessary calculations. Let's do it using the parseInt() method and let's also provide some default values:
const page = parseInt(req.query.page, 10) || 1; // getting the 'page' value
const limit = parseInt(req.query.limit, 10) || 25; // getting the 'limit' value
const startIndex = (page - 1) * limit; // this is how we would calculate the start index aka the SKIP value
const endIndex = page * limit; // this is how we would calculate the end index
// We also need the 'total' and we can get it easily using the Mongoose built-in **countDocuments** method
const total = await <<modelName>>.countDocuments();
// skip() will return a certain number of results after a certain number of documents.
// limit() is used to specify the maximum number of results to be returned.
// Let's assume that both are set (if that's not the case, the default value will be used for)
query = query.skip(startIndex).limit(limit);
// Executing the query
const results = await query;
// Pagination result
// Let's now prepare an object for the frontend
const pagination = {};
// If the endIndex is smaller than the total number of documents, we have a next page
if (endIndex < total) {
pagination.next = {
page: page + 1,
limit
};
}
// If the startIndex is greater than 0, we have a previous page
if (startIndex > 0) {
pagination.prev = {
page: page - 1,
limit
};
}
// Implementing some final touches and making a successful response (Express.js)
const advancedResults = {
success: true,
count: results.length,
pagination,
data: results
}
// That's it. All we have to do now is send the `results` to the frontend.
res.status(200).json(advancedResults);
我建议将这个逻辑实现到中间件中,这样你就可以将它用于各种路由/控制器。
推荐文章
- 如何排序mongodb与pymongo
- 如何在mongodb上导入。bson文件格式
- 很好的初学者教程socket.io?
- CALL_AND_RETRY_LAST分配失败-进程内存不足
- 在Ubuntu上安装Node.js
- 使用express.js代理
- 等价的限制和偏移SQL Server?
- JSON文件的蒙古导入
- Node -使用NODE_MODULE_VERSION 51根据不同的Node.js版本编译
- 如何删除mongodb中的数组元素?
- RabbitMQ / AMQP:单队列,同一消息的多个消费者?
- 修改MongoDB数据存储目录
- Node.js同步执行系统命令
- 在MongoDB中查找重复的记录
- 禁用包的postinstall脚本