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

面试题 相关文章推荐
介绍一下#error预处理
Sep 25 面试题
求两个数的乘积和商数,该作用由宏定义来实现
Mar 13 面试题
Android面试题附答案
Dec 08 面试题
介绍一下游标
Jan 10 面试题
ORACLE十问
Apr 20 面试题
天网面试题
Apr 07 面试题
Linux如何为某个操作添加别名
Mar 01 面试题
ShellScript面试题一则-ShellScript编程
Jun 24 面试题
一套软件测试笔试题
Jul 25 面试题
类的返射机制中的包及核心类
Sep 12 面试题
金鑫耀Java笔试题
Sep 06 面试题
与UNIX有关的几个名词
Sep 17 面试题
北京捷通华声语音技术有限公司Java软件工程师笔试题
Apr 10 #面试题
顺丰快递Java软件工程师面试题
Jul 31 #面试题
Java软件工程师综合面试题笔试题
Sep 08 #面试题
JAVA软件工程师测试题
Jul 25 #面试题
请介绍一下WSDL的文档结构
Mar 17 #面试题
WSDL的操作类型主要有几种
Jul 19 #面试题
如何定义一个可复用的服务
Sep 30 #面试题
You might like
php实现产品加入购物车功能(1)
2020/07/23 PHP
PHP迭代与递归实现无限级分类
2017/08/28 PHP
Laravel学习教程之model validation的使用示例
2017/10/23 PHP
php apache开启跨域模式过程详解
2019/07/08 PHP
alixixi runcode.asp的代码不错的应用
2007/08/08 Javascript
js中方法重载如何实现?以及函数的参数问题
2013/08/01 Javascript
解决JQeury显示内容没有边距内容紧挨着浏览器边线
2013/12/20 Javascript
用C/C++来实现 Node.js 的模块(二)
2014/09/24 Javascript
javaScript基础语法介绍
2015/02/28 Javascript
基于javascript实现图片滑动效果
2016/05/07 Javascript
深入浅析JavaScript中的arguments对象(强力推荐)
2016/06/03 Javascript
JS解决移动web开发手机输入框弹出的问题
2017/03/31 Javascript
JS使用cookie实现只出现一次的广告代码效果
2017/04/22 Javascript
laydate 显示结束时间不小于开始时间的实例
2017/08/11 Javascript
vue.js整合vux中的上拉加载下拉刷新实例教程
2018/01/09 Javascript
JS写滑稽笑脸运动效果
2020/05/28 Javascript
vue 获取到数据但却渲染不到页面上的解决方法
2020/11/19 Vue.js
[04:13]2014DOTA2国际邀请赛 专访DC目前形势不容乐观
2014/07/12 DOTA
[15:35]教你分分钟做大人:天怒法师
2014/10/30 DOTA
[02:57]DOTA2亚洲邀请赛小组赛第四日 赛事回顾
2015/02/02 DOTA
快速查询Python文档方法分享
2017/12/27 Python
用Eclipse写python程序
2018/02/10 Python
PyCharm+PySpark远程调试的环境配置的方法
2018/11/29 Python
PyQt5 QTable插入图片并动态更新的实例
2019/06/18 Python
使用python os模块复制文件到指定文件夹的方法
2019/08/22 Python
Python浮点数四舍五入问题的分析与解决方法
2019/11/19 Python
Centos7下源码安装Python3 及shell 脚本自动安装Python3的教程
2020/03/07 Python
Python字符串及文本模式方法详解
2020/09/10 Python
html5使用canvas实现弹幕功能示例
2017/09/11 HTML / CSS
十一国庆节“向国旗敬礼”主题班会活动方案
2014/09/27 职场文书
三严三实心得体会范文
2014/10/13 职场文书
导游词400字
2015/02/13 职场文书
2016春季校长开学典礼致辞
2015/11/26 职场文书
实验心得体会范文
2016/01/25 职场文书
2019年第四季度财务部门工作计划
2019/11/02 职场文书
MYSQL数据库使用UTF-8中文编码乱码的解决办法
2021/05/26 MySQL