如何学好Javascript的异步编程
Posted 腾讯课堂Coding学院
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何学好Javascript的异步编程相关的知识,希望对你有一定的参考价值。
来源:Coding学院(ID:ke_coding)
导语
javascript的异步编程一直在发展。
通常,单线程的javaScript代码是由上往下依次执行的。多个任务需要处理时就需要排队处理,完成一个任务后才执行下一个任务。这种执行模式称之为:同步。同步的一个隐含问题是个别任务耗时较长时会导致线程阻塞,造成网页卡顿、阻塞用户交互等情况。
而异步编程是指将任务分为两段,一段现在执行,另一段放在未来执行。执行第一段后js线程将转向执行其他任务,直到响应后才把任务的第二段加入事件循环队列或任务队列中,这样就不会因为个别任务耗时长而导致线程阻塞问题。而异步编程的核心则是任务中现在运行部分与未来运行部分之间的关系。
◆ ◆ ◆
回调函数
Callback是javaScript中最基础的异步模式,他将任务在将来执行的部分单独抽象出一个函数,在需要执行将来部分时直接调用该函数。
比如Ajax(url,callback),将回调函数传进Ajax函数中,Ajax得到响应后将callback加入到事件队列中等待执行。
但是使用回调函数存在着两个问题,第一个就是众所周知并且深恶痛绝的回调地獄。当代码中出现嵌套的回调函数时,就破坏了代码的“线性”流程,取而代之的是嵌套回调函数造成的“非线性”流程。与同步执行的“线性”执行顺序很大不同,这里的执行顺序为A->E->B->D->E,如果B、C函数里又嵌套有Ajax执行函数呢?那就更复杂了。
回调函数的第二个问题是信任问题。如果上文所提到的Ajax(url,callback)方法是第三方库提供的,当Ajax请求成功后会调用callback,好像这一切都没什么问题。但是需要注意的是,这时候的callback已经是传到Ajax函数里,callback怎么调,调几次完全是由Ajax方法来进行控制。虽然通常情况下不会出现问题,但是万一Ajax方法抽风了呢?你的callback控制权已经在Ajax手上了,你无法控制。
◆ ◆ ◆
Promise
Promise的出现很好地解决了上述这两个问题。
针对信任问题,Promise换了个方式来处理。在这里Promise不把将来执行的部分交给第三方处理,而是希望第三方处理任务完成时通知并把结果传递到Promise,由Promise.then()控制的代码决定下一部分的任务。getJson(url)是一个第三方提供的异步获取json数据并返回promise对象的一个函数;Promise.resolve()是信任的第一层保证,它会将传递进来的promise对象/thenable对象/其他值转化为真实可靠的promise对象。在本例中getJson()返回的是promise对象,Promise.resolve()原封不动地将其返回。但如果某天getJson抽风了,返回的不再是promise对象而只是响应值的时候,Promise.resolve()也能将其转换为Promise对象,保证下一步能够正常执行。
第二层保证是上面提到的,任务的第二部分并没有传进到第三方函数中,把控制权交给第三方函数。而是通过获取第三方函数异步返回的响应值后,把任务第二部分交给了Promise对象的then方法进行处理,牢牢把控制权掌握在我们手上。而then方法让我们信任的理由是提供给then的函数回调会在promise决议后自动调用,而且一定是异步调用,这样就不会出现调用过早/过晚的问题;其次,promise只接受第一次决议时的then方法进行的调用,忽略其他决议,这样阻止了调用过多的问题。
针对回调地狱问题,由于每次执行then方法后都会返回一个新的Promise对象,所以可以采用链式调用写法,在then方法后继续调用另一个then方法。这段函数的执行顺序是先执行getJson(url1),响应后执行第一个then方法内的任务将来执行部分,返回getJson(url2)返回的新Promise对象,等待响应后执行第二个then方法。
如此反复可以返回多个Promise对象,这样可以用链式调用方式执行下去。如果要在回调中以这种顺序实现,则需要使用嵌套回调这样的写法,嵌套多了就形成回调地狱,所以Promise很好地解决了回调地狱的问题。
其实Promise一样使用了callback,只是通过标准化规范来使自身成为一个可信任的中间机构,并且可以使用链式调用,解决了callback异步调用的问题。
Promise看似很好地提供了以顺序表达异步流的方法,但是这并不完美。不断地链式调用意味着不断地调用then方法,这会导致代码冗余。那有没有更好地方法呢?下面就是即将要说的Generator。
◆ ◆ ◆
Generator
Generator的语法和行为跟传统函数有很大不同。在Generator内部增加了yield语句,用于定义内部不同的状态并且可以控制内部流程。那他对于Promise又有什么改进呢?
看看上面的代码使用Generator+Promise编写会是怎样的风格。通过yield出来一个Promise,然后通过这个Promise来控制生成器的迭代器,而在Generator内部则可以使用“线性”方式的同步写法,不需要使用then链式调用,代码的冗余程度也降低了。但如果多个异步操作需要“线性”执行的话,是不是也需要手工编写多个Promise来控制Generator内部流程呢?
co模块会是你的好帮手,只要你把Generator函数传进co函数,就会自动执行。本来需要多个回调嵌套的代码现在就以一种纯“线性”的方式编写出来,这会是更优的选择。
Generator通过关键字yield语句进行内部暂停,但是保持其内部状态,然后切换回主线程,只有控制Generator的迭代器才有能力在将来的主线程中从Generator暂停的地方恢复运行。这里关键的地方在于Generator内部有多个异步控制流程时,可以以同步方式编写代码,因为异步隐藏在yield语句之后,在异步操作完成时会从yield暂停的地方继续同步执行Generator内部代码。
◆ ◆ ◆
Async/Await
上面Generator美中不足的地方在于无法自动控制生成器内部流程,需要借助co模块。
好消息是ES7引入了async函数,配合await语句,无需co模块的帮助我们也能以“线性”方式进行异步编程。 正常情况下await后面是一个Promise对象,如果不是也会通过Promise.resolve()被转化。async函数执行到await时会先暂停这个函数,等到Promise决议后再继续运行,实质上就是在语言层面中提供了自动执行器。async函数调用后会返回一个Promise,在函数完全结束后这个Promise才会进行决议。
但是作为ES7的提案,各平台对Async的支持还不完善。目前Node.js的V7.x版本支持,同时Babel也支持对Async/await进行编译,如果项目在使用Babel的话也不妨尝试一下。
只要去做,
总有可能。
先转发,再点二维码,
就是对我们最好的支持!
▼
业界最顶尖的技术大咖/最权威的实战分享/最前沿的行业资讯/尽在腾讯课堂Coding学院
以上是关于如何学好Javascript的异步编程的主要内容,如果未能解决你的问题,请参考以下文章