使用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面试题如何让异步操作顺序执行

相关推荐
神探小白牙5 小时前
eCharts 多系列柱状图增加背景图
javascript·ecmascript·echarts
薛定猫AI11 小时前
【深度解析】Gemma Chat 本地 AI 编程 Agent:Electron + MLX + 开源模型的离线 Vibe Coding 实战
javascript·人工智能·electron
全栈前端老曹12 小时前
【前端地图】多地图平台适配方案——高德、百度、腾讯、Google Maps SDK 差异对比、封装统一地图接口
前端·javascript·百度·dubbo·wgs84·gcj-02·bd09
笑虾12 小时前
Win10 修改注册表 让鼠标悬停PNG上时 tip 始终显示分辨率
开发语言·javascript·ecmascript
雾岛听风69112 小时前
JavaScript基础语法速查手册
开发语言·前端·javascript
用户23678298016812 小时前
从零实现 GIF 制作工具:LZW 压缩与 Median Cut 色彩量化
前端·javascript
棉猴13 小时前
Python海龟绘图之绘制文本
javascript·python·html·write·turtle·海龟绘图·输出文本
Highcharts.js13 小时前
线形比赛积分增长或竞赛图|Highcharts企业图表代码示列
开发语言·前端·javascript·折线图·highcharts·竞赛图
让学习成为一种生活方式13 小时前
大肠杆菌合成扑热息痛--对乙酰氨基酚--文献精读227
开发语言·前端·javascript
多秋浮沉度华年14 小时前
electron 初始使用记录
javascript·arcgis·electron