一道字节的面试题

322 阅读4分钟

1.题目如下

const request1 = () => new Promise(resolve => setTimeout(() => resolve(1), 4000))
const request2 = () => new Promise(resolve => setTimeout(() => resolve(2), 1000))
const request3 = () => new Promise(resolve => setTimeout(() => resolve(3), 2000))
const request4 = () => new Promise(resolve => setTimeout(() => resolve(4), 1500))

cosnt scheduler = max => {
        // todo
}

const s = scheduler(2)
s(request1).then(res => console.log(res))
s(request2).then(res => console.log(res))
s(request3).then(res => console.log(res))
s(request4).then(res => console.log(res))
// 打印顺序如下
// 2, 3, 1, 4

2. 题目思考

额,果不其然,和去年面试字节的时候一样,字节还是喜欢搞这种题目。那就分析一下,max是每次运行最大的任务的总数,所以刚开始是request1request2在执行状态,然后1000ms之后,request2执行了,打印2,然后request3进入执行状态,因为request3需要执行3000ms,所以先于request1执行,打印3,然后request4进入执行状态,过了1000ms``request1执行完毕,输出1,最后输出4.

  1. s是一个函数,所以scheduler返回值是一个函数
const scheduler = max => {
    return function (fn) {
    }
}
  1. s的返回值可以调用then方法,那么s返回值是一个Promise对象
const scheduler = max => {
    return function (fn) {
        return new Promise()
    }
}
  1. 如果按照以上分析的顺序执行,那么我们需要一个queue来存储需要执行的任务,然后还需要一个异步任务来在所有任务存储之后开始执行
const scheduler = max => {
    const queue = [] // 用来存储任务的队列
    let pending = false // 用来判断当前的异步任务有没有执行
    function run () {} // 用来执行存储的任务
    return function (fn) {
        return new Promise(resolve => {
            queue.push(fn)
            if (!pending) {
                pending = true
                Promise.resolve().then(run)
            }
        })
    }
}
  1. 当我们开始执行任务的时候,我们怎么知道正在执行了多少任务?我们怎么知道执行到了哪个任务?于是新增runningCount表示正在执行多少任务,index表示执行到了那个任务
const scheduler = max => {
    const queue = [] // 用来存储任务的队列
    let pending = false // 用来判断当前的异步任务有没有执行
    let runningCount = 0
    let index = 0
    function run () {} // 用来执行存储的任务
    return function (fn) {
        return new Promise(resolve => {
            queue.push(fn)
            if (!pending) {
                pending = true
                Promise.resolve().then(run)
            }
        })
    }
}
  1. 现在有出现一个问题就是,我们如何知道当前运行的任务执行完了?然后通知下一个任务开始执行?所以我们可以把放进队列的任务进行封装一下,可以传入f参数
const scheduler = max => {
    const queue = [] // 用来存储任务的队列
    let pending = false // 用来判断当前的异步任务有没有执行
    let runningCount = 0
    let index = 0
    function run () {} // 用来执行存储的任务
    return function (fn) {
        return new Promise(resolve => {
            queue.push(function (f) {
                fn()
                f()
            })
            if (!pending) {
                pending = true
                Promise.resolve().then(run)
            }
        })
    }
}
  1. 所以run开始执行的时候,需要递归执行,即当有任务完成的时候,需要通知下一个任务入队列,所以每个递归函数都会有个终止条件,这个终止条件就是index大于queue中的最后的那个下标
const scheduler = max => {
    const queue = [] // 用来存储任务的队列
    let pending = false // 用来判断当前的异步任务有没有执行
    let runningCount = 0
    let index = 0
    function run () {  // 用来执行存储的任务
        if (index >= queue.length) return
        while(runningCount < max) {
            queue[index](() => {
                // 这里面的是任务完成之后的回调
                runningCount-- // 当前正在运行的额任务数量减一
                run() // 下一个任务开始执行
            })
            runningCount++ // 正在执行的数量+1
            index ++ // 队列中执行的任务的下标 + 1
        }
    }
    return function (fn) {
        return new Promise(resolve => {
            queue.push(function (f) {
                fn()
                f()
            })
            if (!pending) {
                pending = true
                Promise.resolve().then(run)
            }
        })
    }
}
  1. 写成上面那样发现执行顺序没有达到要求,额,这是为啥?因为fn函数都是异步的,其实我们没有等他们执行完毕就通知下一个进来了,所以async await排上用场了
const scheduler = max => {
    const queue = [] // 用来存储任务的队列
    let pending = false // 用来判断当前的异步任务有没有执行
    let runningCount = 0
    let index = 0
    function run () {  // 用来执行存储的任务
        if (index >= queue.length) return
        while(runningCount < max) {
            queue[index](() => {
                // 这里面的是任务完成之后的回调
                runningCount-- // 当前正在运行的额任务数量减一
                run() // 下一个任务开始执行
            })
            runningCount++ // 正在执行的数量+1
            index ++ // 队列中执行的任务的下标 + 1
        }
    }
    return function (fn) {
        return new Promise(resolve => {
            queue.push(async function (f) {
                await fn()
                f()
            })
            if (!pending) {
                pending = true
                Promise.resolve().then(run)
            }
        })
    }
}
  1. 顺序是对了,可是后续的then没有操作啊,这是为啥啊?原因很简单,我们没有任务的返回值给暴露出来,所以
const scheduler = max => {
    const queue = [] // 用来存储任务的队列
    let pending = false // 用来判断当前的异步任务有没有执行
    let runningCount = 0
    let index = 0
    function run () {  // 用来执行存储的任务
        if (index >= queue.length) return
        while(runningCount < max) {
            queue[index](() => {
                // 这里面的是任务完成之后的回调
                runningCount-- // 当前正在运行的额任务数量减一
                run() // 下一个任务开始执行
            })
            runningCount++ // 正在执行的数量+1
            index ++ // 队列中执行的任务的下标 + 1
        }
    }
    return function (fn) {
        return new Promise(resolve => {
            queue.push(async function (f) {
                const res = await fn()
                f()
                resolve(res) // 将执行结果 resolve 出去
            })
            if (!pending) {
                pending = true
                Promise.resolve().then(run)
            }
        })
    }
}

3. 题目总结

这道题其实考察更多的是js中同步异步的执行方式,闭包,队列的基础操作,递归,函数式编程,async await