Promise面试题详解之控制并发


Posted in 面试题 onMay 14, 2021

前言

在写这篇文章的时候我有点犹豫,因为先前写过一篇类似的,一道关于并发控制的面试题,只不过那篇文章只给出了一种解决方案,后来在网上又陆续找到两种解决方案,说来惭愧,研究问题总是浅尝辄止,所以今天便放在一起,借着这道面试题再重新梳理一下。

题目是这样的:

有 8 个图片资源的 url,已经存储在数组 urls 中(即urls = [‘http://example.com/1.jpg', …., ‘http://example.com/8.jpg']),而且已经有一个函数 function loadImg,输入一个 url 链接,返回一个 Promise,该 Promise 在图片下载完成的时候 resolve,下载失败则 reject。

但是我们要求,任意时刻,同时下载的链接数量不可以超过 3 个。

请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。

已有代码如下:

var urls = [
'https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg', 
'https://www.kkkk1000.com/images/getImgData/gray.gif', 
'https://www.kkkk1000.com/images/getImgData/Particle.gif', 
'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 
'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 
'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 
'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 
'https://www.kkkk1000.com/images/wxQrCode2.png'
];
function loadImg(url) {
    return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = function () {
            console.log('一张图片加载完成');
            resolve();
        }
        img.onerror = reject
        img.src = url
    })
};

看到这个题目的时候,脑袋里瞬间想到了高效率排队买地铁票的情景,那个情景类似下图:

Promise面试题详解之控制并发

上图这样的排队和并发请求的场景基本类似,窗口只有三个,人超过三个之后,后面的人只能排队了。

首先想到的便是利用递归来做,就如这篇文章采取的措施一样,代码如下:

//省略代码

var count = 0;
//对加载图片的函数做处理,计数器叠加计数
function bao(){
    count++;
    console.log("并发数:",count)
    //条件判断,urls长度大于0继续,小于等于零说明图片加载完成
    if(urls.length>0&&count<=3){
    //shift从数组中取出连接
        loadImg(urls.shift()).then(()=>{
        //计数器递减
            count--
            //递归调用
            }).then(bao)
    }
}
function async1(){
//循环开启三次
    for(var i=0;i<3;i++){
        bao();
    }
}
async1()

以上是最常规的思路,我将加载图片的函数loadImg封装在bao函数内,根据条件判断,是否发送请求,请求完成后继续递归调用。

以上代码所有逻辑都写在了同一个函数中然后递归调用,可以优化一下,代码如下:

var count = 0;

// 封装请求的异步函数,增加计数器功能
function request(){
    count++;
    loadImg(urls.shift()).then(()=>{
            count--
            }).then(diaodu)

    
}
// 负责调度的函数
function diaodu(){
    if(urls.length>0&&count<=3){
        request();
    }
}

function async1(){
    for(var i=0;i<3;i++){
        request();
    }
}
async1()

上面代码将一个递归函数拆分成两个,一个函数只负责计数和发送请求,另外一个负责调度。

这里的请求既然已经被封装成了Promise,那么我们用Promise和saync、await来完成一下,代码如下:

//省略代码

// 计数器
var count = 0;
// 全局锁
var lock = [];
var l = urls.length;
async function bao(){
    if(count>=3){
        //超过限制利用await和promise进行阻塞;
        let _resolve;
        await new Promise((resolve,reject)=>{
            _resolve=resolve;
            // resolve不执行,将其推入lock数组;
            lock.push(_resolve);
        });
    }
    if(urls.length>0){
        console.log(count);
        count++
        await loadImg(urls.shift());
        count--;
        lock.length&&lock.shift()()
    }
}
for (let i = 0; i < l; i++) {
    bao();
}

大致思路是,遍历执行urls.length长度的请求,但是当请求并发数大于限制时,超过的请求用await结合promise将其阻塞,并且将resolve填充到lock数组中,继续执行,并发过程中有图片加载完成后,从lock中推出一项resolve执行,lock相当于一个叫号机;

以上代码可以优化为:

// 计数器
var count = 0;
// 全局锁
var lock = [];
var l = urls.length;
// 阻塞函数
function block(){
    let _resolve;
    return  new Promise((resolve,reject)=>{
        _resolve=resolve;
        // resolve不执行,将其推入lock数组;
        lock.push(_resolve);
    });
}
// 叫号机
function next(){
    lock.length&&lock.shift()()
}
async function bao(){
    if(count>=3){
        //超过限制利用await和promise进行阻塞;
        await block();
    }
    if(urls.length>0){
        console.log(count);
        count++
        await loadImg(urls.shift());
        count--;
        next()
    }
}
for (let i = 0; i < l; i++) {
    bao();
}

最后一种方案,也是我十分喜欢的,思考好久才明白,大概思路如下:

用 Promise.race来实现,先并发请求3个图片资源,这样可以得到 3 个 Promise实例,组成一个数组promises ,然后不断的调用 Promise.race 来返回最快改变状态的 Promise,然后从数组(promises )中删掉这个 Promise 对象实例,再加入一个新的 Promise实例,直到全部的 url 被取完。

代码如下:

//省略代码
function limitLoad(urls, handler, limit) {
    // 对数组做一个拷贝
    const sequence = [].concat(urls)
    let promises = [];

    //并发请求到最大数
    promises = sequence.splice(0, limit).map((url, index) => {
        // 这里返回的 index 是任务在 promises 的脚标,
        //用于在 Promise.race 之后找到完成的任务脚标
        return handler(url).then(() => {
            return index
        });
    });

    (async function loop() {
        let p = Promise.race(promises);
        for (let i = 0; i < sequence.length; i++) {
            p = p.then((res) => {
                promises[res] = handler(sequence[i]).then(() => {
                    return res
                });
                return Promise.race(promises)
            })
        }
    })()
}
limitLoad(urls, loadImg, 3)

第三种方案的巧妙之处,在于使用了Promise.race。并且在循环时用then链串起了执行顺序。

以上便是关于并发控制的一点点思考,有使用promise的,有不使用promise的,关键在于灵活运用,通过这次梳理,你有哪些思考呢

总结

到此这篇关于Promise面试题详解之控制并发的文章就介绍到这了,更多相关Promise控制并发内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!


Tags in this post...

面试题 相关文章推荐
北京银河万佳Java面试题
Mar 21 面试题
简述索引存取方法的作用和建立索引的原则
Mar 26 面试题
什么叫应用程序域?什么是受管制的代码?什么是强类型系统?什么是装箱和拆箱?
Aug 13 面试题
广州盈通面试题
Dec 05 面试题
私有程序集与共享程序集有什么区别
Apr 05 面试题
TCP协议通讯的过程和步骤是什么
Oct 18 面试题
Linux的文件类型
Mar 07 面试题
飞利信loadrunner和软件测试笔试题
Sep 22 面试题
传统软件工程与面向对象的软件工程有什么区别
May 31 面试题
编程用JAVA解析XML的方式
Jul 07 面试题
Boolean b = new Boolean(“abcde”); 会编译错误码
Nov 27 面试题
Java servlet面试题
Mar 04 面试题
北京捷通华声语音技术有限公司Java软件工程师笔试题
Apr 10 #面试题
顺丰快递Java软件工程师面试题
Jul 31 #面试题
Java软件工程师综合面试题笔试题
Sep 08 #面试题
JAVA软件工程师测试题
Jul 25 #面试题
请介绍一下WSDL的文档结构
Mar 17 #面试题
WSDL的操作类型主要有几种
Jul 19 #面试题
如何定义一个可复用的服务
Sep 30 #面试题
You might like
十大感人催泪爱情动漫 第一名至今不忍在看第二遍
2020/03/04 日漫
基于PHPExcel的常用方法总结
2013/06/13 PHP
PHP使用正则表达式清除超链接文本
2013/11/12 PHP
php浏览历史记录的方法
2015/03/10 PHP
CodeIgniter自定义控制器MY_Controller用法分析
2016/01/20 PHP
thinkPHP实现多字段模糊匹配查询的方法
2016/12/01 PHP
setTimeout函数兼容各主流浏览器运行执行效果实例
2013/06/13 Javascript
JS日期和时间选择控件升级版(自写)
2013/08/02 Javascript
谷歌浏览器调试JavaScript小技巧
2014/12/29 Javascript
js获取域名的方法
2015/01/27 Javascript
jfreechart插件将数据展示成饼状图、柱状图和折线图
2015/04/13 Javascript
你有必要知道的25个JavaScript面试题
2015/12/29 Javascript
Nodejs抓取html页面内容(推荐)
2016/08/11 NodeJs
Javascript生成带参数的二维码示例
2016/10/10 Javascript
基于JavaScript实现下拉列表左右移动代码
2017/02/07 Javascript
详解Vue2+Echarts实现多种图表数据可视化Dashboard(附源码)
2017/03/21 Javascript
Vue的elementUI实现自定义主题方法
2018/02/23 Javascript
关于引入vue.js 文件的知识点总结
2020/01/28 Javascript
详谈Object.defineProperty 及实现数据双向绑定
2020/07/18 Javascript
零基础写python爬虫之神器正则表达式
2014/11/06 Python
Python操作串口的方法
2015/06/17 Python
python互斥锁、加锁、同步机制、异步通信知识总结
2018/02/11 Python
对Python中9种生成新对象的方法总结
2018/05/23 Python
Python读取txt某几列绘图的方法
2018/10/14 Python
Python3中函数参数传递方式实例详解
2019/05/05 Python
python中单下划线(_)和双下划线(__)的特殊用法
2019/08/29 Python
python 实现学生信息管理系统的示例
2020/11/28 Python
h5页面背景图很长要有滚动条滑动效果的实现
2021/01/27 HTML / CSS
美国知名的时尚购物网站:Anthropologie
2016/12/22 全球购物
应届生体育教师自荐信
2013/10/03 职场文书
党员教师工作决心书
2014/03/13 职场文书
小班幼儿评语大全
2014/04/30 职场文书
2014年社区宣传工作总结
2014/12/02 职场文书
python3实现无权最短路径的方法
2021/05/12 Python
Python爬取用户观影数据并分析用户与电影之间的隐藏信息!
2021/06/29 Python
Python docx库删除复制paragraph及行高设置图片插入示例
2022/07/23 Python