agagega

agagega

V2EX 第 302609 号会员,加入于 2018-03-22 23:16:35 +08:00
今日活跃度排名 2183
根据 agagega 的设置,主题列表被隐藏
二手交易 相关的信息,包括已关闭的交易,不会被隐藏
agagega 最近回复了
7 小时 32 分钟前
回复了 shendaowu 创建的主题 JavaScript JavaScript 中 async/await 有没有必要彻底搞懂?
计算机的知识从来都是可深可浅。其实所有领域的知识都是这样,但因为计算机整个体系人造属性特别强,所以这个特征表现得更明显。

对于 async/await ,最简单的理解:某些函数需要加 await 才能调用,用到 await 的函数需要用 async 标记,而调 async 函数也需要 await 。

更深一点:await 的其实是一个 Promise 对象,所有 async/await 的代码都可以改写为 Promise 和 then 。还有个叫 Generator 的东西,它的思路和 Promise 完全不一样。更古早的代码里还有种写法叫回调函数。这四者可以相互改写。

为什么要这样?因为一些操作(如网络请求、读写文件)需要等待取回数据,在等待过程中可以让 CPU 做其他的事情提高效率,等到数据读完再回来执行下一步代码,所以有了回调函数,这种写法叫做异步。NodeJS 里有同步读写文件的方法,可以和异步写法对比感受差异。

因为浏览器环境强调实时响应,所以 JS 一开始就很重视异步,这也是其他语言容易实现 sleep 函数而 JS 很麻烦的原因。

为什么有这么多种写法?程序运行的一个本质问题是,下一步要执行什么。通常的逻辑是,如果是普通语句就正常执行,如果是函数调用就传参进入,如果是 return 则回到上一层调用继续执行。

假如所有代码都写到一个 C 函数里,那我们不需要考虑这么多复杂的问题,因为我们可以自由地自己做调度。但我们希望在保证代码结构性(拆成函数)的同时,能够继续保证效率(在等待网络响应时能执行别的代码)。对于有匿名函数的语言,最直接的想法就是回调函数,而且通常这个回调函数除了固定的参数外,还要能从发请求的地方抓一些变量写东西(这被称作闭包)。回调函数的思路和处理网页事件是一致的。

但回调函数的写法意味着,每次要发送请求或者干什么事情时,都要在上一层回调函数里再新建一层回调函数,缩进层次会特别深,这被称作回调地狱( callback hell )。更麻烦的是,真实代码的逻辑可能不是线性的,可能有一个 if 或循环,里面执行一些异步代码,然后再出来。这种时候写回调函数简直就是脑力大作战。

换种思路,我们能不能发明一种特殊的函数,比传统函数更强大?传统函数一旦 return ,只能再从头开始执行。我们想要的这个新函数,可以从中间返回,下次再从上次返回的地方继续执行。这样我们就不用写回调了,发请求的时候返回,等响应到了再跳回来就可以了。JS 本身就有闭包,且支持函数套函数(参考计数器的写法),这种功能理论上只需要实现一个返回函数的函数就行了,但 JS 还是有一个专门的语法,叫生成器( generator )。

而回调函数的写法也有改进空间。如果能把异步操作封装进一个对象里,然后动态添加每一层的回调,虽然还得写很多匿名函数,但缩进上至少可以拍平了。这就是 Promise ,它用 then 函数连接下一个回调。

如果再把一层层的 then 里的函数也抽出来,那 then 就变成了 await 。而对于用到了 await 的函数,它的执行流程和生成器就非常像了,在 await 的地方跳出去交还给 runtime ,等到合适的时候再跳回来。所以用到 await 的函数和生成器生成的函数一样,不是普通函数,必须要有区别,所以有 async 标记。但到这里应该能理解,这几种写法是等价的,await 一个 async 函数,和 await 一个返回 Promise 的普通函数,是一回事。

关于回调函数的讨论还可以更进一步。回调函数的写法,是把事件响应后要执行的代码封装成一个函数。但如果我把所有要执行的语句都写成这个样子呢?比如正常的 a=b+1;b=c+1 ,把后一句也写在一个回调函数里面会怎么样?除了极度蛋疼外,你也会获得极度的灵活性。

实际上,这个由接下来的代码组成的回调函数有个学名,叫 continuation (有时中文翻译作续体)。一些语言(如 Lisp 、Ruby )直接提供了操作 continuation 对象的能力,但这个功能太强大,所以衍生出一种弱一些的变体,叫 delimited continuation ,也就是需要指定这个接下来执行的函数到哪为止。Kotlin 里面带 @ 符号的 return 就有点这个味。

而 async/await 的语法也可以进一步泛化。await 的逻辑是跳出去再跳回来,形式上是否和异常的 try catch 有点像?特别某些语言的异常还真的支持 retry 语句。还真的有种语法概念把 await 和异常统一起来,叫 algebraic affects 。所以 await 真的没什么神秘的。

到这一步其实还有很多问题没有深入,比如这些 runtime 在操作系统和原生代码层面是怎么实现和封装的,其他模型比如 go 的 goroutine 和传统的多进程模型等。但我想你现在理解 async/await 一定容易了很多:)
那是因为用中文还能搜到东西,日语、西班牙语、俄语这些还能有内容,使用人数更少的语言就真的和计算机绝缘了。中文计算机内容有内容农场污染(但这个英文也有),独立博客少,更重要的原因或许是很多东西根本没人写。相比之下,知乎在计算机方面算得上不错的信息源,但现在也不行了。

令我比较感慨的一个时刻是,想搜 PL 相关的一些东西,知乎上有人讨论,但很多都是小圈子卖萌黑话,像是某群二次元爱好者的 play 。但我用英文搜索时,在 StackOverflow 上就有非常正经的、语言平实的回答和讨论,这对比比用中文啥也搜不出来更难受。
1 天前
回复了 pxiphx891 创建的主题 Apple 关于 iMessage 端到端加密,我有一个问题
简单来说你的信任总是要落在某个基点的,如果你觉得苹果的 A 系列芯片有手脚,那什么操作都不安全。再往上是 iOS 、App Store 和聊天 App 本身。如果你愿意信任 App 那这个问题就有讨论的意义了。

Signal 支持让联系人线下相互扫码确认公钥没被篡改,到这一步如果服务器也不记录历史消息,那基本可以确认为安全。

iMessage 因为太黑盒了,苹果说的话很暧昧,也不开源,所以无法判断。但至少可以确定,开启了 iCloud 同步的 iMessage ,不咋安全。
1 天前
回复了 pxiphx891 创建的主题 Apple 关于 iMessage 端到端加密,我有一个问题
反诈宣传各种暗示你关闭 FaceTime 和 iMessage ,但他们明明有能力直接墙掉,这很奇怪
有一个为苹果不给 iPhone 加内存的辩解:如果今年 8G ,明年 12G ,那老机型只会被淘汰得更快。某种思路上看,这个说法还有点道理
7 天前
回复了 cxhello 创建的主题 Ruby on Rails Ruby on Rails 国内使用情况
Ruby 适合创业,国内创业不行了 Ruby 就不行了
9 天前
回复了 OliverDD 创建的主题 程序员 对数据库感兴趣,但完全接受不了 cpp
cpp 的心智负担是重,但你说的右值引用和移动构造这些东西并不是 cpp 的心智负担造成的,甚至不是因为 cpp 没有 gc 造成的,所有支持值语义的语言都必须实现类似的概念。
一个人觉得残忍是因为他作为个体在公司面前没有议价能力,只能成为赤裸裸内卷的代价。而如果一个人认为自己有议价能力,这种模式就会显得非常美好,说白了就是咨询。

《程序员的软技能》这本书里提到一种思维模式,即打工人不要想象自己是就职公司的一分子,而是自己开了一家一个人的公司,然后把当前雇主当成自己唯一的客户。这个思路很有意思。
Mac 和 iOS 以及 Linux 普遍支持 Emacs 风格快捷键,所以还好。用 HHKB 更麻烦的其实是 Esc 和~,这两个键合二为一很不方便
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2812 人在线   最高记录 6543   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 11ms · UTC 12:30 · PVG 20:30 · LAX 05:30 · JFK 08:30
Developed with CodeLauncher
♥ Do have faith in what you're doing.