面试官要求我们手动实现 Promise.all

2,167 阅读2分钟

情景:

最近面试,有两次被问到手动实现 Promise.all,不幸的是我都没把这题做好。因为我没有去准备这个,我不知道手动实现已有的 API 有什么意义。

但是为了防止以后还会遇到此类题,还是记录下吧,同时也是为了给同样遇到该面试题坑的同学一点微不足道的帮助。

难点:

其实这道题并没有什么难度,难点在于你如果之前没有思考过类似手动实现异步任务的并发处理时,很难捅破如何确定这几个任务都已经执行完了,并且已经拿到了全部该有的数据这层窗户纸,容易转不过来弯儿。

思路:

因为 Promise 是个异步任务,在数据没返回之前它相当于是动态的,我们要确定任务是否都已经成功执行完毕并且拿到了数据,就需要找一个静止的、我们能够掌控的东西,那就是 .then 后面的回调函数。

我们可以设置一个数组,用它来存放每个 Promise 返回的数据,当该数组的长度等于 Promise 任务的个数时,说明拿到了所有的数据,此时就可以把该数组返回出去了。

然后又因为原生 Promise.all 返回的是一个 Promise,所以我们也需要返回一个 Promise,然后把结果数据放入 resolve 中返回出去。

代码:

Promise.all = promises => {
	// 返回的是一个 Promise
	return new Promise((resolve, reject) => {
		let dataArr = new Array(promises.length)
		let count = 0

		for (let i = 0; i < promises.length; i++) {
			// 在 .then 中收集数据,并添加 .catch,在某一个 Promise 遇到错误随时 reject。
			// 这样,在最外面调用 Promise.all().catch() 时也可以 catch 错误信息
			promises[i].then(res => { addData(res, i) })
				.catch(err => { reject({ message: err, index: i }) })
		}

		function addData(data, index) {
			dataArr[index] = data
			count++
			// 如果数据收集完了,就把收集的数据 resolve 出去
			if (count === promises.length) resolve(dataArr)
		}
	})
}

测试用例1:

let p1 = new Promise(resolve => { resolve('p1') })
let p2 = new Promise(resolve => { setTimeout(() => { resolve('p2') }, 3000) })
let p3 = new Promise(resolve => { resolve('p3') })
let p4 = new Promise(resolve => { setTimeout(() => { resolve('p4') }, 1500) })
let p5 = new Promise(resolve => { resolve('p5') })

Promise.all([p1, p2, p3, p4, p5]).then(res => {
	console.log(res)
}).catch(err => {
	console.error(err)
})

测试用例2:

let p1 = new Promise(resolve => { resolve('p1') })
let p2 = new Promise(resolve => { setTimeout(() => { resolve('p2') }, 3000) })
let p3 = new Promise((resolve, reject) => { reject('p3') })
let p4 = new Promise(resolve => { setTimeout(() => { resolve('p4') }, 1500) })
let p5 = new Promise(resolve => { resolve('p5') })

Promise.all([p1, p2, p3, p4, p5]).then(res => {
	console.log(res)
}).catch(err => {
	console.error(err)
})

Promise.all([p1, p2, p3, p4, p5]).then(res => {
	console.log(res)
}).catch(err => {
	console.error(err)
})