有关Promise的几道题让一个低质量程序员失眠了

再来水篇文章吧,深入一下Promise。

题1 - 异步打印1,2,3(then链的技巧)

题干

使用Promise实现每隔1秒输出1,2,3

来自霖呆呆 文章 # 8.1 使用Promise实现每隔1秒输出1,2,3

题解

手写过 Promise 的人都知道, 如果 then 的 首参返回的是 Promise,就可以异步执行then链。 那么就可以利用 then链 完成这个题目.

let arr = [1, 2, 3]
Promise.resolve()
.then(() => new Promise((resolve => { setTimeout(() => { console.log(arr[0]); resolve() }, 1000) })))
.then(() => new Promise((resolve => { setTimeout(() => { console.log(arr[1]); resolve() }, 1000) })))
.then(() => new Promise((resolve => { setTimeout(() => { console.log(arr[2]) }, 1000) })))

也可以利用 forEach 循环

let p = Promise.resolve();
arr.forEach(item => {
    p = p.then(
    () => new Promise((resolve => { setTimeout(() => { console.log(item); resolve() }, 1000) }))
    )
})

reduce 也可以实现

arr.reduce((p,item) => {
    return p.then(
      () => new Promise((resolve => { setTimeout(() => { console.log(item); resolve() }, 1000) }))
    )
}, Promise.resolve())

这种思想很好,很多线性异步任务都可以基于这个思路。

题2 - Promise.resolve()

此题来自 # 从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节

题干

猜猜以下程序会输出什么?

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() =>{
    console.log(6);
})

题解

别说,有一些活跃的同学立马就举手了啊

–“我知道,我知道!!”

角落的王二蛋眼神坚定的答到。他自信的走到讲台,写下了他的答案:

0124356

二蛋总算神气一回,带着 ‘✔’ 状的嘴角回到了座位上。

故事结束。

正确结果是

0123456

主要是 3, 4 的次序问题,也就是 return Promise.resolve() 发生了什么事。

手搓过Promise的话,可能对 Promise.resolve 的实现很熟悉:

static resolve(value = undefined) {
    if(value instanceof MyPromise) return value;
    return new MyPromise(resolve => resolve(value))
}

首先返回的是个 Promise , 前面说过 后面的 then 方法是依靠前面的 Promise 内部的resolve 方法触发.

如果按照这个方法,4是在3前面的,但这种实现没什么大问题,因为大部分的场景都能满足。

但是这种实现方式不足以让我了解其实其实现原理。简单来说就是 return Promise.resolve() 执行了两次 tick, 所以前面输出了23

如果想知道具体的原因,需要看源码了,这个博主的文章专门更了一篇 # 【V8源码补充篇】从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节 ,如果你有 实力 + 感兴趣,那就去干。

另外博主在知乎开了个问题: # promise.then 中 return Promise.resolve 后,发生了什么? 其中 李杭帆的回答 我看了好几遍,说实话,有点吃力,懂了个大概。

因为这种代码出现的不多(实力不够 + 懒),我就没去深究了。

不过这篇 # 文章 实现的 # Promise A+ 规范值得学习。

题3 - 限制个数图片快速请求

最近看了 # 【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) 最后一道题,【霖呆呆】呆兄说是大厂题,说实话心里有点怵。

这篇文章建议大家读一读,有点意思的,作为查漏补缺不错

题干

限制异步操作的并发个数并尽可能快的完成全部

有8个图片资源的url,已经存储在数组urls中。

urls类似于['https://image1.png', 'https://image2.png', ....]

而且已经有一个函数function loadImg,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject

但有一个要求,任何时刻同时下载的链接数量不可以超过3个

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

var urls = [
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      console.log("一张图片加载完成");
      resolve(img);
    };
    img.onerror = function() {
    	reject(new Error('Could not load image at' + url));
    };
    img.src = url;
  });

我不要脸了,直接复制过来了。

不会做,不会做,怎么做?怎么做?说实话有点方!

不会还不会看看人家怎么写吗,想到这我直接往我脸上呼了一巴掌,真是不争气~~
关于呆兄的思路呢,循序渐进的氛围两种。

题解1(基础解)

基础解法思路:
将八张图片以至多三个为单位分成3组: 1~34~67~8。然后 将加载每组图片当做一个异步任务(利用Promise.all) ,完成后才进行下一组任务,直至加载完成。

我就不细说了哈,程序员嘛,代码就是最好的答案。我做的和呆兄有些许不同,实现思路是一样的,大家可以去原文查看。

  • 提前分组
function limitRequest(urls, max) {
    const groupArr = [];
    const groupLen = Math.ceil(urls.length / max);
    const results = [];

    for(let i = 0; i < groupLen; i++){
      groupArr.push(urls.slice(i*max, (i+1) * max))
    }

    return groupArr.reduce((acc, urlG) => {
      return acc.then(() => { 
        return Promise.all(urlG.map(url => loadImg(url)))
      })

      .then(res => {
        console.log('get: ', res.map(item => item.src));
        results.push(...res)
        return results
      })
    }, Promise.resolve())
  }  


limitRequest(urls, 3).then(res => {
    console.log('end: ', res)
})

结果如下
有关Promise的几道题让一个低质量程序员失眠了_第1张图片

当然,我也折腾过动态分组

function limitRequest(urls, max) {
    const len = urls.length;
    let urlArr = urls.slice(0, 3);
    const results = [];
    const groupLen = Math.ceil(len / max);

    return Array(groupLen).fill(0).reduce((acc, url, index) => {
      return acc
        .then((reqs) => Promise.all([...reqs.map(url => loadImg(url))]))
        .then(res => {
          console.log('get: ', res.map(item => item.src));
          results.push(...res)
          if(index === groupLen - 1) {
            return results;
          }
          // 倒数第二
          else if(index === groupLen - 2) {
            const i = (index + 1) * 3;
            urlArr = urls.slice(i, len)

          } else {
            const i = (index + 1) * 3;
            urlArr = urls.slice(i, i + max)
          }
          return urlArr;
        })
        // return acc; // 这样会一开始就全部执行, 因为 acc 没改变
        /*
          结果就是 执行如下代码
            Promise.resolve().then(() => Promise).then()
            Promise.resolve().then(() => Promise).then()
            Promise.resolve().then(() => Promise).then()
        */
      }, Promise.resolve(urlArr))
  }  

  limitRequest(urls, 3).then(res => {
    console.log('end: ', res);
  })

结果和上面一样,这里就偷个懒不截图了。

题解2(较优解)

上面的解没有达到请求最快,因为 Promise.all 的返回是传入 promise组 相互依赖的结果,当其中有一个完成了还得等其它都完成,蜗牛都睁大了双眼。 也不是题目想要的效果。也就有了下面的思路

将八张图片先拿三张出来,当做 初始请求,但是一旦有完成的(无论成功或失败)就 将成后面的一张的结果加入请求。

我折腾了好几个版本。

  • 版本1:存在重复加载的问题
function limitRequest(urlArr, max) {

    const urls = [].concat(urlArr);
    let currentUrls = urls.splice(0, 3);
    let resolvedUrl = '';
    const len = urls.length;
    const results = []

    return urls.reduce((acc, url, index) => {
      return acc.then(
          () => Promise.race(currentUrls.map(url => loadImg(url)))
        ).then(img => {
          console.log('wow~~', index, img);
          results.push(img)
          resolvedUrl = img.src;
          updateUrls(resolvedUrl, url)
        })
        .catch(err => {
          console.log(`${index} 张图片加载失败了`);
          resolvedUrl = err.message.match(/http[s]?:\/\/.{1,}$/);
          results.push(index)
          updateUrls(resolvedUrl, url);
        }).then(() => {
          return new Promise(resolve => {
            if (index === urls.length - 1) {
              Promise.all(currentUrls.map(url => loadImg(url))).then(res => {
                results.push(...res)
                resolve(results);
              })
            } else resolve()
          })
        })
    }, Promise.resolve())

    function updateUrls(removeUrl, addUrl) {
      const idx = currentUrls.findIndex(url => url === removeUrl);
      currentUrls.splice(idx, 1, addUrl)
    }
 }
limitRequest(urls, 3).then(res => {
    console.log('end', res);
})

有关Promise的几道题让一个低质量程序员失眠了_第2张图片
每张图都重复加载了,这是我版本三的雏形,错误就是更换请求的urls时,每个url都重新请求了。

  • 版本2:可行(嵌套回调)
function limitRequest(urlArr, max) {
    return new Promise(resolve => {

      const results = [];
      const len = urlArr.length;
      const Urls = [].concat(urlArr);
      let loadedIndex = max;

      function request(index) {
        const url = Urls[index];
        return loadImg(url).then(res => {
          console.log(`~wa o~`, url, 'toLoad', loadedIndex);
          results.push(res)
          if (loadedIndex < Urls.length) {
            request(loadedIndex)
            loadedIndex++
          } 
          if(results.length === len) resolve(results)

        }).catch(err => {
          results.push(index)
          console.log(`~uha~, 第 ${ index } 图片加载出错了`);
          if (loadedIndex < Urls.length) {
            request(loadedIndex)
            loadedIndex++;
          }
          if(results.length === len) resolve(results)
        })
      }

      for (let i = 0; i < max; i++) {
        request(i);
      }
    })
 }

limitRequest(urls, 3).then(res => {
    console.log('end', res);
})
  • 版本3:可行(最后看了呆兄的实现,将版本1改了一下)
function limitRequest(urlArr, max) {

    const urls = [].concat(urlArr);
    let currentUrls = urls.splice(0, 3)
    let currentPromises = currentUrls.map(url => loadImg(url));
    let resolvedUrl = '';
    const len = urls.length;
    const results = []

    return urls.reduce((acc, url, index) => {
      return acc.then(
          () => Promise.race(currentPromises)
        ).then(img => {
          console.log('wow~~', index, img);
          results.push(img)
          resolvedUrl = img.src;
          updatePromises(resolvedUrl, url)
        })
        .catch(err => {
          console.log(`${index} 张图片加载失败了`);
          resolvedUrl = err.message.match(/http[s]?:\/\/.{1,}$/);
          results.push(index)
          updatePromises(resolvedUrl, url);
        }).then(() => {
          return new Promise(resolve => {
            if (index === urls.length - 1) {
              Promise.all(currentPromises).then(res => {
                results.push(...res)
                resolve(results);
              })
            } else resolve()
          })
        })
    }, Promise.resolve())

    function updatePromises(removeUrl, addUrl) {
      const idx = currentUrls.findIndex(url => url === removeUrl);
      currentUrls.splice(idx, 1, addUrl)
      currentPromises[idx] = loadImg(addUrl)
    }
}

limitRequest(urls, 3).then(res => {
    console.log('end', res);
})

有关Promise的几道题让一个低质量程序员失眠了_第3张图片

不得不说,这道题有点东西。

最后

这篇文章我一点没提 宏任务, 微任务, 很多讲的了,我就不提了。
上面 霖呆呆的 # 【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) 查漏补缺。

如果你想找虐,没错找虐,建议看看# 王者题 ,Promise面试题难不倒你了

你可能感兴趣的:(JS,node.js,javascript,vue.js)