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

精读《async/await 是把双刃剑》 #82

Closed
ascoders opened this issue May 2, 2018 · 35 comments
Closed

精读《async/await 是把双刃剑》 #82

ascoders opened this issue May 2, 2018 · 35 comments

Comments

@ascoders
Copy link
Owner

ascoders commented May 2, 2018

文章地址,前端的发展就是一个大逃亡,刚逃离了 callback 地狱,又要开始逃离 async/await 地狱。

@atian25
Copy link

atian25 commented May 2, 2018

@popomore 来,吐槽下。

@ascoders ascoders closed this as completed May 7, 2018
@ascoders ascoders mentioned this issue May 7, 2018
65 tasks
@Ariex
Copy link

Ariex commented May 7, 2018

代码写成这样,只能怪自己学艺不精吧?

a(() => {
  b();
});

c(() => {
  d();
});

等价的应该是

await Promise.all(a().then(b), c().then(d));

回调的问题在于,它必须是嵌套的。而async和await可以在一定程度上把异步代码写的像是同步的,从而达到更好的可读性。

@ascoders
Copy link
Owner Author

ascoders commented May 7, 2018

@Ariex 用 .then 确实会简化点

@Ariex
Copy link

Ariex commented May 7, 2018

@ascoders 并不是用then代码能简化,而是作为例子里面的代码实在是没有按照async/await的要求来实现,如果随便写一些乱七八糟的代码就能拿来当作槽点的话,那就真是一念一地狱了。
我觉得文中以及原文中提到的这些问题,根本不是async/await的问题,而是写代码的人需要对自己使用的东西有正确的基础的了解。

@ascoders
Copy link
Owner Author

ascoders commented May 7, 2018

@Ariex 说的有一定道理,但是你能写好,其他人呢。?

希望能减少一些矫枉过正的评论,让其他读者可以平静的看到滥用带来的问题,不要陷入无谓的争斗。

@fantix
Copy link

fantix commented May 7, 2018

写代码的人需要对自己使用的东西有正确的基础的了解。

其他读者可以平静的看到滥用带来的问题,不要陷入无谓的争斗。

各一朵小红花。

https://python-gino.readthedocs.io/en/latest/why.html

还没写完,也还没来得及翻译,当做基础用于将来分享,请批评。

@ae6623
Copy link

ae6623 commented May 7, 2018

@Ariex 对于一个刚开始接触async/await的前端小白来说,确实缺少一些成功的范例来引导《如何正确的使用async/await》,如果一个老手已经学会了轻车熟路,逃避async/await的陷阱,不妨来 一篇/几语 来指引一下。

@atian25
Copy link

atian25 commented May 7, 2018

@ascoders 我赞同 @Ariex 的观点,这跟不能跟 callback hell 并论的,后者是因为语言缺陷导致开发者容易陷入。但 async/await 的话,从文中举得例子来说,我觉得更多是开发者的问题,还有就是 Promise 的锅(逃。。。

@ascoders
Copy link
Owner Author

ascoders commented May 7, 2018

@atian25 文中的例子是有意而为的,因为 callback 和 async/await 都经常出现在业务代码中,而业务项目又经常在倒排,有时候项目室热起来,内心一急躁,就写出连珠炮的 await。。

@atian25
Copy link

atian25 commented May 7, 2018

还是举个稍微正常一点的例子吧,文中的 a b c 这样的太简陋了,如果能举一个带有含义的单词,并举出存在以下的场景,再来讨论会更有聚焦一点

a(() => {
  b();
});

c(() => {
  d();
});

@baxtergu
Copy link

baxtergu commented May 7, 2018

这篇文章的解读的前半部分使用到了英文原文的例子,后半部分采用了简化版的抽象例子。
我觉得英文原文里结合了生活实际的例子产生的歧义可能会小一点。
原文链接:https://medium.freecodecamp.org/avoiding-the-async-await-hell-c77a0fb71c4c

@ascoders
Copy link
Owner Author

ascoders commented May 7, 2018

用户详情页,根据用户 id 拿用户信息、历史文章列表、最近更新的文章列表;同时加载第三方问答插件,加载完毕后上报插件展现打点日志。

如果回调的方式写,是这样的:

getUser(123, user => {
  getArticles(user, articles => /* fresh view */)
  getRecentArticles(user, articles => /* fresh view */)
})

initQA(() => {
  reportQAPV()
})

换成 promise:

Promise.all([
  getUser().then(user => {
    getArticles(user).then(/* fresh view */)
    getRecentArticles(user).then(/* fresh view */)
  }),
  initQA().then(reportQAPV)
])

如果换成 async/await,最好拆两个函数:

async getUserInfo() {
  // 这里也要用到 .then,如果纯粹用 await 的话,这里代码会更多
  getArticles(user).then(/* fresh view */)
  getRecentArticles(user).then(/* fresh view */)
}
async initAndReportQA() {
  await initQA()
  reportQAPV()
}

async function main() {
  await getUserInfo()
  initAndReportQA()
}

如果写代码的时候比较急躁,可能会想在一个函数里快速完成任务:

const user = await getUser()
getArticles(user)
getArticles(user).then(/* fresh view */)

await initQA()
reportQAPV()

这样 initQA() 就滞后了。

@ascoders
Copy link
Owner Author

ascoders commented May 7, 2018

@xpal7512 如果用 callback 方式,就算写的急躁,顶多是不雅观,而不会出现性能问题。

@jin5354
Copy link

jin5354 commented May 7, 2018

不能因为 async/await 太易使用而怪罪它啊。成串的 await 使用本就意味着类似同步顺序一个个执行,如果需要考虑并发那就不能这么写,更多是因为人没有意识。

@atian25
Copy link

atian25 commented May 7, 2018

如果用 callback 方式,就算写的急躁,顶多是不雅观,而不会出现性能问题。

@ascoders 会不会出现性能问题我不知道,但写的急躁的话, callback 比 await 出现的 bug 的概率大,维护成本和 Review 成本也大。

你举得那个例子其实是偏袒 callback 了,

getUser(123, user => {
  getArticles(user, articles => /* fresh view */)
  getRecentArticles(user, articles => /* fresh view */)
})

实际情况下,这里 callback 还要处理异步协同,还有错误处理

PS:上面的示例代码,Promise 那块漏了 return,会坑死的

@ascoders
Copy link
Owner Author

ascoders commented May 7, 2018

@atian25 async/await 相比 callback,项目整体更容易维护了,但局部代码执行性能可能比 callback 糟糕,原文说的 hell 应该指局部的问题。

@atian25
Copy link

atian25 commented May 7, 2018

其实我 diss 的是生造的 地狱 这个宣传,会对新手造成误解的。

说到底就是 JS 这块的异步协同的锅,callback 是十八层地狱,Promise 和 await 是往上爬了 10 层,大幅解决了很多问题了,帮开发者省了很多事了,剩下的几层目前就是开发者本身的能力要求了。

但这个标题给人的印象是,刚爬出 callback hell,又掉入了隔壁另一个十八层地狱。

@arronf2e
Copy link

arronf2e commented May 7, 2018

@atian25 确实是这样,很多同事第一眼看到这个标题就是这种感觉

@ascoders
Copy link
Owner Author

ascoders commented May 7, 2018

我意识到 async/await 的问题没有那么严重,把文章标题改为 《async/await 是把双刃剑》。

@ascoders ascoders changed the title 精读《逃离 async/await 地狱》 精读《async/await 是把双刃剑》 May 7, 2018
@atian25
Copy link

atian25 commented May 7, 2018

我感觉应该是 「async/await 万里长征,还在路上」,「async/await 需要注意的点」,哈哈

说到底就是开发者还不熟悉 await,等了解后就没这些问题了

@atian25
Copy link

atian25 commented May 7, 2018

说到底就是开发者还不熟悉 await,等了解后就没这些问题了,我们日常开发就没看到有这类问题的讨论。

更多的是从 co 时代迁移过来后,一些不习惯的问题,如 yield {} 不支持, await Promise.all([]) 啰嗦。

还有就是 Promise 本身的那一堆坑,你看朴老师内网的分享,内存泄露的,一大半是 SSR,另一大半是 Promise 。

@ascoders
Copy link
Owner Author

ascoders commented May 7, 2018

@atian25 好嘞,内网 ssr 内存泄漏的经典案例也略有耳闻,对于熟悉 node 的团队,对 js 特性都是一步步跟着过来的,有坑也先踩在 generator 上了。。

@zWingz
Copy link

zWingz commented May 7, 2018

大概有些人为了逼格而用上async/await,却没有去思考它实际怎么运作。

@atian25
Copy link

atian25 commented May 7, 2018

就像那些一直翘首以盼 ESM(import) 啥时进入 Node 的....

@CoderIvan
Copy link

CoderIvan commented May 8, 2018

callback -> fibers.js -> promise -> generator + co.js -> async/await

这个演进过程,一直也就只是语法糖的改变而已

说到底也就是开发者,没分清楚什么时候要并发执行,什么时候要串行执行

这个问题和场景,最容易出现在哪?

就是出现在新手,在连异步并发都不知道是什么的情况下,跳过callback直接用async/await,他连自己想要什么样的执行顺序都不知道。

我的结论是,写例子的人,连自己想要什么样的执行顺序都不知道,和用callback和还是用async/await无关

@CoderIvan
Copy link

async/await不是双刃剑,异步才是双刃剑

@crimx
Copy link

crimx commented May 8, 2018

@CoderIvan 赞同。这不是 async/await 的问题,而是文章作者自己的问题,抱怨锤子不能当螺丝刀用。

@ascoders
Copy link
Owner Author

ascoders commented May 8, 2018

这个问题已经讨论的很充分了。不是 async/await 不好,而是不理解就使用它会造成问题,这个看似简单的 api,其实对理解要求比较高,不要小看它。

@hoythan
Copy link

hoythan commented Nov 27, 2018

如果把写代码急躁也算因素,那就算是 var 也可以写一篇滥用不好的文章出来。

@zerofront
Copy link

如果代码写得急躁算是一个因素的话,那么使用回调还大概率会导致获取不到异步数据会得到undefined。

因为我们的思维基本都是同步思维。是JS以前只能写回调的形式,你写了几年代码,终于熟悉了回调的形式。
怎么就变成了回调更容易理解呢??当一个新手同时接触到异步回调和async/await,你们觉得哪个更容易犯错,那个更容易便于新手理解?

@ascoders
Copy link
Owner Author

ascoders commented Dec 5, 2018

@zerofront 不是指回调代码比 async 更容易看懂。

指的是回调更容易写出正确顺序的异步代码,而 async 容易导致原本能并行的移步写成了串行。

@abisuq
Copy link

abisuq commented Dec 5, 2018

如果换成 async/await,最好拆两个函数:

async getUserInfo() {
  // 这里也要用到 .then,如果纯粹用 await 的话,这里代码会更多
  getArticles(user).then(/* fresh view */)
  getRecentArticles(user).then(/* fresh view */)
}
async initAndReportQA() {
  await initQA()
  reportQAPV()
}

async function main() {
  await getUserInfo()
  initAndReportQA()
}

这个例子中 getUserInfo 为什么是 async ?

@ascoders

@ascoders
Copy link
Owner Author

ascoders commented Dec 5, 2018

@abisuq 例子写的有问题,应该去掉 async, 把 promise return 出来。

@ckvv
Copy link

ckvv commented Mar 5, 2020

滥用回调才是地狱

@netwjx
Copy link

netwjx commented Jul 1, 2022

用户详情页,根据用户 id 拿用户信息、历史文章列表、最近更新的文章列表;同时加载第三方问答插件,加载完毕后上报插件展现打点日志。

如果回调的方式写,是这样的:

getUser(123, user => {
  getArticles(user, articles => /* fresh view */)
  getRecentArticles(user, articles => /* fresh view */)
})

initQA(() => {
  reportQAPV()
})

换成 promise:

Promise.all([
  getUser().then(user => {
    getArticles(user).then(/* fresh view */)
    getRecentArticles(user).then(/* fresh view */)
  }),
  initQA().then(reportQAPV)
])

如果换成 async/await,最好拆两个函数:

async getUserInfo() {
  // 这里也要用到 .then,如果纯粹用 await 的话,这里代码会更多
  getArticles(user).then(/* fresh view */)
  getRecentArticles(user).then(/* fresh view */)
}
async initAndReportQA() {
  await initQA()
  reportQAPV()
}

async function main() {
  await getUserInfo()
  initAndReportQA()
}

如果写代码的时候比较急躁,可能会想在一个函数里快速完成任务:

const user = await getUser()
getArticles(user)
getArticles(user).then(/* fresh view */)

await initQA()
reportQAPV()

这样 initQA() 就滞后了。

文章例子对意图表达不清晰,此处的改造也不是太合适

await 的使用准则:仅在需要 promise 的内含资源前一刻时,才进行 await

let pmsUser = getUser(123)
let pmsInitQA = initQA().then(reportQAPV) // 典型场景是Articles可见后才上报PV,reportQAPV() 需放到getArticles()之后

let r = getArticles(await pmsUser)
/* fresh view */
let r2 = getRecentArticles(await pmsUser) // 可以重复await,promise只是一个某个时刻可用的资源容器
/* fresh view */

await pmsInitQA

async await 是为了让 异步编程同步执行,这样可以获得一些好处

  • 顺序可控
  • 状态可预知
  • 易于调试
  • 更少的缩进

只要遵循:仅在需要 promise 的内含资源前一刻时,才进行await

就可以做到异步操作尽可能并行,而逻辑代码可以。类似浏览器,会充分并行下载内嵌资源,但默认按照script标签顺序执行

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

No branches or pull requests