概述
本章列举具体应用场景以及常见大厂面试题
前文
应用场景
多个请求并行并处理一致
发起多个请求,当这些请求都返回数据了,再执行loading操作;若其中有一个请求失败了,立即执行loading操作
- 分析
需要多个请求,请求间无顺序要求;都返回数据后才执行接下来操作,这里是同步操作;需要用到Promise.all
- 代码
// 1.请求图片
const getPics = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("pics")
}, 300)
})
};
// 2.请求标题
const getTitle = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("titles")
}, 800)
})
};
// loading show
console.log("loading show")
Promise.all([getPics(),getTitle()]).then(res => {
console.log(res);
// loading hide
console.log("loading hide")
}).catch(e => {
console.log(e);
// loading hide
console.log("loading hide")
})
// 输出
loading show
["pics", "titles"]
loading hide
// 此处用打印loading来模拟loading效果
复制代码
多个请求并行但处理不一致
一个页面,需要发起多个请求,无论成功与否,都要能在下一步捕获到
- 分析
与前一个场景相比,要求请求无论成功或失败都能捕获到。若在Promise.all后直接catch,那么第一个任务失败后后面的任务不会执行;所以有两种方案:1.Promise.all后考虑单独捕获任务,然后转为成功状态,交由then处理(此时能获取所有任务状态)2.Promise.allSettled
方案
- 代码1(不是很推荐)
// 1.请求图片
const getPics = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("pics")
}, 300)
})
};
// 2.请求标题
const getTitle = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("titles")
}, 800)
})
};
Promise.all([getPics().catch(err => err),getTitle().catch(err => err)]).then(res => {
if(res[0] === "pics"){
console.log("getPics reject")
}
if(res[1] === "titles"){
console.log("getTitle resolve")
}
})
// 输出
getPics reject
getTitle resolve
复制代码
- point
上述为了简单,直接在catch里抛出了err,其实可以做很多处理的
- 代码2
// 1.请求图片
const getPics = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("pics")
}, 300)
})
};
// 2.请求标题
const getTitle = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("titles")
}, 800)
})
};
Promise.allSettled([getPics(),getTitle()]).then(res => {
if(res[0].status === "rejected"){
console.log("getPics reject")
}
if(res[1].status === "fulfilled"){
console.log("getTitle resolve")
}
})
// 输出
getPics reject
getTitle resolve
复制代码
请求超时处理
某个请求,若一定时间内未返回,则返回超时
- 分析
此处有2个处理,一个是图片请求,一个是定时处理,谁的状态先确定下来就以谁的为准,采用Promise.race
- 代码
// 1.请求图片
const getPics = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("pics")
}, 3000)
})
};
// 2.超时
const timeout = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("超时")
}, 1000)
})
};
Promise.race([getPics(),timeout()]).then(res => {
console.log(res)
}).catch(err => {
// 超时的reject或者图片请求的reject
console.log(err);
})
// 输出
超时
复制代码
面试题
使用Promise实现每隔1秒输出1,2,3
- 分析
每隔一秒输出,可以想到用setTimeout;按顺序输出,可以想到链式then调用
- 第一版
new Promise(resolve => {
resolve()
}).then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(1);
resolve();
}, 1000)
})
}).then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(2);
resolve();
}, 1000)
})
}).then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(3);
resolve();
}, 1000)
})
})
// 该版比较基本,缺点明显,重复代码过多,且把输出数组细化到了代码里
复制代码
- 分析
考虑抽出重复代码,将数组定义成变量;前者就是重复执行,类似map;但要考虑then是链式操作,下一次执行依赖于上一次结果,因而考虑reduce 代码一开始有一个resolve(),这部分是不重复的,可以考虑用作reduce的初始值;然后注意接下来对数组的操作,都是返回了一个带then回调的promise,最终的结果是一个链式调用then 所以考虑在reduce执行方法中,对于入参(旧的promise)的基础上,return一个新的带then的promise
- 第二版
const arr = [1,2,3];
arr.reduce( (prePromise, item) => {
return prePromise.then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(item);
resolve();
}, 1000)
})
})
}, Promise.resolve())
复制代码
使用Promise实现红绿灯交替重复亮
红灯3秒亮一次,黄灯2秒亮一次,绿灯1秒亮一次,让三个灯不断交替重复亮灯,三个亮灯函数已经存在
- 分析
个人觉得题目不是很清晰,其实要实现的是,红灯等待3秒后执行一次,【然后】绿灯等待2秒执行一次,【然后】黄灯1秒执行一次,然后按顺序一直执行 注意然后,这里可以构成一个then的关系;等待多少秒,可以用定时器;按顺序一直执行,可以用递归
- 代码
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
var light = function (timmer, cb) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
cb();
resolve();
}, timmer);
});
};
var step = function () {
Promise.resolve().then(function () {
return light(3000, red);
}).then(function () {
return light(2000, green);
}).then(function () {
return light(1000, yellow);
}).then(function () {
step();
});
}
step();
复制代码
实现 mergePromise 函数
实现mergePromise函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组data中
const time = (timer) => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, timer)
})
}
const ajax1 = () => time(2000).then(() => {
console.log(1);
return 1
})
const ajax2 = () => time(1000).then(() => {
console.log(2);
return 2
})
const ajax3 = () => time(1000).then(() => {
console.log(3);
return 3
})
function mergePromise () {
// 在这里写代码
}
mergePromise([ajax1, ajax2, ajax3]).then(data => {
console.log("done");
console.log(data); // data 为 [1, 2, 3]
});
// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]
复制代码
- 分析
乍一看,有点像Promise.all;但又要依次输出,我们再看上面的时延,明显前者是不行,所以考虑Promise如何才能同步执行,链式then 最后的data会依次得到上述的结果,考虑将上述结果依次push进某个数组,执行完毕后再打印
- 代码
function mergePromise (ajaxArray) {
// 存放每个ajax的结果
let data = [];
let promise = Promise.resolve();
ajaxArray.forEach(ajax => {
// 第一次的then为了用来调用ajax
// 第二次的then是为了获取ajax的结果
// 这里对promise重新赋值,其实是相当于延长了 Promise 链
promise = promise.then(ajax).then(res => {
data.push(res);
return data; // 把每次的结果返回
})
})
// 最后得到的promise它的值就是data
return promise;
}
复制代码
限制异步操作的并发个数并尽可能快的完成全部
常见的是图片加载,有最大并发加载限制:有N个图片url,最大支持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;
});
复制代码
- 分析
无,原因看上
- 代码
function limitLoad(urls, handler, limit) {
let sequence = [].concat(urls); // 复制urls
// 这一步是为了初始化 promises 这个"容器"
let promises = sequence.splice(0, limit).map((url, index) => {
return handler(url).then(() => {
// 返回下标是为了知道数组中是哪一项最先完成
return index;
});
});
// 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
return sequence
.reduce((pCollect, url) => {
return pCollect
.then(() => {
return Promise.race(promises); // 返回已经完成的下标
})
.then(fastestIndex => { // 获取到已经完成的下标
// 将"容器"内已经完成的那一项替换
promises[fastestIndex] = handler(url).then(
() => {
return fastestIndex; // 要继续将这个下标返回,以便下一次变量
}
);
})
.catch(err => {
console.error(err);
});
}, Promise.resolve()) // 初始化传入
.then(() => { // 最后三个用.all来调用
return Promise.all(promises);
});
}
limitLoad(urls, loadImg, 3)
.then(res => {
console.log("图片全部加载完毕");
console.log(res);
})
.catch(err => {
console.error(err);
});
复制代码