Ajax GET:多个特定于数据的调用,或更少的不太具体的调用



我正在使用Node.js/express后端和MongoDB作为数据库开发一个Web应用程序。

以下示例适用于管理仪表板页面,我将在其中显示与站点用户相关的不同信息的卡片。我可能想在示例页面上显示 - 例如:

  1. 每种类型的用户数
  2. 每种用户类型最常见的位置
  3. 每月有多少注册
  4. 最受欢迎的职位

我可以在一个路由中完成所有这些操作,其中我有一个执行所有这些任务的控制器,并将它们作为对象捆绑到一个 url 中,然后我可以使用ajax从中提取数据。或者,我可以将每个任务拆分为自己的路由/控制器,并对每个任务进行单独的 ajax 调用。我试图决定的是在一个页面上进行多个 ajax 调用的最佳实践是什么


例:

我正在建立一个页面,我将在其中使用DataTables为不同类型的用户(目前有两个:mentorsmentees)制作一个交互式表格。此示例只需要两个数据请求(每个用户类型一个),但我的最终页面将更像 10 个。

对于每种用户类型,我正在为每个用户类型进行 ajax get 调用,并从返回的数据构建表:

用户类型 1 - 学员

$.get('/admin/' + id + '/mentees')
.done(data => {
$('#menteeTable').DataTable( {
data: data,
"columns": [
{ "data": "username"},
{ "data": "status"}
]
});
})

用户类型 2 - 导师

$.get('/admin/' + id + '/mentors')
.done(data => {
$('#mentorTable').DataTable( {
data: data,
"columns": [
{ "data": "username"},
{ "data": "position"}
]
});
})

然后,这需要我的 Node.js后端中的两条路由:

router.get("/admin/:id/mentors", getMentors);
router.get("/admin/:id/mentees", getMentees);

还有两个结构相同的控制器(但针对不同的用户类型进行筛选):

getMentees(req, res, next){
console.log("Controller: getMentees");
let query = { accountType: 'mentee', isAdmin: false };
Profile.find(query)
.lean()
.then(users => {
return res.json(users);
})
.catch(err => {
console.log(err)
})
}

这很好用。但是,由于我需要发出多个数据请求,因此我想确保我以正确的方式构建它。我可以看到几个选项:

  1. 对每种数据类型进行单独的 ajax 调用,并在后端执行任何繁重的工作(例如,统计用户类型和返回) - 如上所述
  2. 对每种数据类型进行单独的 ajax 调用,但在前端完成繁重的工作。在上面的例子中,我可以很容易地过滤掉从我的 ajax 调用返回dataisAdmin
  3. 用户
  4. 减少请求较少精细数据的 ajax 调用。在上面的例子中,我可以为所有用户进行一次调用(只需要一个路由/控制器),然后在前端过滤data以构建两个表

我希望得到一些关于哪种策略在采购数据所花费的时间方面最有效的建议


更新

为了澄清这个问题,我可以使用如下所示的控制器设置来获得与上述相同的结果:

Profile.find(query)
.lean()
.then(users => {
let mentors = [],
mentees = []
users.forEach(user => {
if(user.accountType === 'mentee') {
mentees.push(user);
} else if (user.accountType === 'mentor') {
mentors.push(user);
}
});
return res.json({mentees, mentors});
})

然后进行一次ajax调用,并相应地拆分数据。我的问题是:哪个是首选?

TL;DR:选项 1

IMO 我不会将未处理的数据提供给前端,事情可能会出错,您可能会透露太多,未指定的客户端计算机可能需要很多时间来处理(例如,可能是带宽和电池电量有限的低功耗设备),您想要流畅的用户体验,客户端上的 JavaScript 从大量数据中大量生成信息会减损这一点。我使用后端进行处理(根据需要准备信息),JS检索信息并将其放置在页面上(AJAX)以及切换元素状态之类的东西,以及CSS用于任何移动(动画和过渡等)在诉诸JS之前尽可能多。 同样对于路由,我的方法是每个不同的信息包(dataTable)都有一个路由,因此您不会重载具有太多目的的方法,保持其简单且可维护。你总是可以抽象出任何相同且经常重复的东西。

因此,为了回答您的问题,我会选择选项 1。 您还可以提供单个"页面加载"端点,然后如果有任何更改,稍后使用其不同的端点更新各个表。这个初始的"页面加载"调用可以整理来自后端端点的信息,并作为一个数据包来最初填充所有表。一个包含大量明确定义的数据的初始请求,然后能够在用户请求时更新单个表(或者如果您进入该请求,则进行推送)。

这真是个好问题。首先,您应该意识到您的应用程序将如何使用接收到的数据进行管理。如果它是大量未在 fronend 上更改的数据,但具有不同的视图和这些视图的整个数据需求,它可能会被缓存到前端(如用户设置数据 - 应用程序总是读取它但很少更改),那么您可以按照第二个选项进行操作。其他情况是,如果前端只处理大量数据库数据中的一小部分(如特定用户的日志数据),最好在服务器端预处理(过滤)您的第一个和第三个选项。实际上,第二个选项仅适用于在前端缓存未更改的数据,就像我一样。

澄清问题后,您可以为您的请求和 lodash 库使用分组:

Profile.find(query)
.lean()
.then(users => {
let result = [];    
result = _(users)
.groupBy((elem) => elem.accountType)
.map((vals, key) => ({accountType: key, users: vals}))
.value(); 
});
return res.json(result);
});

当然,您可以根据需要映射数据。这种方式允许获得所有类型的帐户(不仅是"学员"和"导师")

通常这种架构中有 3 件事:

1. Client 
2. API Gateway
3. Micro services (Servers)

在您的情况下:

1. Client is JS application code
2. API Gateway + Server is Nodejs/express (Dual responsibility)

需要注意的第1点

服务器仅提供核心 API。因此,服务器的此 API 应该只是一个用户 api,如下所示:

/users?type={mentor/mentee/*}&limit=10&pageNo=8

即任何人都可以使用类型查询字符串请求所有数据或过滤数据。

需要注意的第2点

由于网页由多个数据点组成,并且对同一服务器的每个数据点进行调用会增加往返行程并使用户体验变得更糟,因此 API 网关就在那里。因此,在这种情况下,JS不会直接与核心服务器通信,而是与API网关和API进行通信,例如:

/home

上述 API 在内部调用以下 API,并将数据聚合到具有导师和学员列表的单个 json 中

/users?type={mentor/mentee/*}&limit=10&pageNo=8

此 API 只是使用查询属性将调用传递到核心服务器

现在,由于在您的代码中,API 网关和核心服务器合并为单层,因此您应该如何设置代码:

getHome(req, res, next){
console.log("Controller: /home");
let queryMentees = { accountType: 'mentee', isAdmin: false };
let queryMentors = { accountType: 'mentor', isAdmin: true };
mentes  = getProfileData(queryMentees);
mentors = getProfileData(queryMentors);
return res.json({mentes,mentors});
}
getUsers(req, res, next){
console.log("Controller: /users");
let query = {accountType:request.query.type,isAdmin:request.query.isAdmin};
return res.json(getProfileData(query));
}

还有一个常见的 ProfileService.js 类,其函数如下:

getProfileData(query){
Profile.find(query)
.lean()
.then(users => {
return users;
})
.catch(err => {
console.log(err)
})
}

有关 API 网关模式的详细信息,请参阅此处

如果你无法估计你的应用程序需要多少种类型,那么需要使用参数, 如果我像这个应用程序一样编写,我不编写用于调用 ajax 的多个函数,也不编写多个路由和控制器,

像这样的客户端

let getQuery = (id,userType)=>{
$.get('/admin/' + id + '/userType/'+userType)
.done(data => {
let dataTable = null;
switch(userType){
case "mentee":
dataTable = $('#menteeTable');
break;
case "mentor":
dataTable = $('#mentorTable');
break;
//.. you can add more selector for datatables but I wouldn't prefer this way you can generate "columns" property on server like "data" so meaning that you can just use one datatable object on client side
}
dataTable.DataTable( {
data: data,
"columns": [
{ "data": "username"},
{ "data": "status"}
]
});
})
}

我更喜欢客户端

let getQuery = (id,userType)=>{
$.get('/admin/' + id + '/userType/'+userType)
.done(data => {
$('#dataTable').DataTable( {
data: data.rows,
"columns": data.columns
]
});
})
}

服务器响应应支持 {data: [{}...], columns:[{}....]} 像这样 在此方案中 数据表示例

像这样的服务器端 路由器只有一个

router.get("/admin/:id/userType/:userType", getQueryFromDB);

控制器

getQueryFromDB(req, res, next){
let query = { accountType: req.params.userType, isAdmin: false };
Profile.find(query)
.lean()
.then(users => {
return res.json(users);
})
.catch(err => {
console.log(err)
})
}

所以你的问题对我来说的主要含义是学员、导师等......是像"id"这样的参数

确保您的身份验证检查了哪些用户有权访问 userType 我的代码示例和您的代码的数据,有人只需更改路由即可访问您的数据

周末愉快

  1. 来自用户设备上UI的性能和流畅性: 当然,最好对所有核心数据执行 1 个 ajax 请求(尽快显示这一点很重要),并可能以一些微小的延迟对优先级较低的数据执行更多请求。或者执行 2 个请求:一个用于"快速"数据,另一个用于"慢"数据(如果适用),因为:

一方面,许多 ajax 请求可能会减慢 ui 的速度,同时完成的 ajax 请求数量可能会受到限制(它取决于浏览器,可能是 2 到 10),所以如果例如在 IE 中将限制为 2,那么对于 10 个 ajax,将有一个等待 ajax 请求的队列

但另一方面,如果要显示大量数据或某些数据需要更长的时间来准备,则可能会导致长时间等待后端响应显示某些内容。

说到繁重的工作:反正在UI端做这样的事情是不好的,因为: 用户设备可能不善于利用资源和"缓慢"。 Javascript是同步的,因此,任何长循环都会"冻结"UI运行该循环所需的时间。

谈到过滤用户:

Profile.find(query)
.lean()
.then(users => {
let mentors = [],
mentees = []
users.forEach(user => {
if(user.accountType === 'mentee') {
mentees.push(user);
} else if (user.accountType === 'mentor') {
mentors.push(user);
}
});
return res.json({mentees, mentors});
})

似乎有一个问题,可能查询会有排序和限制,如果是这样,最终结果将不一致,最终可能只有学员或只有导师,我认为您应该对数据存储进行 2 个单独的查询

  1. 从项目结构、可维护性、灵活性、可重用性等方面,当然尽可能解耦是件好事。

所以,最后,想象一下你做了: 1. 许多微服务,例如每个小部件 1 个后端微子宫颈,但有一个层允许聚合结果以优化来自 1-2 个 ajax 查询中的 UI 的流量。 2. 许多 UI 模块,每个模块使用自己的数据,从某个服务接收,这些服务执行 1-2 次调用来聚合后端,并将其收到的不同数据集分发到许多前端模块。

在后端,只需创建一个动态参数化方法 API。 您可以将导师、学员、管理员等作为角色传递。您应该具有某种类型的用户身份验证和授权,以检查用户 A 是否可以看到角色 B 中的用户。 关于UI,这取决于用户,他们想要一个带有下拉过滤器的页面,或者他们希望URL添加书签。 像多个网址/管理员/导师等。 或一个带有查询字符串和下拉列表的 URL./user?role=mentor,/user?role=admin。 根据网址,你必须制作控制器。我通常更喜欢下拉并获取数据(默认情况下,所有导师都可能是选择)。 这是适合浪漫性质邀请(例如约会或订婚派对)的特定邀请函。

最新更新