前言
前端时间有个大佬在qq群发了3道他面试遇到的前端上机题目,其中有一道是有关Promise的,当时对于这题如何实现实在没有头绪于是请教了大佬,理解了实现原理后对Promise的理解有了一定的改正和提升。
题目详情
function fetch(url, time) {
return new Promise(resolve => {
setTimeout(() => {
resolve(url)
}, time)
})
}
const tasks = [
fetch('1', 1000),
fetch('2', 2000),
fetch('3', 2000),
fetch('4', 1000),
fetch('5', 2000),
fetch('6', 1000)
]
function fetchLimited(tasks = []) {
}
fetchLimited(tasks)
// 要求根据给定数组调用函数,实现如下输出
/*
1
2
4
3
6
5
*/
题目的要求就是一次只对任务列表中的两个Promise做出响应,并按要求输出。
解法
function fetchLimited(tasks = []) {
const formatterTasks = tasks.map(p => ({ isPending: false, p }));
const headPromises = [formatterTasks.splice(0,1)[0], formatterTasks.splice(0,1)[0]]
function next(){
headPromises.forEach((obj, i) => {
if(!obj.isPending) {
obj.isPending = true;
obj.p.then((value) => {
console.log(value);
if (formatterTasks.length) {
const nextObj = formatterTasks.splice(0, 1)[0];
headPromises[i] = nextObj;
next();
}
})
}
})
}
next();
}
分析之前
在我们分析fetchLimited这个函数之前,先谈谈Promise是如何运作的,我们在fetch函数中加上console.log打印当前的url
function fetch(url, time) {
return new Promise(resolve => {
setTimeout(() => {
console.log(url)
resolve(url)
}, time)
})
}
const tasks = [
fetch('1', 1000),
fetch('2', 2000),
fetch('3', 2000),
fetch('4', 1000),
fetch('5', 2000),
fetch('6', 1000)
]
输出如下
当我们创建一个Promise时,构造函数中的操作都是同步执行的,定义tasks数组时就会创建6个定时器对象打印url并resolve(url)。
但是对于.then里面的操作则是异步执行的,所有.then里的操作都会进入队列中,在Promise状态改变时尽快执行。.then定义的先后顺序不会影响到内容的执行,例子如下
function fetch(url, time) {
return new Promise(resolve => {
setTimeout(() => {
resolve(url)
}, time)
})
}
const tasks = [
fetch('1', 1000),
fetch('2', 2000),
fetch('3', 2000),
fetch('4', 1000),
fetch('5', 2000),
fetch('6', 1000)
]
tasks[0].then(v => {
console.log(v)
})
tasks[1].then(v => {
console.log(v)
})
tasks[2].then(v => {
console.log(v)
})
tasks[3].then(v => {
console.log(v)
})
tasks[4].then(v => {
console.log(v)
})
tasks[5].then(v => {
console.log(v)
})
运行结果
我们把.then的执行顺序进行替换,再次运行查看结果
可以看出替换.then的执行顺序并不影响结果,执行顺序只与Promise的通知顺序有关系,因此如果我们想要实现一次只响应两次请求就要通过.then的嵌套控制输出顺序
分析开始
重新构造一个包含每个Promise回调执行状态的数组
const formatterTasks = tasks.map(p => ({ isPending: false, p }));
从数组中取出前两项
const headPromises = [formatterTasks.splice(0,1)[0], formatterTasks.splice(0,1)[0]]
定义next函数用于嵌套调用
function next() {
}
next()
重点部分
headPromises.forEach((obj, i) => {
if(!obj.isPending) {
obj.isPending = true;
obj.p.then((value) => {
console.log(value);
if (formatterTasks.length) {
const nextObj = formatterTasks.splice(0, 1)[0];
headPromises[i] = nextObj;
next();
}
})
}
})
这里不好概括讲解,我会用模拟一遍执行流程帮助大家理解,整个过程比较冗长,只要理解了部分后面都是重复的过程,可以跳过不看。
headPromise: [{isPending: false, fetch('1', 1000)}, {isPending: false, fetch('2', 2000)}]
i:0
obj:{isPending: false, fetch('1', 1000)}
执行obj.isPending = true;修改isPending的值
调用fetch('1', 1000).then(),因为Promise的状态还没改变,因此不会执行.then里面的内容,压入队列等待执行
i:1
obj:{isPending: false, fetch('2', 2000)}
执行obj.isPending = true;修改isPending的值
调用fetch('2', 2000).then(),因为Promise的状态还没改变,因此不会执行.then里面的内容,压入队列等待执行
等待Promise状态改变
1s后fetch('1', 1000)的Promise状态改变,开始执行.then里面的内容
调用console.log(value)打印出1
判断是否还有需要执行的任务,如果有取出最上面的任务{isPending: false, fetch('3', 2000)}
fetch('1', 1000)是在i为0的状态下执行的,因此headPromises[0] = {isPending: false, fetch('3', 2000)}
执行next()
此时headPromise: [{isPending: false, fetch('3', 2000)}, {isPending: true, fetch('2', 2000)}]
i:0
obj:{isPending: false, fetch('3', 2000)}
执行obj.isPending = true;修改isPending的值
调用fetch('3', 2000).then(),因为Promise的状态还没改变,因此不会执行.then里面的内容,压入队列等待执行
i:1
obj:{isPending: true, fetch('2', 2000)}
obj.isPending为true不会执行操作
2s后
现在队列中存在2个回调,分别是fetch('2', 2000).then()和fetch('3', 2000).then(),由于定义时fetch('2', 2000)是先定义的,因此优先响应fetch('2', 2000).then()
fetch('2', 2000).then()开始执行
调用console.log(value)打印出2
取出最上面的任务{isPending: false, fetch('4', 1000)},fetch('2', 2000)是在i为1的状态下执行的,因此headPromises[1] = {isPending: false, fetch('4', 1000)}
执行next()
此时headPromise: [{isPending: true, fetch('3', 2000)}, {isPending: false, fetch('4', 1000)}]
i:0
obj:{isPending: true, fetch('3', 2000)}
obj.isPending为true不会执行操作
i:1
obj:{isPending: false, fetch('4', 1000)}
执行obj.isPending = true;修改isPending的值
调用fetch('4', 1000).then(),此时队列里有fetch('3', 2000).then()和fetch('4', 1000).then(),由于fetch('4', 1000)在1s后Promise状态就改变了,因此执行顺序先于fetch('3', 2000)
fetch('4', 1000).then()开始执行
调用console.log(value)打印出4
取出最上面的任务{isPending: false, fetch('5', 2000)},fetch('4', 1000)是在i为1的状态下执行的,因此headPromises[1] = {isPending: false, fetch('5', 2000)}
执行next()
此时headPromise: [{isPending: true, fetch('3', 2000)}, {isPending: false, fetch('5', 2000)}]
i:0
obj:{isPending: true, fetch('3', 2000)}
obj.isPending为true不会执行操作
i:1
obj:{isPending: false, fetch('5', 2000)}
执行obj.isPending = true;修改isPending的值
调用fetch('4', 1000).then(),此时队列里有fetch('3', 2000).then()和fetch('5', 2000).then(),由于二者都是2s后执行而fetch('3', 2000)是先定义的,因此优先响应fetch('3', 2000).then()
fetch('3', 2000).then()开始执行
调用console.log(value)打印出3
取出最上面的任务{isPending: false, fetch('6', 1000)},fetch('3', 1000)是在i为0的状态下执行的,因此headPromises[0] = {isPending: false, fetch('6', 2000)}
执行next()
此时headPromise: [{isPending: false, fetch('6', 1000)}, {isPending: false, fetch('5', 2000)}]
i:0
obj:{isPending: false, fetch('6', 1000)}
执行obj.isPending = true;修改isPending的值
调用fetch('6', 1000).then(),此时队列里有fetch('5', 2000).then()和fetch('6', 1000).then(),由于fetch('6', 1000)在1s后Promise状态就改变了,因此执行顺序先于fetch('5', 2000)
fetch('6', 1000).then()开始执行
调用console.log(value)打印出6
此时已经没有多余的任务可取了,因此执行结束
接下来队列中只剩下fetch('5', 2000).then()
fetch('6', 1000).then()开始执行
调用console.log(value)打印出6
此时已经没有多余的任务可取了,因此执行结束
i:1
obj:{isPending: true, fetch('5', 2000)}
obj.isPending为true不会执行操作
整个执行结果执行完了