实现要求
传入一个包含多个异步任务的tasks数组与重试次数retries, 按序执行tasks内的异步任务,当所有的任务执行完成之后,返回一个包含全部执行结果的结果数组
对于执行失败的task,允许重试retries次,如果均失败了,就将失败原因存入结果数组之中,接着继续执行下一个task
实现思路
如何使用promise实现串行执行异步任务的效果?重点需要思考的点应该是如何在当前的task完成之后再执行下一个task?
没错,就是使用promise的.then()进行叠加 ,通过为当前task的promise使用.then添加task完成的回调,在.then之中执行下一个task,这样就实现了完成当前task再执行下一个task的效果。
那么又如何实现累计添加.then?可以使用数组的reduce方法,实现不断的累加的效果。使用tasks.reduce((p,task) => {...},Promise.resolve()),其中p是不断累计添加.then的promise,task是当前异步任务
异步执行实现(不带重试功能)
先来看看如何使用promise+reduce实现串行调用异步任务
js
function serialExcute(tasks) {
const values = [] // 结果数组
// 返回的p是已经处理完所有task串行操作的promise
let p = tasks.reduce((p,task) => {
return p.then(() => {
return new Promise(resolve => {
task().then((value) => {
values.push(value) // 将结果存储
resolve() // 执行下一个任务
}).catch(err) {
// 出错时,记录错误,执行下一task
value.push(err)
resolve()
}
})
})
},Promise.resolve())
// 当tasks中所有task执行完成之后,返回values数组
return p.then(() => {
return Promise.reslove(values)
})
}
重试功能实现
额外添加一个方法excuteTaskWithRetry(task,retryTime),它接收一个异步任务task以及重试次数作为参数,实现单个异步任务执行并带有失败重试的功能,返回一个返回一个已完成/失败的promise,作为task执行的结果
重试如何实现? 使用递归的思想,当task执行失败时,会被.catch捕获,在.catch之中判断当前重试次数是否为0,如果为0,就直接reject(err),反之则递归到下一层执行,同时重试次数减1 excuteTaskWithRetry(task,retryTime-1)
js
function excuteTaskWithRetry(task, retryTime) {
return new Promise((resolve, reject) => {
task().then(res => {
// 任务完成,返回结果
resolve(res)
}).catch(err => {
// 如果次数足够,就重试
if (retryTime > 0) {
console.log(`任务执行出错,剩余重试次数${retryTime}`)
excuteTaskWithRetry(task, retryTime - 1).then((res) => {
// 如果重试成功了
resolve(res)
}).catch(err => {
// 如果重试失败
reject(err)
})
} else {
// 重试次数用完
reject(new Error(`执行出错,重试次数用尽,error:${err}`))
}
})
})
}
整合到先前不带重试功能的串行执行方法之中
js
function serialExcute(tasks, retries) {
const values = []
let p = tasks.reduce((p, task) => {
return p.then(() => {
// 更换成可重试的方法
return excuteTaskWithRetry(task, retries).then(res => {
values.push(res)
}).catch(err => {
values.push(err)
})
})
}, Promise.resolve())
// 按序返回全部数据
return p.then(() => {
return Promise.resolve(value)
})
}
完整代码 + 调试数据
下面附上完整的代码及调试的数据
js
// 执行单个任务,包含重试
function excuteTaskWithRetry(task, retryTime) {
return new Promise((resolve, reject) => {
task().then(res => {
// 任务完成,返回结果
resolve(res)
}).catch(err => {
// 如果次数足够,就重试
if (retryTime > 0) {
console.log(`任务执行出错,剩余重试次数${retryTime}`)
excuteTaskWithRetry(task, retryTime - 1).then((res) => {
// 如果重试成功了
resolve(res)
}).catch(err => {
// 如果重试失败
reject(err)
})
} else {
// 重试次数用完
reject(err)
}
})
})
}
// 串行执行异步任务
function runTask(tasks, retries) {
const value = []
let p = tasks.reduce((p, task) => {
return p.then(() => {
// 更换成可重试的方法
return excuteTaskWithRetry(task, retries).then(res => {
value.push(res)
}).catch(err => {
value.push(err)
})
})
}, Promise.resolve())
// 按序返回全部数据
return p.then(() => {
return Promise.resolve(value)
})
}
// 生成异步任务
function createMockTask(taskName, successProbability = 0.7) {
return () => new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < successProbability) {
const result = `${taskName} 执行成功`;
console.log(result);
resolve(result);
} else {
reject(new Error(`${taskName} 执行失败`));
}
}, 500); // 每个任务模拟 500ms 执行时间
});
}
const tasks = [createMockTask('task1'), createMockTask('task2', 0.4), createMockTask('task3')]
runTask(tasks,3).then((value) => {
console.log(value)
})
完结撒花🌸~有疑问或者有什么问题的欢迎在评论区提出!
参考文档: 45道promise面试题、如何让异步操作顺序执行