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

相关推荐
前端老宋Running2 小时前
Vue 3 的“降维打击”:Composition API 是如何让 Mixin 成为历史文物的?
前端·javascript·vue.js
Pluto_CRown2 小时前
H5 开发的各类小知识点
前端·javascript
Pluto_CRown2 小时前
上下文存储【下】
前端·javascript
AAA阿giao2 小时前
JavaScript 中基于原型和原型链的继承方式详解
前端·javascript·面试
奋斗吧程序媛2 小时前
Vue Router的路由模式
前端·javascript·vue.js
by__csdn2 小时前
Vue.js 生命周期全解析:从创建到销毁的完整指南
前端·javascript·vue.js·前端框架·ecmascript·css3·html5
m0_471199632 小时前
【JavaScript】前端如何处理服务端部分接口加解密
开发语言·前端·javascript
离别又见离别3 小时前
vue使用js渲染组件案例(公用打印组件动态渲染)及静默打印实现
前端·javascript·vue
徐同保3 小时前
n8n项目编译时取消类型检测,提交代码时取消校验
开发语言·前端·javascript