携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情
最近在学习nodejs
中间件的过程中涉及到js中的串行执行相关的知识,同时在面试过程中本人也有被问到手写一个js相关串行和并行的问题。因此,本文来总结下js中的串行和并行。
promise的并行
定义一个函数数组,每个函数执行时返回一个promise
:
const fnarr = [0, 1, 2, 3, 4, 5].map(t => {
return () => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`promise${t}`)
resolve(t)
}, t * 1000)
})
}
})
既然是并行,也就是定义的函数同时去执行,那么可以利用Promise.all
来实现:
Promise.all(fnarr.map(fn => fn())).then(resArr => {
console.log(resArr)
})
打印结果:
// 0s后
promise0
// 1s后
promise1
// 2s后
promise2
// 3s后
promise3
// 4s后
promise4
// 5s后
promise5
// 当所有promise执行完后返回所有的结果
[0, 1, 2, 3, 4, 5]
可以看到,尽管所有的Promise
是并行执行的,但是返回的结果是按照顺序排列的,这一点在某些场景下很有用。
promise的串行
既然要求串行,首先想到的是async/await
实现:
async function run() {
for (let i = 0; i < fnarr.length; i++) {
const result = await fnarr[i]()
console.log(i, result)
}
}
run()
打印结果:
// 0s后
0 0
// 1s后
1 1
// 2s后
2 2
// 3s后
3 3
// 4s后
4 4
// 5s后
5 5
除了是用async/await
外,还可以使用递归来实现,nodejs
的中间件就是利用递归来做的。
function next(index, arr) {
if (index >= arr.length) {
return
}
arr[index]().then(res => {
console.log(index, res)
index++
next(index, arr)
})
}
next(0, fnarr)
promise的串行和并行
利用上面的知识,我们现在有一个需求:
- 可以并发发送请求;
- 可以控制并发的数量:就是先发几个请求等这几个请求的结果回来后,在发另外的请求;
思路:并发可以使用Promise.all
,控制并发的数量,相当于是串行,可以使用async+await来做。
- 分组:把所有的请求按照并发数量进行分组
const group = (list = [], max = 0) => {
if (!list.length) {
return list
}
let results = []
for (let i = 0, len = list.length; i < len; i += max) {
results.push(list.slice(i, i + max))
}
return results
}
- 分组之后,那么就一组一组的执行,即串行,使用async+await
async function sendRequest(requests = [], max = 0, callback = () => {}) {
if (!requests.length) {
return requests
}
const groupRequest = group(requests, max)
const results = []
// 串行
for (let groupItems of groupRequest) {
const result = await requestHandler(groupItems, callback)
results.push(result)
}
console.log('done', results)
return results
}
// 并行
async function requestHandler(groupItems, callback = () => {}) {
if (!groupItems.length) {
callback()
return groupItems
}
const promiseArr = groupItems.map(fn => fn())
const data = await Promise.allSettled(promiseArr).then(resArr => {
resArr.map(callback)
return resArr
})
return data
}
- 测试
const p1 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p1'))
const p2 = () => Promise.resolve('p2')
const p3 = () => new Promise((resolve, reject) => setTimeout(resolve, 2000, 'p3'))
const p4 = () => Promise.resolve('p4')
const p5 = () => new Promise((resolve, reject) => setTimeout(resolve, 2000, 'p5'))
const p6 = () => Promise.resolve('p6')
const p7 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p7'))
const p8 = () => Promise.resolve('p8')
const p9 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p9'))
const arr = [p1, p2, p3, p4, p5, p6, p7, p8, p9]
sendRequest(arr, 3, ({ reason, value }) => {
console.log(value || reason)
})
打印结果:
// 3个为一组,每组都必须要请求结束
p1
p2
p3
// 3个为一组,每组都必须要请求结束
p4
p5
p6
// 3个为一组,每组都必须要请求结束
p7
p8
p9
// 最后的结果result
done [
[
{ status: 'fulfilled', value: 'p1' },
{ status: 'fulfilled', value: 'p2' },
{ status: 'fulfilled', value: 'p3' }
],
[
{ status: 'fulfilled', value: 'p4' },
{ status: 'fulfilled', value: 'p5' },
{ status: 'fulfilled', value: 'p6' }
],
[
{ status: 'fulfilled', value: 'p7' },
{ status: 'fulfilled', value: 'p8' },
{ status: 'rejected', reason: 'p9' }
]
]
Promise.allSettled
是一个新的API,跟Promise.all
差不多的用法,也是接受的数组,不过不同的是Promise.allSettled
会等所有任务结束之后才会返回结果,而Promise.all
只要有一个reject
就会返回结果。
同时Promise.allSettled
返回的结果也有些不同:
- 如果对应的 promise 已经
fulfilled
,返回 { status: 'fulfilled', value: value } - 如果相应的 promise 已经被
rejected
,返回 {status: 'rejected', reason: reason }
setTimeout第三个参数的作用是给回调函数传入参数,如下
for (var i = 0; i<4; i++) {
setTimeout((i) => {
console.log(i)
}, 1000, i)
}