再来水篇文章吧,深入一下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);
})
题解
别说,有一些活跃的同学立马就举手了啊
–“我知道,我知道!!”
角落的王二蛋眼神坚定的答到。他自信的走到讲台,写下了他的答案:
0 、1 、2 、4 、3 、5 、6
二蛋总算神气一回,带着 ‘?’ 状的嘴角回到了座位上。
故事结束。
正确结果是
0 、1 、2 、3 、4 、5 、6 。
主要是 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 方法触发.
如果按照这个方法,3是在4前面的,但这种实现没什么大问题,因为大部分的场景都能满足。
但是这种实现方式不足以让我了解其实其实现原理。简单来说就是 return Promise.resolve() 执行了两次 tick, 所以前面输出了2 、3 。
但是呢,如果想知道具体的原因,需要看源码了,这个博主的文章专门更了一篇 # 【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~3 、4~6 、7~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)
})
结果如下
当然,我也折腾过动态分组
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;
})
}, Promise.resolve(urlArr))
}
limitRequest(urls, 3).then(res => {
console.log('end: ', res);
})
结果和上面一样,这里就偷个懒不截图了。
题解2(较优解)
上面的解没有达到请求最快,因为 Promise.all 的返回是传入 promise组 相互依赖的结果,当其中有一个完成了还得等其它都完成,蜗牛都睁大了双眼👀。 也不是题目想要的效果。也就有了下面的思路
将八张图片先拿三张出来,当做 初始请求,但是一旦有完成的(无论成功或失败)就 将成后面的一张的结果加入请求。
我折腾了好几个版本。
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);
})
每张图都重复加载了,这是我版本三的雏形,错误就是更换请求的urls时,每个url都重新请求了。
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);
})
不得不说,这道题有点🤏东西。
最后
这篇文章我一点没提 宏任务 , 微任务 , 很多讲的了,我就不提了。 上面 霖呆呆的 # 【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) 查漏补缺。
如果你想找虐,没错找虐,建议看看# 王者题 ,Promise面试题难不倒你了😬
|