GithubHelp home page GithubHelp logo

Comments (16)

monkingxue avatar monkingxue commented on April 28, 2024 14

异步是前端很喜欢的讨论的一个问题,对我这样的前端新手而言,在为数不多的面试过程中,异步出现的概率是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.

camsong avatar camsong commented on April 28, 2024 8

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 设计其实是有很多问题的,功能上有很多的缺失。

  1. 缺少复杂的流程控制:always,progress,finally,pause,resume
  2. 一旦开始,不能中断,没有 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.

ascoders avatar ascoders commented on April 28, 2024 6

强调一下,文中结尾观点有误,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.

arcthur avatar arcthur commented on April 28, 2024 2

@monkingxue 多线程下的 async/await 也是在一个线程下实现异步,可以看 .net,在一个线程下不存在线程间切换。CSP 与 actor 都是并发模型,发挥的是多线程的优势,比如 actor 模型所有消息都是异步交付的。JS 是跑在单线程下,并没有多线程并发能力。

from weekly.

fanhc019 avatar fanhc019 commented on April 28, 2024 2

推荐一篇关于异步演化史的文章 https://zhuanlan.zhihu.com/p/20322843

from weekly.

try-to-fly avatar try-to-fly commented on April 28, 2024 2

目前支持 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.

BlackGanglion avatar BlackGanglion commented on April 28, 2024

[译] 6个Async/Await优于Promise的方面
之前已有相关译文与讨论,供大家参考

from weekly.

arcthur avatar arcthur commented on April 28, 2024

async/await 是对 yield 的简单封装,promise 结合 async/await 是比较好的玩法。

未来 CSP 或 Actor 用在 js 上 callback 本质是不会变的,现在也有 js-csp 的实现,还是利用 generator 或 async await 的语法糖。

from weekly.

monkingxue avatar monkingxue commented on April 28, 2024

@arcthur 主要是对于多线程下的 async/await 有点疑惑。比如我await 了一个 get,那我这个http get 的方法在单线程下是被扔到 task queue了。但是如果是多线程呢?这时候为什么不选择再开一个线程去执行 get 呢?那 await 到时候的语义除了流程控制是不是还有线程的切换呢?

from weekly.

monkingxue avatar monkingxue commented on April 28, 2024

@arcthur get,蟹蟹啦~

from weekly.

javie007 avatar javie007 commented on April 28, 2024

文章不错,清晰易懂
@monkingxue 要搞清楚多线程和多进程的区别
@arcthur 现在async function 会自动包成promise返回

from weekly.

monkingxue avatar monkingxue commented on April 28, 2024

@javie007 啊这个问题想请教下。就是在 r3层级中,比如 JS 语言中,我们都说 JS 是单线程的,但是这里的线程好像就是对应着内核的进程,再比如 Go 中的goroutine 说是用户态线程,但是如果启动的 goroutine 数小于硬件的逻辑核心数,那么其实这些 goroutine 也是跑在不同的逻辑核心上,按理来说对应的也应该是内核级别的进程。这里进程和线程该怎么区分呢?

from weekly.

javie007 avatar javie007 commented on April 28, 2024

要分清楚process, thread, coroutine。
thread的问题是,使用的是OS的scheduler,涉及system call,上下文切换成本高。
coroutine的问题是用不到多核。
所以一种先进的方案是使用M:N threading model ,自己实现了个scheduler,只调度到有限的线程,just like goroutine,具体怎么玩的可以参见goroutine的scheduler设计。

from weekly.

monkingxue avatar monkingxue commented on April 28, 2024

嗯呢,蟹蟹啦~

from weekly.

arcthur avatar arcthur commented on April 28, 2024

今天的 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.

Lizhooh avatar Lizhooh commented on April 28, 2024

chrome 和 nodejs 都已经原生支持 async/await

from weekly.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.