使用Promise实现串行执行异步任务,含出错重试功能

实现要求

传入一个包含多个异步任务的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面试题如何让异步操作顺序执行

相关推荐
灵感__idea35 分钟前
Hello 算法:众里寻她千“百度”
前端·javascript·算法
袋鼠云数栈UED团队2 小时前
基于 Lexical 实现变量输入编辑器
前端·javascript·架构
亦妤2 小时前
JS执行机制、作用域及作用域链
javascript
SuperEugene4 小时前
表单最佳实践:从 v-model 到自定义表单组件(含校验)
前端·javascript·vue.js
不会敲代码14 小时前
React性能优化:深入理解useMemo和useCallback
前端·javascript·react.js
YukiMori236 小时前
一个有趣的原型继承实验:为什么“男人也会生孩子”?从对象赋值到构造函数继承的完整推演
前端·javascript
摸鱼的春哥7 小时前
惊!黑客靠AI把墨西哥政府打穿了,海量数据被黑
前端·javascript·后端
小兵张健7 小时前
Playwright MCP 截图标注方案调研(推荐方案1)
前端·javascript·github
我叫黑大帅9 小时前
Vue3和Uniapp的爱恨情仇:小白也能懂的跨端秘籍
前端·javascript·vue.js
None3219 小时前
【NestJs】使用Winston+ELK分布式链路追踪日志采集
javascript·node.js