前端如何做到并发请求
- 使用AJAX及其各种高级封装的异步技术,如jQuery的$.ajax()、axios等;
- 使用Promise(ES6)异步编程,如使用axios.all()或者Promise.all()多个请求并发发出;
- 使用Web Workers技术,将任务并发分发到多个Web Worker中执行;
- 使用WebSocket技术,可单个持久链接上进行多次请求;
- 使用Stream API技术,浏览器可以同时消费多个数据流,实现多个请求的并发;
- 使用定时器setInterval()及setTimeout(),可定时发出多个需要并发请求;
方法一:封装队列
js
class taskQueue {
constructor() {
this.queue = [];
this.result = [];
this.error = [];
this.max = 3;
setTimeout(() => {
this.run();
}, 0);
}
addTask(task) {
this.queue.push(task);
}
async run() {
const length = this.queue.length;
if (length === 0) return;
const min = Math.min(length, this.max);
for (let i = 0; i < min; i++) {
this.max--; //开始占用任务空间
const task = this.queue.shift();
try {
const res = await task();
console.log("[ res ]-131", res);
this.result.push(res);
} catch (err) {
this.error.push(err);
} finally {
this.max++; //释放占用空间
this.run();
}
}
}
}
//测试
function createTask(delay) {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("✅" + delay);
// delay % 3 ? resolve("✅" + delay) : reject("❌" + delay);
}, delay);
});
};
}
const task = new taskQueue();
const times = [100, 2600, 400, 300, 500, 600, 900, 800, 700, 1000];
times.forEach((item) => {
task.addTask(createTask(item));
});
方法二:函数封装
js
// 并发请求函数
const concurrencyRequest = (tasks, maxNum) => {
return new Promise((resolve) => {
if (tasks.length === 0) {
resolve([]);
return;
}
const results = [];
let index = 0; // 下一个请求的下标
let count = 0; // 当前请求完成的数量
// 发送请求
async function request() {
if (index === tasks.length) return;
const i = index; // 保存序号,使result和tasks相对应
index++;
try {
const resp = await tasks[i]();
console.log("[ resp ]-29", resp);
results[i] = resp;
} catch (err) {
results[i] = err;
} finally {
count++;
// 判断是否所有的请求都已完成
if (count === tasks.length) {
console.log("完成了");
resolve(results);
}
request();
}
}
const times = Math.min(maxNum, tasks.length);
for (let i = 0; i < times; i++) {
request();
}
});
};
//测试代码
function createTask(delay) {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("✅" + delay);
// delay % 3 ? resolve("✅" + delay) : reject("❌" + delay);
}, delay);
});
};
}
const times = [100, 2600, 400, 300, 500, 600, 900, 800, 700, 1000];
const tasks = times.map((item) => createTask(item));
console.log("[ tasks ]-141", tasks);
concurrencyRequest(tasks, 3).then((res) => {
console.log("[ res ]-143", res);
});
Web Worker
可以创建一个独立线程, 因为不会阻塞主线程运行,可以将比较耗费资源操作放在里面执行,比如耗时计算,可以通过 postMessage
进行线程间通信
js
// 主线程
var worker = new Worker('worker.js')
worker.postMessage([10, 24])
worker.onmessage = function(e) {
console.log(e.data)
}
// Worker 线程
onmessage = function (e) {
if (e.data.length > 1) {
postMessage(e.data[1] - e.data[0])
}
}
// 主线程
worker.terminate()
在 Worker 线程中,self 和 this 都代表子线程的全局对象。对于监听 message 事件,以下的四种写法:
js
// 写法 1
self.addEventListener('message', function (e) {
// ...
})
// 写法 2
this.addEventListener('message', function (e) {
// ...
})
// 写法 3
addEventListener('message', function (e) {
// ...
})
// 写法 4
onmessage = function (e) {
// ...
}
Service Worker
服务器与浏览器之间的中间人,如果网站中注册了Service Worker那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器,我们在Service Worker 中可以做拦截客户端的请求、向客户端发送消息、向服务器发起请求等先关操作,其中最重要且广泛的的作用就是离线资源缓存。
特性
- 基于web worker(JavaScript主线程的独立线程,如果执行消耗大量资源的操作也不会堵塞主线程)
- 在web worker的基础上增加了离线缓存的能力
- 本质上充当Web应用程序(服务器)与浏览器之间的代理服务器
- 创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验)
- 由事件驱动的,具有生命周期
- 可以访问cache和indexDB
- 支持消息推送
- 并且可以让开发者自己控制管理缓存的内容以及版本
- 可以通过 postMessage 接口把数据传递给其他JS 文件
- 更多无限可能
WARNING
不能访问 DOM、不能同步操作