Comments (16)
异步是前端很喜欢的讨论的一个问题,对我这样的前端新手而言,在为数不多的面试过程中,异步出现的概率是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*/
稍有常识的都能看出(大雾),foo(bar)
和baz()
实现的是相同的控制效果,既所谓的先打印 foo,再打印 bar。这里的 bar 函数就是 foo 函数的 callback,我们也利用 CPS 实现了同步的流程的控制,但是话说回来,同步我们为什么要这么麻烦呢?但是在异步中,这种顺序的情况不再实用,所以我们又找回了 CPS 这个老祖宗,让它继续发光发热,来进行异步的流程控制。
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 么?很期待哇,拭目以待。
from weekly.
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/await 之后就非常直观了。庆幸的是,react 也支持这样做。
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 是最简单的,也是最容易被人滥用的。导致了臭名昭著的回掉地狱。
promise 是以低成本的方式来优雅解决异步的问题。generator 属于Iterator的一种,设计之初是用来生成一种特殊的迭代器,功能比 promise、async 强大多了,但需要每次调用的时候用 next
下去,还要处理临界条件,除非你使用 co 这样的库,非常麻烦。
async/await 表面上看是最优雅的异步解决方案,但其实是有很多限制的。这一切都是因为 ES6 的 Promise 设计其实是有很多问题的,功能上有很多的缺失。
- 缺少复杂的流程控制:always,progress,finally,pause,resume
- 一旦开始,不能中断,没有 abort、terminate、onTimeout 或 cancel 方法
另外,也要尽量避免陷阱:
在刚开始写代码的时候,我相信很多人会这样写,但注意这里是串行执行的,在前后请求没有依赖的情况下,是没有必要的。
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,
js-csp 之流。
from weekly.
强调一下,文中结尾观点有误,async/await 不是 promise 的替代方案,因为 async/await
本质是 generator + promise
,相当于流程控制 + 异步断言,这两个武器组合起来解决了异步回调的问题,但这两样武器还没有火力全开,只是配合起来,这个之前也有提到,不再展开。
本来 generator
可以不用在异步场景,同步的回调也能用它搞定,比如:
// foo 函数不是异步,当前函数也不是异步
const result = yield foo()
但换成 async/await
方案的话,所有 function
都会加上 async
标记才可以被 await
使用,统统转化成了 promise
,即便同步的回调也变成了异步,不过这个异步插入到的是 microtask
队列,优先级比 setTimeout
之类的高。
所以使用 async/await
方案有利也有弊,缺点是所有方法都强制转成了异步,即使你希望代码是同步的。其实我个人挺赞同这一点的,把所有同步转为异步,才会避免回调是否立即执行的猜测,async/await
拥抱了 promise
,我希望把所有 action
都写成 async/await
,让 promise
**得到统一。
from weekly.
@monkingxue 多线程下的 async/await 也是在一个线程下实现异步,可以看 .net,在一个线程下不存在线程间切换。CSP 与 actor 都是并发模型,发挥的是多线程的优势,比如 actor 模型所有消息都是异步交付的。JS 是跑在单线程下,并没有多线程并发能力。
from weekly.
推荐一篇关于异步演化史的文章 https://zhuanlan.zhihu.com/p/20322843
from weekly.
目前支持 Async/Await 的 Node.js 版本(Node 7)并非 LTS 版本,但是下一个 LTS 版本很快将会发布
现在node 8 LTS已经支持async,await,英文原文已更新:[UPDATE]: Node 8 LTS is out now with full Async/Await support.
from weekly.
[译] 6个Async/Await优于Promise的方面
之前已有相关译文与讨论,供大家参考
from weekly.
async/await 是对 yield 的简单封装,promise 结合 async/await 是比较好的玩法。
未来 CSP 或 Actor 用在 js 上 callback 本质是不会变的,现在也有 js-csp 的实现,还是利用 generator 或 async await 的语法糖。
from weekly.
@arcthur 主要是对于多线程下的 async/await 有点疑惑。比如我await 了一个 get,那我这个http get 的方法在单线程下是被扔到 task queue了。但是如果是多线程呢?这时候为什么不选择再开一个线程去执行 get 呢?那 await 到时候的语义除了流程控制是不是还有线程的切换呢?
from weekly.
@arcthur get,蟹蟹啦~
from weekly.
文章不错,清晰易懂
@monkingxue 要搞清楚多线程和多进程的区别
@arcthur 现在async function 会自动包成promise返回
from weekly.
@javie007 啊这个问题想请教下。就是在 r3层级中,比如 JS 语言中,我们都说 JS 是单线程的,但是这里的线程好像就是对应着内核的进程,再比如 Go 中的goroutine 说是用户态线程,但是如果启动的 goroutine 数小于硬件的逻辑核心数,那么其实这些 goroutine 也是跑在不同的逻辑核心上,按理来说对应的也应该是内核级别的进程。这里进程和线程该怎么区分呢?
from weekly.
要分清楚process, thread, coroutine。
thread的问题是,使用的是OS的scheduler,涉及system call,上下文切换成本高。
coroutine的问题是用不到多核。
所以一种先进的方案是使用M:N threading model ,自己实现了个scheduler,只调度到有限的线程,just like goroutine,具体怎么玩的可以参见goroutine的scheduler设计。
from weekly.
嗯呢,蟹蟹啦~
from weekly.
今天的 async await 让我想起当年老赵的 windjs,windjs 的灵感来自于 F#,看微软很早就在异步编程上有完善的方案。5年前的作品与今天的规范相比依然具有前瞻性。
老赵 windjs 的网站已经没有了,但文档还在,可以怀念一下。上面同学也提到了流程控制的话题,windjs 的设计放到今天算很完备的方案了。比如,windjs 在处理 cancel 时的想法
https://github.com/JeffreyZhao/windjs.org-cn/blob/master/source/docs/async/cancellation.md
windjs 为了避免浏览器不支持,提到了AOT和JIT两种编译方式(是不是想到 ng2),当时很多人诟病的是 JIT 下用 eval 来写代码不支持优化,不方便调试或安全性,但 aot 编译后是没有 eval 的。放到今天,大家都在用 babel,是否可以用更好的实现(不懂),其实大家已经不会反感作实时编译。
你看 tc39 到今天还在讨论关于 cancel 的设计用法。web 标准化总是慢于工业界发展,都等着他们出标准,浏览器支持不知道要等多少年了,但好歹也算是在讨论了。
from weekly.
chrome 和 nodejs 都已经原生支持 async/await
from weekly.
Related Issues (20)
- 【自荐开源】AI可视化SolidUI HOT 2
- 精读《自由 + 磁贴混合布局》
- 请假一次 🏳️
- 精读《自由布局吸附线的实现》
- 请假一次 HOT 1
- 精读《算法题 - 通配符匹配》
- 这里我想是对应的 '*' 不匹配任何字符? HOT 1
- 精读《算法题 - 统计可以被 K 整除的下标对数目》
- 精读《算法题 - 最小覆盖子串》
- 请假一次
- 精读《算法题 - 地下城游戏》
- 请假一次 HOT 6
- 精读《VisActor 数据可视化工具》
- 精读《算法题 - 编辑距离》
- 请假一次
- 精读《算法题 - 二叉树中的最大路径和》
- 休刊一段时间 HOT 8
- 真的牛逼,竟然检测有病毒
- 【开源自荐】基于nextjs14,良好的体验、响应式、编码设计,开源了C-Shopping开源电商平台 HOT 7
- 第二季准备开更,内容预告! HOT 8
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from weekly.