js技巧
wǎng luò shí huāng 2023-04-03
js技巧
前端高并发会让浏览器卡顿原因以及解决办法 减少页面卡顿 异步编程
# 网络高并发引发页面卡顿原因以及解决方法
https://juejin.cn/post/7276026646254092323
上面的解决方法不是最优,下面的方法最优:
- 参考这里:https://mp.weixin.qq.com/s/4JaEyYaN2GZn4OVWznavOg
- 截图 (opens new window)
// 自己优化了链接里的第二种方法:*注意:此方法不可嵌套使用,这也是待解决的问题* export class SendRequest { static callback: any = null; // 当前的并发池,用Set结构方便删除 static pool = new Set(); // set也是Iterable<any>[]类型,因此可以放入到race里 static promises: any[] = []; // 限制并发数量 static limits = 4; static requestList: any[] = []; // 批量的方法 static async batch(requestList, limits = 4, callback) { this.limits = limits; this.callback = callback; this.requestList.concat( requestList.map((item) => { return { request: item, resolver: null, }; }) ); if (this.pool.size < this.limits) { this.run(); } } // 单个任务添加: static async addRequest(request: Function, limits = 4, callback?: Function) { this.limits = limits; let currentPromise = new Promise((resolver, rejector) => { this.requestList.push({ request: request, callback: callback, resolver, rejector, }); }); if (this.pool.size < this.limits) { this.run(); } return currentPromise; } // 开始并发执行所有的任务 static index = 0; static async run() { for (this.index; this.index < this.requestList.length; this.index++) { // await可能会引发多个run()函数在跑,导致this.index跳动,漏掉一些请求 if (this.pool.size >= this.limits) { return; } // 通过index连续来判断是否跳过某些请求;通过查看size确定是否pool被成功限制 const item = this.requestList[this.index]; if (this.pool.size >= this.limits) { await Promise.race(this.pool).catch((err) => err); } const promise = item.request(); // 拿到外面传递进来的promise const cb = (data) => { // 请求结束后,从pool里面移除对应的从外面传递进来的promise this.pool.delete(promise); if (this.requestList.length > this.index && this.pool.size < this.limits - 1) { // 假如恰好当前是以后一个请求时,在获取但还未返回数据时又添加进请求的情况下,我们需要手动调用。 this.run(); } if (this.requestList.length == this.index && this.pool.size === 0) { // 进行空间释放 // 可以认为是当前this.requestList里所有请求已经执行完且途中没有新请求添加进来,针对一次性批量batch方法来说可以调用this.callback表示批量请求已经执行完。但是多次调用batch方法时,this.callback就不一定表示对应batch的所有请求完结时的回调了。因为说白了只有当this.requestList里的所有请求执行完这一刻且没有新请求在中途添加进来时调用this.callback,无法判断这一时刻之后还有没有新请求添加进来。 this.index = 0; this.requestList = []; this.callback?.(true); } item.resolver?.(data); item.callback?.(data); }; const err = (err) => { this.pool.delete(promise); if (this.requestList.length > this.index && this.pool.size < this.limits - 1) { this.run(); } if (this.requestList.length == this.index && this.pool.size === 0) { this.index = 0; this.requestList = []; this.callback?.(false); } item.rejector?.(err); item.callback?.(err); }; this.pool.add(promise); // this.promises.push(promise) // 用不上这个浪费空间 promise.then(cb, err); } } } // 处理promise报错的优雅方法(非ts版本) function to(promise, errorExt) { return promise .then((data) => [null, data]) .catch((err) => { if (errorExt) { const parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; } return [err, undefined]; }); } // 处理promise报错的优雅方法(ts版本) function to<T, U = Error>(promise: Promise<T>, errorExt?: object): Promise<[U, undefined] | [null, T]> { return ( promise.then < [null, T] > ((data: T) => [null, data]).catch < [U, undefined] > ((err: U) => { if (errorExt) { const parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; } return [err, undefined]; }) ); } // 使用范例 let [err, rsp] = await to(sendRequest.addRequest(() => 返回结果为promise的表达式)); // 使用示例:// 下面依次执行,等号左边都会获取到结果 // 代码会依次间隔一秒往下执行 let [kk2err, kk2rsp] = await to( sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(2); res("successwww2"); }, 1000); }) ) ); let [kk3err, kk3rsp] = await to( sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(3); rej("successwww3"); }, 1000); }) ) ); let [kk6err, kk6rsp] = await to( sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(6); res("successwww6"); }, 1000); }) ) ); let [kk4err, kk4rsp] = await to( sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(4); res("successwww4"); }, 1000); }) ) ); let [kk5err, kk5rsp] = await to( sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(5); rej("successwww5"); }, 1000); }) ) ); // 使用示例:// 输出 3 6 4 5,一秒后输出2 // 这是动态添加任务示例 sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(2); res("successwww2"); }, 1000); }) ); console.log("add"); sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(3); rej("successwww3"); }, 0); }) ); sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(6); res("successwww6"); }, 0); }) ); sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(4); res("successwww4"); }, 0); }) ); sendRequest.addRequest( () => new Promise((res, rej) => { setTimeout(() => { console.log(5); rej("successwww5"); }, 0); }) );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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# cpu 密集型导致 js 主线程阻塞引发的卡顿以及解决方法
- 使用 Web Workers 解决 cpu 密集型任务导致的卡顿:https://mp.weixin.qq.com/s/hR5H-ZrD8iZCZxe1nBQ7uw
- 有效利用 cpu 核心数来合理分配 Web Workers 以提高算力:https://mp.weixin.qq.com/s/_clwThyQnKJuPT4gKx_FUQ
- web worker 在主线程关闭和自己(worker 线程)关闭的区别:
- 区别是,在主线程手动关闭 Worker,主线程与 Worker 线程之间的连接都会被立刻停止,此时如果继续调用 postMessage() 方法发送消息,但主线程不会再接收到消息。而在 Worker 线程内部关闭 Worker,不会直接断开与主线程的连接,而是等 Worker 线程当前的 Event Loop 所有任务执行完,再关闭。也就是说,此时 Worker 线程可以继续调用 postMessage()方法。