-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
那些年我们处理过的异步问题 #6
Comments
[译] 6个Async/Await优于Promise的方面 |
异步是前端很喜欢的讨论的一个问题,对我这样的前端新手而言,在为数不多的面试过程中,异步出现的概率是100%,按照某乎的习惯,先问是不是,再问为什么,最后问怎么做,我就先抛砖引玉一下了。 异步在前端是必须的么?是的。原因看下一条。 为什么要有异步?在我的理解中,异步在 JS 中盛行的很大原因是 JS 的单线程性,而 JS 的单线程性又与浏览器环境有着密切的关系,君不见 Node 已经引入了 fork,还有 cluster 这样的多线程解决方案(当然现在浏览器端也有了 service worker,不过这和传统的多线程方案不太一样,暂且不表)。 那单线程和异步又有什么关系呢?由于部分操作的耗时性或者完成时的不确定性,我们不能阻塞地去等待这些操作的完成,所以就把这些操作单独拿出来,让他们在同步操作的最后执行,也就是所谓的异步操作。这里就引入了一个问题:如果我的某些操作依赖这些异步操作的结果怎么办呢?换句话来说,如果我的下一步操作需要紧跟在异步操作之后,怎么办呢? 异步的流程怎么控制呢?JS 给出的答案是:callback。其实这种解决方案就是 CPS(Continuation-passing style),并且,CPS 也一直是 JS 的异步解决方案的核心,所谓的 callback 就是手写 CPS,用个 yield、async 就是非 CPS 自动展开成 CPS(一种实现方式),王垠聚聚之前就用 scheme 写过一个自动展开的程序,可惜我没看懂(捂脸)。 那 CPS 是怎么解决这个问题的呢?其实非常简单,我们给每一个异步函数都注册一个函数作为参数,指定,当异步函数执行到某一个步骤时,执行传入的那个参数函数,并给参数函数传递需要的参数。 所以我们回头来看所谓的 callback,它在 JS 中是异步流程控制的解决方案,但是,CPS 的思想,本质上就是对流程的控制,不仅仅是异步,举个🌰: function foo(next) {
console.log("CPS foo")
next()
}
function bar() {
console.log("CPS bar")
}
///////////////////////////
function baz() {
console.log("foo")
console.log("bar")
}
foo(bar) /* CPS foo
CPS bar */
baz() /*foo
bar*/ 稍有常识的都能看出(大雾), TIPS:这里插一点,其实 CPS 还有别的一些好处,比如在自己写 PL 的时候不用 自己实现个函数栈了,函数的调用全部 CPS 化,这样就可以利用 target 的函数栈了,顺便实现个 tail recursive,岂不美哉。 既然有了 callback,我们还要 promise 干嘛呢?首先明确一点,promise 所取代的是 callback ,而不是 CPS,CPS 的思想赛高!那 callback 有什么不好呢?他不就是 CPS 思想的直接体现么?唔,一方面是手写 CPS 太麻烦了,另一方面涉及到一个控制翻转(依赖注入)的问题,回调函数的执行权限并不在回调函数的编写者的手上,而在异步函数编写者的手上,如果异步函数出了偏差,比如多次调用回调,或者忘了调用回调等等,回调函数这边就一脸懵逼了,这时候 promise 就应运而生了。 最开始我对于 promise 这个名字不太理解,不就是把 callback 嵌套拍平拉成链了么,为什么要叫promise?后来读了一些 promise 的实现,发现 promise 的内部对于异步和回调的调用情况和流程有着非常严格的控制,所以这就是异步操作对未来的回调的一个“允诺”: PS:这里具体的Promise 实现可以参考网上各种源码,我就不班门弄斧了。 等等,async/await 又是什么?Promise 很棒很 nice,可是这个在 ES2016中提出的愣头青是什么?其实说 async/await 是愣头青还真是冤枉他了,在宇宙最棒语言——C#(不是黑)中,这对亲兄弟早就作为一个处理异步的利器出现了。 async/await 的好处和用法在文章中已经提到了,这里最不再赘述了,当然 promise 和 async/await 并不是冲突的,promise 是 monad,负责数据的 wrap,而另外一对则是负责流程控制,岂不美哉? JS异步流程控制的未来?按照现在这样的趋势,JS 和多线程迟早要碰撞出火花,那么单线程引出的异步处理方式面对多线程还适用么?比如 async/await,如何映射到多线程上呢?未来 JS 会引入 CSP 或者 Actor 么?很期待哇,拭目以待。 |
async/await 是对 yield 的简单封装,promise 结合 async/await 是比较好的玩法。 未来 CSP 或 Actor 用在 js 上 callback 本质是不会变的,现在也有 js-csp 的实现,还是利用 generator 或 async await 的语法糖。 |
@arcthur 主要是对于多线程下的 async/await 有点疑惑。比如我await 了一个 get,那我这个http get 的方法在单线程下是被扔到 task queue了。但是如果是多线程呢?这时候为什么不选择再开一个线程去执行 get 呢?那 await 到时候的语义除了流程控制是不是还有线程的切换呢? |
@monkingxue 多线程下的 async/await 也是在一个线程下实现异步,可以看 .net,在一个线程下不存在线程间切换。CSP 与 actor 都是并发模型,发挥的是多线程的优势,比如 actor 模型所有消息都是异步交付的。JS 是跑在单线程下,并没有多线程并发能力。 |
@arcthur get,蟹蟹啦~ |
推荐一篇关于异步演化史的文章 https://zhuanlan.zhihu.com/p/20322843 |
文章不错,清晰易懂 |
@javie007 啊这个问题想请教下。就是在 r3层级中,比如 JS 语言中,我们都说 JS 是单线程的,但是这里的线程好像就是对应着内核的进程,再比如 Go 中的goroutine 说是用户态线程,但是如果启动的 goroutine 数小于硬件的逻辑核心数,那么其实这些 goroutine 也是跑在不同的逻辑核心上,按理来说对应的也应该是内核级别的进程。这里进程和线程该怎么区分呢? |
要分清楚process, thread, coroutine。 |
嗯呢,蟹蟹啦~ |
async/await 类似于 Promise 的语法糖因为 async 返回的就是 promise,await 后面跟的一般也是 promise。这也表明了 Promise 的一些限制在 async/await 同样存在。如无法 cancel,没有 timeout。而且也说明如果想用好 async/await,你一定要先熟悉 Promise,后面我会有例子说明。 即使如此,async/await 也是很让人兴奋的,除了文中列举的 6 点之外,我最喜欢的就是它可以把异步当成同步来开发,而人脑就喜欢同步的方式来思想。 使用 promise 发请求,是这样的 componentDidMount() {
fetch('https://example.com')
.then(response => response.json())
.then(data => this.setState({ data }))
} 上面代码并不难,但复杂了以后,Promise 的 then 写法很不优雅,而且 promise 的异常处理非常诡异,很多人会漏掉。 async componentDidMount() {
const response = await fetch('https://example.com');
const data = await response.json();
await this.setState({ data });
} async/await 虽好,但不是终点前端处理异步进化几个关键技术为 Callback -> Promise -> Generator -> Async/Await callback 是最简单的,也是最容易被人滥用的。导致了臭名昭著的回掉地狱。 async/await 表面上看是最优雅的异步解决方案,但其实是有很多限制的。这一切都是因为 ES6 的 Promise 设计其实是有很多问题的,功能上有很多的缺失。
另外,也要尽量避免陷阱: 在刚开始写代码的时候,我相信很多人会这样写,但注意这里是串行执行的,在前后请求没有依赖的情况下,是没有必要的。 async function loadData() {
var res1 = await fetch(url1);
var res2 = await fetch(url2);
var res3 = await fetch(url3);
return "whew all done";
} 正确的做法是: await Promise.all([fetch(url1), fetch(url2), fetch(url3)]) 但能这样写就表示你要熟悉使用 Promise。 总之,async/await 简化了异步处理流程,但也只是节省了简单异步处理的方法,如果要处理复杂的异步流程,还是很有必要尝试 RxJS, |
今天的 async await 让我想起当年老赵的 windjs,windjs 的灵感来自于 F#,看微软很早就在异步编程上有完善的方案。5年前的作品与今天的规范相比依然具有前瞻性。 老赵 windjs 的网站已经没有了,但文档还在,可以怀念一下。上面同学也提到了流程控制的话题,windjs 的设计放到今天算很完备的方案了。比如,windjs 在处理 cancel 时的想法 windjs 为了避免浏览器不支持,提到了AOT和JIT两种编译方式(是不是想到 ng2),当时很多人诟病的是 JIT 下用 eval 来写代码不支持优化,不方便调试或安全性,但 aot 编译后是没有 eval 的。放到今天,大家都在用 babel,是否可以用更好的实现(不懂),其实大家已经不会反感作实时编译。 你看 tc39 到今天还在讨论关于 cancel 的设计用法。web 标准化总是慢于工业界发展,都等着他们出标准,浏览器支持不知道要等多少年了,但好歹也算是在讨论了。 |
强调一下,文中结尾观点有误,async/await 不是 promise 的替代方案,因为 本来 // foo 函数不是异步,当前函数也不是异步
const result = yield foo() 但换成 所以使用 |
现在node 8 LTS已经支持async,await,英文原文已更新:[UPDATE]: Node 8 LTS is out now with full Async/Await support. |
chrome 和 nodejs 都已经原生支持 async/await |
本期精读文章: 6 Reasons Why JavaScript’s Async/Await Blows Promises Away
从 Callback 到 Promise,从 Generator 到 Async/Await,前端异步问题的处理正在踏入又一个新的台阶。Async/Await 是不是救世主?Promise 又是不是即将式微?看完本文相信你心里会有自己的答案。
The text was updated successfully, but these errors were encountered: