迭代和递归:一道面试题引发的思考(2)

前文阅读:迭代和递归:一道面试题引发的思考

不说废话,直接上正文。

案例

已知信息如下,loadUrls用于控制多个异步请求,通过max限制同一时刻的并发请求数,其返回Promise,结果为所有urls的异步结果。有点类似于Promise.all,不过内部对最大并发数做了控制。

const urls = ['url1', 'url2', 'url3', 'url4','url5'];
function load(url){
  return new Promise((resolve, reject) => {
      resolve(`${url}result`);
  })
}
function loadUrls(urls, max) { }

分析:每次最多同时有max个异步请求,有一个请求返回,补上下一个请求,以此类推。

关键信息

1)首先需要发max个请求;
2)只有一个请求结束,才会补上一个请求发出;
3)当urls中的url全部处理后,结束并返回所有异步结果。
很显然,这是个循环处理的问题。前面我们讨论过递归与迭代,不再赘述。

迭代和递归的选择

由于while循环是同步控制,顺序执行的;而上例是异步的,执行循环的时机由.then决定,因此这里我们选择递归实现。

明确循环体和出口条件

按上期制定的原则,接下来最重要的是明确出口条件以及循环体:
1)出口条件:urls的中url是否已全部处理完成;
2)循环体:一个请求返回后,load().then()中触发下一个url进行load
需要注意下,上例多了个条件:限制max个并发请求,这个放到循环函数中好像没法处理啊,怎么办呢?
加一条原则:和循环过程无关的条件提到函数外

基于上面的分析,对loadUrls进行改造:
1) 由于loadUrls需要返回Promise对象,因此第一步可先构造返回:

function loadUrls(urls, max) { 
    return Promise((resolve, reject) => {
        //...
    });
}

2) 再者由于loadUrls返回的Promise对象得到的结果是所有异步请求的结果,因此,需要其resolve:

function loadUrls(urls, max) { 
    return Promise((resolve, reject) => {
        //启动max个请求,传入resolve,供执行结束返回结果
        for(let i = 0; i < max; i++ ) {
            loadNext(urls[i], i, resolve);
        }
    });
}

3) 递归函数loadNext:

function loadUrls(urls, max) {
    //记录所有异步请求的结果
    let result = [];
    //记录已经发出请求的序号
    let loadIndex = max - 1;
    //递归函数,初始条件以提出
    function loadNext(url, index, resolve) {
        load(url).then((val) => {
            result[index] = val;
            //下一个待发起请求url的下标
            loadIndex++;
            //判断是否所有的url已发出请求
            if(loadIndex < urls.length) {    
                //对下一个待请求的url进行处理
                loadNext(urls[loadIndex], loadIndex, resolve);
            }else {
            //所有url处理完毕,进行resolve
             resolve(result);
            }
    })}
    return new Promise((resolve, reject) => {
        for(let i = 0; i < max; i++ ) {
            loadNext(urls[i], i, resolve);
        }

    })
}

执行结果:

loadUrls(urls, 2).then(i => console.log(i))
// ["url1result", "url2result", "url3result", "url4result", "url5result"]
总结

递归函数如果遇到和循环逻辑无关的,不要慌,试着提出函数外考虑,那么关于递归总结的三大原则:
1)出口条件
2)循环逻辑
3)初始逻辑(考虑提出函数外执行)
迭代的不同在于:while可以简单理解为一个独立的'循环函数',因此其初始条件(如果有的话),并不需要提出函数外,只要写在while外即可。

() => {
  //1.初始信息
  while() {  //2.终止条件
    //3.循环体
  }
}

获取更多干货分享,请【扫码关注】~
迭代和递归:一道面试题引发的思考(2)_第1张图片

你可能感兴趣的:(javascript,前端,程序员,递归)