理解javascript异步编程


Posted in Javascript onJanuary 27, 2016

一、异步机制

JavaScript的执行环境是单线程的,单线程的好处是执行环境简单,不用去考虑诸如资源同步,死锁等多线程阻塞式编程等所需要面对的恼人的问题。但带来的坏处是当一个任务执行时间较长时,后面的任务会等待很长时间。在浏览器端就会出现浏览器假死,鼠标无法响应等情况。所以在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应。所谓异步执行,不同于同步执行(程序的执行顺序与任务的排列顺序是一致的、同步的),每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。既然Javascript是单线程的,那它又如何能够异步的执行呢?

二、Javascript线程模型和事件驱动

JavaScript有一个基于事件循环的并发模式。这个模式与C语言和java有很大不同。

运行时的概念

理解javascript异步编程


函数调用形成堆栈帧。

function f(b){
  var a = 12;
  return a+b+35;
}

function g(x){
  var m = 4;
  return f(m*x);
}

g(21);

当调用函数g时,创建第一个包含g参数和局部变量的帧。当g函数调用f函数时,创建包含f参数和局部变量第二个堆栈帧并推到第一个堆栈帧的顶部。当f返回时,顶部的堆栈帧元素被弹出(只留下g调用)。当g函数返回时,堆栈为空。


堆是一个大型的非结构化区域,对象被分配到堆中。
队列
一个javascript运行环境包含一个信息队列,这个队列是一系列将被执行的信息列表。每一个消息被关联到一个函数上。当堆栈为空时,从消息队列中取出一个消息并进行处理。该处理包含调用相关的函数(以及因此产生一个初始化的堆栈帧)。当堆栈再次为空时,消息处理结束。
事件循环

事件循环的名字源于它的实现,经常像下面这样:

while(queue.waitForMessage()){
 queue.processNextMessage();
}

queue.waitForMessage同步等待一个消息。

1、运行到完成
每个消息完全处理之后,其它消息才会被处理。这样的好处就是当一个函数不能被提前,只能等其他函数执行完毕(并且可以修改数据的函数操作)。这不同于C,例如,如果一个函数在一个线程运行时,它可以停在任何点运行在另一个线程一些其他的代码。这种模式的缺点是,如果一个消息时间过长完成,Web应用程序无法处理像点击或滚动的用户交互。该浏览器可缓解此与“脚本花费的时间太长运行”对话框。一个很好的做法,遵循的是使信息处理短,如果可能削减一个消息到几条消息。
2、添加消息
在网页浏览器中,事件可以在任何时候添加,一个事件发生并伴随事件监听绑定到事件上。如果没有事件监听,则事件丢失。就像点击一个元素,元素上绑定点击事件。调用setTimeout时,当函数的第二个参数时间被传递进去,将添加一个消息到队列中。如果在队列中没有其他消息,该消息被立即处理;然而,如果有消息,则setTimeout的信息将必须等待其它消息以进行处理。由于这个原因,第二个参数是最小的时间,而不是一个保证时间。
3、几个运行环境之间的通信
一个web worker或跨域iframe都有自己的堆栈,堆,和消息队列。两个不同的运行环境只能通过postMessage的方法发送消息进行通信。这种方法增加了一个消息到其他运行时,如果后者监听消息事件。
从不阻塞

事件循环模型是javascript的一个很有意思的属性,不像其它语言,它从不阻塞。假定浏览器中有一个专门用于事件调度的实例(该实例可以是一个线程,我们可以称之为事件分发线程event dispatch thread),该实例的工作就是一个不结束的循环,从事件队列中取出事件,处理所有很事件关联的回调函数(event handler)。注意回调函数是在Javascript的主线程中运行的,而非事件分发线程中,以保证事件处理不会发生阻塞。通过事件和回调的I/O操作是一个典型的表现,所以当应用等待索引型数据库查询返回或XHR请求返回时,它仍然可以处理其他事情比如用户输入。

三、回调

回调是javascript的基础,函数被作为参数进行传递。像下面:

f1();
f2();
f3();

如果f1中执行了大量的耗时操作,而且f2需要在f1之后执行。则程序可以改为回调的形式。如下:

function f1(callback){
  setTimeout(function () {
  // f1的大量耗时任务代码并的到三个结果i,l,you.

  console.log("this is function1");

  var i = "i", l = "love", y = "you";
    if (callback && typeof(callback) === "function") {
      callback(i,l,y);
    }
  }, 50);
}

function f2(a, b, c) {
  alert(a + " " + b + " " + c);
  console.log("this is function2");
}

function f3(){console.log("this is function3");}
f1(f2);
f3();

运行结果:

this is function3
this is function1
i love you
this is function2

采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
回调函数的优点是简单,轻量级(不需要额外的库)。缺点是各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。某个操作需要经过多个非阻塞的IO操作,每一个结果都是通过回调,产生意大利面条式(spaghetti)的代码。

operation1(function(err, result) {
  operation2(function(err, result) {
    operation3(function(err, result) {
      operation4(function(err, result) {
        operation5(function(err, result) {
          // do something useful
        })
      })
    })
  })
})

四、事件监听

另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

// plain, non-jQuery version of hooking up an event handler
var clickity = document.getElementById("clickity");
clickity.addEventListener("click", function (e) {
  //console log, since it's like ALL real world scenarios, amirite?
  console.log("Alas, someone is pressing my buttons…");
});

// the obligatory jQuery version
$("#clickity").on("click", function (e) {
  console.log("Alas, someone is pressing my buttons…");
});

也可以自定义事件进行监听,关于自定义事件,属于另外一部分的内容。这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。

五、观察者模式

我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。

var pubsub = (function(){
  var q = {}
    topics = {},
    subUid = -1;
  //发布消息
  q.publish = function(topic, args) {
    if(!topics[topic]) {return;}
    var subs = topics[topic],
      len = subs.length;
    while(len--) {
      subs[len].func(topic, args);
    }
    return this;
  };
  //订阅事件
  q.subscribe = function(topic, func) {
    topics[topic] = topics[topic] ? topics[topic] : [];
    var token = (++subUid).toString();
    topics[topic].push({
      token : token,
      func : func
    });
    return token;
  };
  return q;
  //取消订阅就不写了,遍历topics,然后通过保存前面返回token,删除指定元素
})();
//触发的事件
var f2 = function(topics, data) {
  console.log("logging:" + topics + ":" + data);
  console.log("this is function2");
}

function f1(){
   setTimeout(function () {


// f1的任务代码



console.log("this is function1");
    //发布消息'done'
    pubsub .publish('done', 'hello world');


}, 1000);
}
pubsub.subscribe('done', f2);
f1();

上面代码的运行结果为:

this is function1
logging:done:hello world
this is function2

观察者模式的实现方法有很多种,也可以直接借用第三方库。这种方法的性质与"事件监听"类似(观察者模式和自定义事件非常相似),但是明显优于后者。观察者模式和事件监听一样具有良好的去耦性,并且有一个消息中心,通过对消息中心的处理,可以良好地监控程序运行。

六、Promises对象

Promises的概念是由CommonJS小组的成员在 Promises/A规范 中提出来的。Promises被逐渐用作一种管理异步操作回调的方法,但出于它们的设计,它们远比那个有用得多。Promise允许我们以同步的方式写代码,同时给予我们代码的异步执行。

function f1(){
  var def = $.Deferred();
  setTimeout(function () {
    // f1的任务代码
    console.log("this is f1");
    def.resolve(); 
  }, 500);
  return def.promise();
}

function f2(){
  console.log("this is f2");
}

f1().then(f2);

上面代码的运行结果为:

this is f1
this is f2

上面引用的是jquery对Promises/A的实现,jquery中还有一系列方法,具体可参考:Deferred Object.关于Promises,强烈建议读一下You're Missing the Point of Promises.还有很多第三方库实现了Promises,如:Q、Bluebird、 mmDeferred 等。Promise(中文:承诺)其实为一个有限状态机,共有三种状态:pending(执行中)、fulfilled(执行成功)和rejected(执行失败)。其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。状态转换关系为:pending->fulfilled,pending->rejected。随着状态的转换将触发各种事件(如执行成功事件、执行失败事件等)。 下节具体讲述状态机实现js异步编程。

七、状态机

Promises的本质实际就是通过状态机来实现的,把异步操作与对象的状态改变挂钩,当异步操作结束的时候,发生相应的状态改变,由此再触发其他操作。这要比回调函数、事件监听、发布/订阅等解决方案,在逻辑上更合理,更易于降低代码的复杂度。关于Promises可参考:JS魔法堂:剖析源码理解Promises/A规范 。

八、ES6对异步的支持

这是一个新的技术,成为2015年的ECMAScript(ES6)标准的一部分。该技术的规范已经完成,但实施情况在不同的浏览器不同,在浏览器中的支持情况如下。

理解javascript异步编程

var f1 = new Promise(function(resolve, reject) {
  setTimeout(function () {
    // f1的任务代码
    console.log("this is f1");
    resolve("Success");

  }, 500); 
});
function f2(val){
  console.log(val + ":" + "this is f2");
}
function f3(){
  console.log("this is f3")
}
f1.then(f2);
f3();

以上代码在Chrome 版本43中的运行结果为:

this is f3
this is f1
Success:this is f2

以上就是针对javascript异步编程的了解学习,之后还有相关文章进行分享,不要错过哦。

Javascript 相关文章推荐
深入理解JavaScript系列(33):设计模式之策略模式详解
Mar 03 Javascript
JavaScript实现动画打开半透明提示层的方法
Apr 21 Javascript
javascript实现点击后变换按钮显示文字的方法
May 13 Javascript
jquery实现的用户注册表单提示操作效果代码分享
Aug 28 Javascript
AngularJS中$interval的用法详解
Feb 02 Javascript
Angularjs实现多个页面共享数据的方式
Mar 29 Javascript
深入浅析javascript函数中with
Oct 28 Javascript
IE11下CKEditor在Bootstrap Modal中下拉问题的解决
Sep 25 Javascript
vue改变循环遍历后的数据实例
Nov 07 Javascript
JS实现电商商品展示放大镜特效
Jan 07 Javascript
如何通过JS实现日历简单算法
Oct 14 Javascript
jquery自定义组件实例详解
Dec 31 jQuery
js实现的鼠标滚轮滚动切换页面效果(类似360默认页面滚动切换效果)
Jan 27 #Javascript
AngularJS转换响应内容
Jan 27 #Javascript
jQuery+css实现的切换图片功能代码
Jan 27 #Javascript
javascript中的3种继承实现方法
Jan 27 #Javascript
jQuery+css实现的换页标签栏效果
Jan 27 #Javascript
js实现的彩色方块飞舞奇幻效果
Jan 27 #Javascript
JavaScript下的时间格式处理函数Date.prototype.format
Jan 27 #Javascript
You might like
php Smarty 字符比较代码
2011/02/27 PHP
浅析PHP程序防止ddos,dns,集群服务器攻击的解决办法
2013/06/18 PHP
php获得用户ip地址的比较不错的方法
2014/02/08 PHP
判断用户是否在线的代码
2011/03/05 Javascript
table对象中的insertRow与deleteRow使用示例
2014/01/26 Javascript
JavaScript中Function()函数的使用教程
2015/06/04 Javascript
jQuery实现平滑滚动页面到指定锚点链接的方法
2015/07/15 Javascript
jQuery时间轴插件使用详解
2015/07/16 Javascript
jquery插件autocomplete用法示例
2016/07/01 Javascript
微信小程序  wx.request合法域名配置详解
2016/11/23 Javascript
nodejs之get/post请求的几种方式小结
2017/07/26 NodeJs
详解webpack与SPA实践之开发环境搭建
2017/12/18 Javascript
element-ui 表格实现单元格可编辑的示例
2018/02/26 Javascript
JS实现点击按钮可实现编辑功能
2018/07/03 Javascript
详解关于JSON.parse()和JSON.stringify()的性能小测试
2019/03/14 Javascript
微信小程序使用 vant Dialog组件的正确方式
2020/02/21 Javascript
微信小程序实现日历小功能
2020/11/18 Javascript
利用JS判断元素是否为数组的方法示例
2021/01/08 Javascript
python自动格式化json文件的方法
2015/03/11 Python
python 远程统计文件代码分享
2015/05/14 Python
在Python的Django框架中更新数据库数据的方法
2015/07/17 Python
Python yield 使用方法浅析
2017/05/20 Python
python+ffmpeg视频并发直播压力测试
2018/03/06 Python
Python使用itertools模块实现排列组合功能示例
2018/07/02 Python
Python拼接微信好友头像大图的实现方法
2018/08/01 Python
Python3实现二叉树的最大深度
2019/09/30 Python
python matplotlib拟合直线的实现
2019/11/19 Python
python使用配置文件过程详解
2019/12/28 Python
如何使用Python破解ZIP或RAR压缩文件密码
2020/01/09 Python
法国在线宠物店:zooplus.fr
2018/02/23 全球购物
美国正版电视节目和电影在线观看:Hulu
2018/05/24 全球购物
销售业务实习自我鉴定
2013/09/23 职场文书
2015年九一八事变纪念活动实施方案
2015/05/06 职场文书
读书笔记怎么写
2015/07/01 职场文书
利用python做数据拟合详情
2021/11/17 Python
div与span之间的区别与使用介绍
2021/12/06 HTML / CSS