Promise面试题——给出一个请求数组,一次只响应两次请求

313 阅读6分钟

前言

前端时间有个大佬在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)
]

输出如下

image.png

当我们创建一个Promise时,构造函数中的操作都是同步执行的,定义tasks数组时就会创建6个定时器对象打印urlresolve(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)
})

运行结果

image.png

我们把.then的执行顺序进行替换,再次运行查看结果

image.png

可以看出替换.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状态改变

1sfetch('1', 1000)的Promise状态改变,开始执行.then里面的内容

调用console.log(value)打印出1

判断是否还有需要执行的任务,如果有取出最上面的任务{isPending: false, fetch('3', 2000)}

fetch('1', 1000)是在i0的状态下执行的,因此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.isPendingtrue不会执行操作

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.isPendingtrue不会执行操作

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)1sPromise状态就改变了,因此执行顺序先于fetch('3', 2000)

fetch('4', 1000).then()开始执行

调用console.log(value)打印出4

取出最上面的任务{isPending: false, fetch('5', 2000)}fetch('4', 1000)是在i1的状态下执行的,因此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)是在i0的状态下执行的,因此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)1sPromise状态就改变了,因此执行顺序先于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.isPendingtrue不会执行操作

整个执行结果执行完了