关于异步——事件轮询、deferred、Promise

什么是单线程,和异步有什么关系

单线程 - 只有一个线程,同一时间只能做一件事情,两段JS不能同时执行。

为什么JS是单线程的 - 避免DOM渲染的冲突。

pic5

异步就是一种“无奈”的解决方案,解决因为单线程执行而造成的一些问题(比如等待20ms执行一个方法,那其他的方法难道也要等20ms后这个方法执行完再执行吗?)。

什么是event-loop

event-loop即事件轮询。是JS实现异步的具体解决方案。

  1. 同步代码(不是setTimeOut,ajax等),直接执行。
  2. 异步函数先放在异步队列中。
  3. 待同步函数执行完毕,轮询执行异步队列中的函数。

举个简单的例子:

pic6

先执行console.log这个同步函数,它被放在了主进程中,直接执行。

主进程中的执行完后,将异步队列中的放在主进程中继续执行。

再举一个稍微复杂的例子

pic7

这种情况中,代码执行到第一个setTimeout时,代码告诉计算机100ms后再将打印1的函数放在异步队列中,而执行第二个setTimeout时,便直接将其中打印2的函数放在异步队列中。console.log(3)则直接放在主进程中。

当主进程内的任务执行完后,计算机观察异步队列,此时异步队列中只有第二个setTimeout函数,第一个setTimeou函数还未被放入。因此此时第二个setTimeout函数被放入主进程执行,在执行完后再观察异步队列,此时JS引擎会一直轮询观察异步队列,当它发现第一个setTimeout函数出现在异步队列中时,再将其放入主进程并执行。

之前有说过这句话:待同步函数执行完毕,轮询执行异步队列中的函数。那么什么叫轮询呢,指的就是每当主进程中的任务执行完后,计算机便会去监视异步队列,以此往复,只要往异步队列中塞入新任务并且此时主进程中的任务执行完,那么新任务会立刻被拿进主进程执行,执行完后再去监听异步队列中是否有新塞进来的任务。这个循环过程,即轮询。

再举一个更为复杂一点的例子,我们加一个ajax:

pic8

其他的和上面相同,而对于ajax而言,它里面的内容何时被放入异步队列中?很简单,就是当ajax请求成功的时候将其任务放入异步队列中。图片解释如下:

pic9

是否用过jquery的Deferred

deferred——延迟

首先看一下,jQuery在1.5版本之前使用ajax是这样使用的

pic10

而在1.5版本之后,使用ajax便可以这样使用了:

pic11

或者这样使用:

pic12

我们需要知道如下四大特点:

pic13

1.5之前,是使用了callback回掉函数的形式,而在1.5之后,ajax返回了deferred对象,可以进行链式操作。

首先,不管我们是用什么方法写代码,都无法改变JS异步和单线程的本质。

其次,新增加的deferred对象有什么好处了,它从写法上杜绝了callback这种形式,是一种语法糖,但是解耦了代码。比如我们要在成功后执行个3000行的代码,1.5版本之前,这3000行都得写在success回调函数中,而1.5版本之后,我们可以使用三个.done()或者.then(),每个都有1000行代码,这是根本上的不同,解耦了代码。

最后需要提到的是,这种使用方式很好的体现了开放封闭原则——对扩展方法开放,对修改方法封闭。比如我们要在ajax成功后再加入一个console.log('成功'),那么1.5版本之前就必须修改success回调函数。而1.5版本之后,我们只需要在之后再加一个.then()或者.done(),在其中加入内容即可。总结来说:允许你自由添加多个回调函数。

顺带一提开放封闭原则的优点,为什么要避免修改方法呢,因为如果修改代码,我们需要重新测试这个方法,涉及到的测试功能可能很多,而如果我们改为新增一个方法,那么只需要测试这个新增的即可。对测试工作者和合作代码者都是很方便的提升。

怎么使用jquery的deferred?

用一个例子来显示如何使用deferred:

pic14

如上,我们定义了一个简单的异步操作代码,2000ms后执行task这个函数。

但是,入伙我们要在执行完成之后再进行一些复杂操作呢?未使用deferred之前,我们可以直接修改task函数,在内部加入这些操作。但是正如之前所说的,这样违背了开放封闭原则。而且每次新增内容都需要修改一次,无论是打代码的还是负责测试的都很麻烦。因此,deferred很好的解决了这个问题。

我们来看看使用deferred后,代码是什么样子的:

pic15

我们可以看到,waitHandle函数中创建了一个deferred对象,在执行wait函数后,返回了已经经过某种修改的这个deferred对象。而这种修改,即是在setTimeout完成后执行resolve()或者reject()操作。

pic16

然后根据上图执行,w即是返回后的deferred对象,如果它已经执行了resolve()那么执行.then()内第一个函数(即成功函数)或者.done();如果它已经执行了reject()那么执行.then()内第二个函数(即失败函数)或者.fail();

上面这种写法,还是有问题。那就是dtd是一个全局对象,所以它的执行状态可以从外部改变。



var wait = function(dtd){

    var dtd = $.Deferred(); // 新建一个Deferred对象

  var tasks = function(){

    alert("执行完毕!");

    dtd.resolve(); // 改变Deferred对象的执行状态

  };

  setTimeout(tasks,5000);

  return dtd;

};

$.when(wait(dtd))

.done(function(){ alert("哈哈,成功了!"); })

.fail(function(){ alert("出错啦!"); });

dtd.resolve();

在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出"哈哈,成功了!"的提示框,等5秒之后再跳出"执行完毕!"的提示框。

为了避免这种情况,jQuery提供了deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。

最初的例子更改后,便成为如下:

pic17

然后再加一个w.reject()

pic18

也就是说,使用了promise()后该deferred对象无法再更改状态,即无法再使用reject()和resolve()方法了。

promise的基本使用和原理

基本语法回顾

举个例子,解释promise的语法:

pic19

异常捕获

当然也没有必要一直在.then()中都写入失败函数,这里我们直接使用异常捕获

pic20

为了使得catch中统一捕获语法等业务逻辑之外的错误和业务逻辑之内的错误(reject()),我们只需要在调用reject()时加上参数'错误信息'即可。这样catch能统一捕获错误。

多个串联

而如果我们要加载两个图片,第一个加载完后再加载第二个图片,这时就可以使用promise串联。

pic24

这里重点在于,第一个.then()结束后返回的是result2这个promise对象。而在之前返回的则是result1。不同要求,使用方法不同,返回的也就不同了。

Promise.all和Promise.race

21.png

all()方法,指的是当all接收到的promise数组内所有对象执行完后统一执行.then;.then()内函数接收到一个数组,该数组包含了这个promise数组内所有promise执行完后返回的内容。

而race则是当接收到的promise数组内首先执行结束完一个后就执行.then,即只要有一个完成,就执行.then;因此,.then()内函数接收到的不是数组就是一个数据data,指代了第一个执行完后的promise返回的内容。

Promise标准

详细标准我在去年写在这一篇文章中,点击进入

介绍一下async/await(和promise的区别)

对于promise而言,.then其实只是将callback回调函数拆分了,而ES7中预先提出的async和await则提出了一种直接的同步写法。用法如下:

pic22

因为此时async和await只是一个提案,所以需要使用babel-polyfill兼容支持

写法如下

pic23

最终result1和result2接收到的是promise成功后返回的img标签。

async使用了await使用了Promise,但并没有和Promise冲突,它提供了一个同步的写法。

async和await之后我会再写一篇详细的文章来学习。

总结一下当前JS解决异步的方案

jQuery Deferred

ES6——Promise

Async/Await

 MVVM和VUE的知识点总结
原型——在zepto和jquery中的应用 
上一篇:MVVM和VUE的知识点总结
下一篇:原型——在zepto和jquery中的应用


如果我的文章对你有帮助,或许可以打赏一下呀!

支付宝
微信
QQ