coding icon indicating copy to clipboard operation
coding copied to clipboard

使用js实现依次执行异步任务

Open fantasticit opened this issue 7 years ago • 11 comments

最近接到了奇舞团的电话面试,总共进行了两轮电话面试,其中有几个问题印象比较深刻,其中一个便是:“如何实现依次执行异步任务”(最近脑子不太好使了,愣是想不起来两轮面试问了哪些问题,故就不记录此次奇舞团面试笔记)。

问题描述

现有 n 个异步任务,这 n 个异步任务是依次执行且下一个异步任务依赖上一个异步任务的结果作参数,问如何实现。

解法1:for 循环 + await

简单的 for 循环是依次进行循环的,利用这一特点加 async / await 很容易写出下面这样的代码:

(async () => {
  const sleep = delay => {
    return new Promise((resolve, reject) => {
      setTimeout(_ => resolve(), delay)
    })
  }
  
  const task = (i) => {
    return new Promise(async (resolve, reject) => {
      await sleep(500)
      console.log(`now is ${i}`)
      ++i
      resolve(i)
    })
  }
  
  let param = 0
  for (let i = 0; i < 4; i++) {
    param = await task(param)
  }  
})()

输出:

now is 0
now is 1
now is 2
now is 3

效果虽然做到了,但是看到 param 这个局部变量就很不爽,请看解法2。

解法2:Array.prototype.reduce

关于 Array.prototype.reduce 方法相信大部分小伙伴初见时都是用来数组求和。不了解的小伙伴可以点击链接了解下 reducereduce初始值积累值,以及 当前值 的概念。其中 “积累值”可以看作是 前一个值,通过 返回 积累值 又可以看作是 下一个值(可能说的比较绕,可以参照 Redux 的 中间件执行顺序 的源码,也是用的 reduce)。使用 reduce 来解决问题的代码为:

const sleep = delay => {
  return new Promise((resolve, reject) => {
    setTimeout(_ => resolve(), delay)
  })
}

const task = (i) => {
  return new Promise(async (resolve, reject) => {
    await sleep(500)
    console.log(`now is ${i}`)
    ++i
    resolve(i)
  })
}

[task, task, task, task].reduce(async (prev, task) => {
  const res = await prev
  return task(res)
}, 0)

输出:

now is 0
now is 1
now is 2
now is 3

可以这样理解 prevtask

  • prev:前一个 异步任务(promise)
  • task:当前的异步任务

当前的异步任务需要上一个异步任务的结果作参数,故很显然要 await prev

总结

要学好ES6,要不断尝试写出优雅的代码。

fantasticit avatar Jun 12 '18 13:06 fantasticit

第二种方法可以终止循环吗?比如有个取消的动作,或者取消后续的ajax行为

Nanchenk avatar Jun 14 '18 02:06 Nanchenk

最常见的应该是promise的then中返回新的promise吧

wangweida avatar Jun 14 '18 03:06 wangweida

@Nanchenk 这种如何?

const sleep = delay => {
  return new Promise((resolve, reject) => {
    setTimeout(_ => resolve(), delay)
  })
}

const task = i => {
  return new Promise(async (resolve, reject) => {
    await sleep(500)
    console.log(`now is ${i}`)
    ++i
    resolve(i)
  })
}
;[task, task, task, task].reduce(async (prev, task) => {
  console.log('调用: ', await prev)
  const res = await prev

  if (res == 2) {
    return res
  }

  return task(res)
}, 0)

fantasticit avatar Jun 14 '18 04:06 fantasticit

@wangweida 对,我也觉得你说的这种比较常见,但是这个问题就故意设计成这样(n 个相互依赖的请求,n 很大)

fantasticit avatar Jun 14 '18 04:06 fantasticit

可以使用递归加 promise 这样可以解决取消的问题

Link-X avatar Jun 19 '18 09:06 Link-X

@Link-X 方便写个例子吗。

fantasticit avatar Jun 19 '18 10:06 fantasticit

@Nanchenk 推荐你看下 redux-observable 这个项目,其中文档的demo就有 通过某个action取消ajax 的例子。但是该项目的定位是 Redux 中间件,可以借鉴其中的思路看一下。

fantasticit avatar Jul 10 '18 15:07 fantasticit

promise现在都支持链式调用吧,直接a.then(b).then(c)就行了吧

marsprince avatar Dec 05 '18 09:12 marsprince

@marsprince 举个例子: 100个promise,总不能直接跟 99 个 then 吧

fantasticit avatar Dec 05 '18 10:12 fantasticit

@marsprince 举个例子: 100个promise,总不能直接跟 99 个 then 吧

实际情况一般不可能有100个,而且用then比用循环语义清晰,循环根本不知道循环到哪去了,不过100个确实得循环,只能说题出的不太好,很容易误导人写出不好的代码

marsprince avatar Dec 06 '18 02:12 marsprince

@marsprince 所以我前面回复其他人也说:『这个问题就故意设计成这样(n 个相互依赖的请求,n 很大)』,(😄)

fantasticit avatar Dec 06 '18 03:12 fantasticit