利用promise.all基于react+antd+koa实现伪并发loading渲染数据

使用场景

当一个页面的小面板或多个面板里需要调用很多接口, 并且要等待接口返回才渲染界面的时候就可以使promise.all

Demo环境

  • 前端
    • antd: 3.4.1
    • bluebird: 3.5.1
    • react: 16.3.1
    • react-dom: 16.3.1
    • react-scripts-ts: 2.14.0
  • 后端
    • koa: 2.5.0
    • koa-router: 7.4.0

业务场景

设定业务是一个持续交付系统, 里面有用户中心和任务中心和项目中心
现在希望在首页的仪表盘中展示当前用户概览, 角色概览, 任务概览和项目概览.
而这里的概览暂且仅仅是对各个中心的数据的统计, 并简单的以数字的方式展示出来.

效果如图

PS: 此图做了快放处理, 实际上在面板的灰色loading过程需要等待3秒

前端效果

后端设计

分别提供四个查询各个中心概览的接口

  • 用户中心概览: /api/user-survey
  • 角色中心概览: /api/role-survey
  • 任务中心概览: /api/task-survey
  • 产品中心概览: /api/project-survey

PS: 后端代码中设定了角色中心的数据需要3秒才返回, 以此来模拟后端数据返回的延迟性

前端设计

需要实现以下接口请求逻辑和界面渲染规则

  1. 在没有请求数据前且数据没有全部返回前, 界面有loading机制
  2. 要求几乎同时查询四个接口的信息, 并且要等到四个接口的数据都有返回时才渲染数据界面

PS: 第一个要求这里使用antd中的Card标签中的loading属性去完成.

前端主要代码实现

以下代码实现了前端需求, 具体实现请查看注释部分.

文件: src/App.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import * as Bluebird from 'bluebird';
import * as React from 'react';
import './App.css';

import { Card } from 'antd';

// interface部分相当于对对象的描述, 可以先不理会
/**
* 用户概览数据定义
*/
interface UserData {
enabled: number;
sum: number;
}

/**
* 角色概览数据定义
*/
interface RoleData {
bind: number;
sum: number;
}

/**
* 任务概览数据定义
*/
interface TaskData {
todo: number;
sum: number;
}

/**
* 项目概览数据定义
*/
interface ProjectData {
healthy: number;
sum: number;
}

const logo = require('./logo.svg');

class App extends React.Component {
/**
* 后端接口的根地址
* 这里因为不使用webpack或nginx做代理跨域
* 所以可临时这样写
*/
public apiRoot = 'http://127.0.0.1:8088';

public gridStyle = {
width: '25%',
textAlign: 'center'
} as React.CSSProperties;

state = {
loading: true,
userData: { enabled: 0, sum: 0 } as UserData,
roleData: { bind: 0, sum: 0 } as RoleData,
taskData: { todo: 0, sum: 0 } as TaskData,
projectData: { healthy: 0, sum: 0 } as ProjectData
};

/**
* 简单封装一下fetch的get方法, 并且Promise化.
* @param url api的后半部分url
*/
public getResBody(url: string) {
return new Bluebird((resolve, reject) => {
fetch(this.apiRoot + url).then(async res => {
// 使用async/await方式避免then的调用
const result = await res.json();
if (result.code === 0) {
resolve(result.data);
} else {
resolve({});
}
});
});
}

/**
* 简单封装一下fetch的get方法, 并且Promise化.
* 此方法与new Bluebird方法比相对简洁
* 因为到在我们的事例中是用不到reject的.
* 一定要注意下面的return, 一个都不能少.
* @param url api的后半部分url
*/
public getResBodyByResolve(url: string) {
return Bluebird.resolve(
fetch(this.apiRoot + url).then(async res => {
// 使用async/await方式避免then的调用
const result = await res.json();
if (result.code === 0) {
return result.data;
} else {
return {};
}
})
);
}

/**
* 使用PromiseAll方法
* 实现等待多个接口查询完毕后再显示Card的结果
*/
public getSurvey() {
// 这不用await的原因是因为await会阻塞这四个请求
// 也就是说如果用await就需要前一个请求的接口得到结果之后才会请求下一个接口
// 而使用PromiseAll就能按照请求书写顺序发送四个请求(非并发)
// 并同时等待四个请求有了回复才进行下一步操作
// 值得注意的是, 这里的四个请求的发起不是并发的
// 但是每个请求都不需要等待上一个请求完毕后才执行下一个请求
const userSurveyPro = this.getResBody('/api/user-survey');
const roleSurveyPro = this.getResBody('/api/role-survey');
const taskSurveyPro = this.getResBody('/api/task-survey');
const projectSurveyPro = this.getResBody('/api/project-survey');

// all方法里需要传入数组, 即使只传一个的时候也要包在数组里
// 当然, 如果只传一个的话, 就不需要用all了
// 传入数组里的对象必须为Promise对象
Bluebird.all([userSurveyPro, roleSurveyPro, taskSurveyPro, projectSurveyPro]).then(surveyList => {
this.setState({
userData: surveyList[0],
roleData: surveyList[1],
taskData: surveyList[2],
projectData: surveyList[3],
loading: false
});
});
}

public getSurveyByResolve() {
// 这不用await的原因是因为await会阻塞这四个请求
// 也就是说如果用await就需要前一个请求的接口得到结果之后才会请求下一个接口
// 而使用PromiseAll就能按照请求书写顺序发送四个请求(非并发)
// 并同时等待四个请求有了回复才进行下一步操作
// 值得注意的是, 这里的四个请求的发起不是并发的
// 但是每个请求都不需要等待上一个请求完毕后才执行下一个请求
const userSurveyPro = this.getResBodyByResolve('/api/user-survey');
const roleSurveyPro = this.getResBodyByResolve('/api/role-survey');
const taskSurveyPro = this.getResBodyByResolve('/api/task-survey');
const projectSurveyPro = this.getResBodyByResolve('/api/project-survey');

// all方法里需要传入数组, 即使只传一个的时候也要包在数组里
// 当然, 如果只传一个的话, 就不需要用all了
// 传入数组里的对象必须为Promise对象
Bluebird.all([userSurveyPro, roleSurveyPro, taskSurveyPro, projectSurveyPro]).then(surveyList => {
this.setState({
userData: surveyList[0],
roleData: surveyList[1],
taskData: surveyList[2],
projectData: surveyList[3],
loading: false
});
});
}

componentDidMount() {
this.getSurvey();
// 此方法仅是上面方法的简洁版
// this.getSurveyByResolve();
}

render() {
const { loading, userData, roleData, taskData, projectData } = this.state;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">欢迎来到Promise.all的学习Demo</h1>
</header>
<Card title="概览仪表盘" loading={loading}>
<Card.Grid style={this.gridStyle}>
<h3>用户概览</h3>
启用数: {userData.enabled} <br />
总数: {userData.sum}
</Card.Grid>
<Card.Grid style={this.gridStyle}>
<h3>角色概览</h3>
被绑定数: {roleData.bind} <br />
总数: {roleData.sum}
</Card.Grid>
<Card.Grid style={this.gridStyle}>
<h3>任务概览</h3>
未完成数: {taskData.todo} <br />
总数: {taskData.sum}
</Card.Grid>
<Card.Grid style={this.gridStyle}>
<h3>项目概览</h3>
健康数: {projectData.healthy} <br />
总数: {projectData.sum}
</Card.Grid>
</Card>
</div>
);
}
}

export default App;

前后端Demo完全源码

参考

显示 Gitment 评论