跳到主要内容

笔试题

hardMan 函数

题目要求:

// 写一个 hardMan函数,满足控制台打印效果如下:
hardMan('小明')
//> Hi! I am 小明.
hardMan('小明').study('学习')
//> Hi! I am 小明.
//> I am studying 学习.
hardMan('小明').rest(3).study('学习')
//> Hi! I am 小明.
// 此时等待三秒钟
//> Wait 3 seconds.
//> I am studying 学习.
hardMan('小明').restFirst(3).study('学习')
// 此时等待三秒钟
//> Wait 3 seconds.
//> Hi! I am 小明.
//> I am studying 学习.

可以看到 hardMan 函数需要有异步等待、链式调用、以及任务调度。

分析:

  • 链式调用,可以通过每次返回 this 实例实现。
  • 异步等待,可以将每次调用包装成一个 Promise task 放入数组中,最后依次执行.
  • restFirst 方法,将任务放入数组的开头,可以保证先执行。

这里在 setTimeout 调用了 runTasks 方法,保证了所有方法都放入到了 tasks 数组中.

class HardMan {
constructor(name) {
this.name = name
this.tasks = []

this.tasks.push(
() =>
new Promise((resolve) => {
console.log(`Hi! I am ${this.name}`)
resolve()
})
)

setTimeout(() => this.runTasks())
}
async runTasks() {
for (const task of this.tasks) {
await task()
}
}
study(job) {
this.tasks.push(
() =>
new Promise((resolve) => {
console.log(`I am studying ${job}`)
resolve()
})
)
return this
}
rest(seconds) {
this.tasks.push(
() =>
new Promise((resolve) => {
setTimeout(() => {
console.log(`Wait ${seconds} seconds.`)
resolve()
}, seconds * 1000)
})
)
return this
}
restFirst(seconds) {
this.tasks.unshift(
() =>
new Promise((resolve) => {
setTimeout(() => {
console.log(`Wait ${seconds} seconds.`)
resolve()
}, seconds * 1000)
})
)
return this
}
}

const hardMan = (name) => new HardMan(name)
hardMan('小明').restFirst(3).study('学习')

并发请求数量控制

题目要求:

实现一个并发请求函数 concurrencyRequest(urls, maxNum),要求如下:

  • 要求最大并发数 maxNum
  • 每当有一个请求返回,就留下一个空位,可以增加新的请求
  • 所有请求完成后,结果按照 urls 里面的顺序依次打出(发送请求的函数可以直接使用 fetch 即可)
const concurrencyRequest = (urls, maxNum) =>
new Promise((resolve) => {
const len = urls.length
let index = 0 // 请求索引
let count = 0 // 当前请求完成的个数
const res = []
async function request() {
const url = urls[index]
const i = index // 保存索引,使得请求和响应保持相同顺序
index++
try {
const resp = await fetch(url)
res[i] = resp
} catch (error) {
res[i] = error
} finally {
count++
if (count === len) {
resolve(res)
}
if (index < len) {
request()
}
}
}

for (let index = 0; index < Math.min(maxNum, urls.length); index++) {
request()
}
})

这里有两个额外的变量 index 和 count,分别用于记录当前发出请求的索引和请求完成的个数。

测试用例:

const urls = []
for (let i = 1; i <= 20; i++) {
urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`)
}
concurrencyRequest(urls, 3).then((res) => {
console.log(res)
})

异步任务并发执行

题目要求如下:

实现 SuperTask 类.

const timeout = (timer) =>
new Promise((resolve) =>
setTimeout(() => {
resolve()
}, timer * 1000)
)

class SuperTask {}

const superTask = new SuperTask()

const addTask = (time, name) =>
superTask
.add(() => timeout(time))
.then(() => {
console.log(`任务${name}完成`)
})

addTask(10, '吃饭') // 10秒后打印
addTask(5, '睡觉') // 5秒后打印
addTask(3, '打豆豆') // 8秒后打印

可以看到这里需要实现的是要并发执行任务(有最大执行个数,题目里面为 2 个),并且执行完成后接着执行下一个任务。

分析:

  • add 方法返回一个 Promise,并且在它的 task 任务执行完成后会调用 resolve 方法,所以我们在保存任务时连同 resolve 方法一起保存起来。
  • maxConcurrentTasks 和 runningTasks 分别表示最大并发数和当前正在执行的任务数。
  • 在每个任务结束后调用 _runTask 方法,保证下一个任务可以执行。

实现如下:

class SuperTask {
constructor(maxConcurrentTasks = 2) {
this.tasks = [] // 任务队列
this.maxConcurrentTasks = maxConcurrentTasks // 最大并发数
this.runningTasks = 0 // 当前执行的任务数
}
add(task) {
return new Promise((resolve) => {
this.tasks.push({ task, resolve })
this._runTask()
})
}
async _runTask() {
if (this.runningTasks < this.maxConcurrentTasks && this.tasks.length) {
const { task, resolve } = this.tasks.shift()
this.runningTasks++
try {
await task()
resolve()
} finally {
this.runningTasks--
this._runTask()
}
}
}
}