赞
踩
摘要:
时隔两年半,我,一个卑微的前端菜鸡,又来写面经了!以为钱是程序员年轻奋斗的动力!作为一个程序员,在一个地方慢慢成长后会产生一个能力小提升的一种傲娇!希望你们一跳涨好几丈。。。下面是我最近面试遇到的题目,总结了一下。。。
由于js是单线程的,并不存在真正的并发,但是由于JavaScript的Event Loop机制,使得异步函数调用有了“并发”这样的假象
题目:
// 设计一个函数,可以限制请求的并发,同时请求结束之后,调用callback函数 sendRequest(requestList: , limits, callback): voidsendRequest([ () => request('1'), () => request('2'), () => request('3'), () => request('4')],3, //并发数 (res)=>{ console.log(res) }) function request (url,time=1){ return new Promise((resolve,reject)=>{ setTimeout(()=>{ console.log('请求结束:'+url); if(Math.random() > 0.5){ resolve('成功') }else{ reject('错误;') } },time*1e3) }) }
概念理解:串发和并发
串行: 一个异步请求完了之后在进行下一个请求!从上到下依次执行对应接口请求
var p = function () { return new Promise(function (resolve, reject) { setTimeout(() => { console.log("1000"); resolve(); }, 1000); }); }; var p1 = function () { return new Promise(function (resolve, reject) { setTimeout(() => { console.log("2000"); resolve(); }, 2000); }); }; var p2 = function () { return new Promise(function (resolve, reject) { setTimeout(() => { console.log("3000"); resolve(); }, 3000); }); }; p() .then(() => { return p1(); }) .then(() => { return p2(); }) .then(() => { console.log("end"); });
并行: 多个异步请求同时进行!
var promises = function () {
return [1000, 2000, 3000].map((current) => {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(current);
}, current);
});
});
};
Promise.all(promises()).then(() => {
console.log("end");
});
Promise.all可以保证,promises数组中所有promise对象都达到resolve状态,才执行then回调。
场景1:
假设以下有 30 个异步请求需发送,但由于某些原因,我们必须将同一时刻并发请求数量控制在 5 个以内,同时还要尽可能快速的拿到响应结果。
如图所示:
图中这样的排队和并发请求的场景基本类似,窗口只有三个,人超过三个之后,后面的人只能排队了!
场景2:
如果你的promises数组中每个对象都是http请求,而这样的对象有几十万个。那么会出现的情况是,你在瞬间发出几十万个http请求,这样很有可能导致堆积了无数调用栈导致内存溢出。这时候,我们就需要考虑对Promise.all做并发限制。Promise.all并发限制指的是,每个时刻并发执行的promise数量是固定的,最终的执行结果还是保持与原来的Promise.all一致。
整体采用递归调用来实现:最初发送的请求数量上限为允许的最大值,并且这些请求中的每一个都应该在完成时继续递归发送,通过传入的索引来确定了urls里面具体是那个URL,保证最后输出的顺序不会乱,而是依次输出。
function multiRequest(urls, maxNum) { const len = urls.length; // 请求总数量 const res = new Array(len).fill(0); // 请求结果数组 let sendCount = 0; // 已发送的请求数量 let finishCount = 0; // 已完成的请求数量 return new Promise((resolve, reject) => { // 首先发送 maxNum 个请求,注意:请求数可能小于 maxNum,所以也要满足条件2 // 同步的 创建maxNum个next并行请求 然后才去执行异步的fetch 所以一上来就有5个next并行执行 while (sendCount < maxNum && sendCount < len) { next(); } function next () { let current = sendCount ++; // 当前发送的请求数量,后加一 保存当前请求url的位置 // 递归出口 if (finishCount >= len) { // 如果所有请求完成,则解决掉 Promise,终止递归 resolve(res); return; } const url = urls[current]; fetch(url).then(result => { finishCount ++; res[current] = result; if (current < len) { // 如果请求没有发送完,继续发送请求 next(); } }, err => { finishCount ++; res[current] = err; if (current < len) { // 如果请求没有发送完,继续发送请求 next(); } }); } }); }
await实现:
async function sendRequest(requestList,limits,callback){ // 维护一个promise队列 const promises = [] // 当前的并发池,用Set结构方便删除 const pool = new Set() //set也是Iterable<any>[]类型,因此可以放入到race里 // 开始并发执行所有的任务 for(let request of requestList){// 开始执行前,先await 判断 当前的并发任务是否超过限制 if(pool.size >= limits){// 这里因为没有try catch ,所以要捕获一下错误,不然影响下面微任务的执行 await Promise.race(pool).catch(err=>err) } const promise = request()// 拿到promise,删除请求结束后,从pool里面移除 const cb = ()=>{ pool.delete(promise) }// 注册下then的任务 promise.then(cb,cb) pool.add(promise) promises.push(promise) }// 等最后一个for await 结束,这里是属于最后一个 await 后面的 微任务 // 注意这里其实是在微任务当中了,当前的promises里面是能确保所有的promise都在其中(前提是await那里命中了if) Promise.allSettled(promises).then(callback,callback) }
总结:
写一个方法,实现最大并发数的并发请求
function multiRequestLimitNum (reqArr, limitNum) { const reqLen = reqArr.length const resArr = new Array(reqLen) let i = 0 return new Promise((resolve, reject) => { const maxNum = reqLen >= limitNum ? limitNum : reqLen while (i < maxNum) { reqFn() } async function reqFn () { if (i > reqLen - 1) return const cur = i++ const fn = reqArr[cur] const data = await fn().catch((err) => { return err }) resArr[cur] = data // 不用length判断,因为resArr里面是empty,用Object.values if (i === Object.values(resArr).length) resolve(resArr) else reqFn() } }) }
function req (res, delay) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(res)
}, delay)
})
}
multiRequestLimitNum([
req.bind(null, 1, 1000),
req.bind(null, 2, 3000),
req.bind(null, 3, 2000),
req.bind(null, 4, 100)],
2)
头条前端笔试题 - 实现一个带并发限制的promise异步调度器
上面正常执行是3421,控制并发为后执行是2314
实现思路:
function Scheduler(){ this.list=[] this.add=function(promiseCreator){ this.list.push(promiseCreator) } this.maxCount=2; var tempRunIndex=0; this.taskStart=function(){ for(var i =0 ;i<this.maxCount;i++){ request.bind(this)() } } function request(){ if(!this.list || !this.list.length || tempRunIndex>=this.maxCount){ return } tempRunIndex++ this.list.shift()().then(()=>{ tempRunIndex-- request.bind(this)() }) } } function timeout(time){ return new Promise(resolve=>{ setTimeout(resolve,time) }) } var scheduler = new Scheduler() function addTask(time,order){ scheduler.add(()=>timeout(time).then(()=>console.log(order))) } addTask(1000,1) addTask(500,2) addTask(300,3) addTask(400,4) scheduler.taskStart()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。