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...

面试题 相关文章推荐
说说你所熟悉或听说过的j2ee中的几种常用模式?及对设计模式的一些看法
May 24 面试题
几道数据库的概念性面试题
May 30 面试题
什么是唯一索引
Jul 05 面试题
NET程序员上机面试题
May 23 面试题
你对IPv6了解程度
Feb 09 面试题
C#笔试题和英文面试题
Feb 07 面试题
Linux常见面试题
Mar 18 面试题
什么时候用assert
May 08 面试题
J2EE中常用的名词进行解释
Nov 09 面试题
如何将一个描述日期或日期/时间的字符串转换为一个Date对象
Oct 13 面试题
Promise面试题详解之控制并发
May 14 面试题
Java面试题冲刺第十七天--基础篇3
Aug 07 面试题
北京捷通华声语音技术有限公司Java软件工程师笔试题
Apr 10 #面试题
顺丰快递Java软件工程师面试题
Jul 31 #面试题
Java软件工程师综合面试题笔试题
Sep 08 #面试题
JAVA软件工程师测试题
Jul 25 #面试题
请介绍一下WSDL的文档结构
Mar 17 #面试题
WSDL的操作类型主要有几种
Jul 19 #面试题
如何定义一个可复用的服务
Sep 30 #面试题
You might like
德劲1103二次变频版的打磨
2021/03/02 无线电
smarty的保留变量问题
2008/10/23 PHP
php构造函数实例讲解
2013/11/13 PHP
php断点续传之如何分割合并文件
2014/03/22 PHP
PHP检测字符串是否为UTF8编码的常用方法
2014/11/21 PHP
php超快高效率统计大文件行数
2015/07/05 PHP
JavaScript中this关键字使用方法详解
2007/03/08 Javascript
统计出现最多的字符次数的js代码
2010/12/03 Javascript
使用jQuery fancybox插件打造一个实用的数据传输模态弹出窗体
2013/01/15 Javascript
GridView中获取被点击行中的DropDownList和TextBox中的值
2013/07/18 Javascript
flash遮住div问题的正确解决方法
2014/02/27 Javascript
JavaScript实现非常简单实用的下拉菜单效果
2015/08/27 Javascript
jQuery实现别踩白块儿网页版小游戏
2017/01/18 Javascript
Nodejs基于LRU算法实现的缓存处理操作示例
2017/03/17 NodeJs
基于hover的用法实例(推荐)
2017/07/04 Javascript
浅谈vue项目重构技术要点和总结
2018/01/23 Javascript
详解使用jQuery.i18n.properties实现js国际化
2018/05/04 jQuery
JavaScript利用键盘码控制div移动
2020/03/19 Javascript
0基础学习前端开发的一些建议
2020/07/14 Javascript
js节流防抖应用场景,以及在vue中节流防抖的具体实现操作
2020/09/21 Javascript
Python 实现链表实例代码
2017/04/07 Python
Python通过命令开启http.server服务器的方法
2017/11/04 Python
Python实现霍夫圆和椭圆变换代码详解
2018/01/12 Python
Python使用Phantomjs截屏网页的方法
2018/05/17 Python
Python爬虫常用库的安装及其环境配置
2018/09/19 Python
Python及Pycharm安装方法图文教程
2019/08/05 Python
使用OpenCV实现仿射变换—平移功能
2019/08/29 Python
快速解决jupyter notebook启动需要密码的问题
2020/04/21 Python
Python基于Hypothesis测试库生成测试数据
2020/04/29 Python
数学高效课堂实施方案
2014/03/29 职场文书
党的群众路线教育实践活动个人对照检查材料(乡镇)
2014/11/05 职场文书
捐助感谢信
2015/01/22 职场文书
士兵突击观后感
2015/06/16 职场文书
使用Python脚本对GiteePages进行一键部署的使用说明
2021/05/27 Python
实操Python爬取觅知网素材图片示例
2021/11/27 Python
Java实现超大Excel文件解析(XSSF,SXSSF,easyExcel)
2022/07/15 Java/Android