Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
fantasticit opened this issue Jun 12, 2018 · 11 comments
Open

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

fantasticit opened this issue Jun 12, 2018 · 11 comments

Comments

@fantasticit
Copy link
Owner

fantasticit commented Jun 12, 2018

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

问题描述

现有 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 方法相信大部分小伙伴初见时都是用来数组求和。不了解的小伙伴可以点击链接了解下 reduce
reduce初始值积累值,以及 当前值 的概念。其中 “积累值”可以看作是 前一个值,通过 返回 积累值 又可以看作是 下一个值(可能说的比较绕,可以参照 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,要不断尝试写出优雅的代码。

@Nanchenk
Copy link

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

@wangweida
Copy link

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

@fantasticit
Copy link
Owner Author

fantasticit commented Jun 14, 2018

@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
Copy link
Owner Author

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

@Link-X
Copy link

Link-X commented Jun 19, 2018

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

@fantasticit
Copy link
Owner Author

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

@fantasticit
Copy link
Owner Author

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

@marsprince
Copy link

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

@fantasticit
Copy link
Owner Author

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

@marsprince
Copy link

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

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

@fantasticit
Copy link
Owner Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants