理解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 相关文章推荐
JS 模态对话框和非模态对话框操作技巧汇总
Apr 15 Javascript
用Jquery.load载入页面实现局部刷新
Jan 22 Javascript
JQuery 两种方法解决刚创建的元素遍历不到的问题
Apr 13 Javascript
将List对象列表转换成JSON格式的类实现方法
Jul 04 Javascript
js实现的在线调色板功能完整实例
Dec 21 Javascript
BootStrap Table对前台页面表格的支持实例讲解
Dec 22 Javascript
使用AngularJS编写多选按钮选中时触发指定方法的指令代码详解
Jul 24 Javascript
基于Vue开发数字输入框组件
Dec 19 Javascript
js实现文件上传功能 后台使用MultipartFile
Sep 08 Javascript
浅谈高大上的微信小程序中渲染html内容—技术分享
Oct 25 Javascript
在vant中使用时间选择器和popup弹出层的操作
Nov 04 Javascript
JavaScript设计模式之原型模式详情
Jun 21 Javascript
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
Ubuntu中搭建Nginx、PHP环境最简单的方法
2015/03/05 PHP
Smarty日期时间操作方法示例
2016/11/15 PHP
php和asp语法上的区别总结
2019/05/12 PHP
游戏人文件夹程序 ver 4.03
2006/07/14 Javascript
轻量级 JS ToolTip提示效果
2010/07/20 Javascript
JQUBAR1.1 jQuery 柱状图插件发布
2010/11/28 Javascript
读jQuery之二(两种扩展)
2011/06/11 Javascript
javascript 事件处理程序介绍
2012/06/27 Javascript
JavaScript去除空格的三种方法(正则/传参函数/trim)
2013/02/06 Javascript
原生js实现查找/添加/删除/指定元素的class
2013/04/12 Javascript
jquery 添加节点的几种方法介绍
2013/09/04 Javascript
用js设置下拉框为只读的小技巧
2014/04/10 Javascript
Egret引擎开发指南之创建项目
2014/09/03 Javascript
JQuery boxy插件在IE中边角图片不显示问题的解决
2015/05/20 Javascript
创建自己的jquery表格插件
2015/11/25 Javascript
使用pcs api往免费的百度网盘上传下载文件的方法
2016/03/17 Javascript
jQueryUI 拖放排序遇到滚动条时有可能无法执行排序的小bug及解决方案
2016/12/19 Javascript
js中的 || 与 && 运算符详解
2018/05/24 Javascript
详解js常用分割取字符串的方法
2019/05/15 Javascript
vue2之简易的pc端短信验证码的问题及处理方法
2019/06/03 Javascript
python基于mysql实现的简单队列以及跨进程锁实例详解
2014/07/07 Python
使用Python内置的模块与函数进行不同进制的数的转换
2016/03/12 Python
Django 浅谈根据配置生成SQL语句的问题
2018/05/29 Python
python使用正则表达式来获取文件名的前缀方法
2018/10/21 Python
Python批量删除只保留最近几天table的代码实例
2019/04/01 Python
Python cookie的保存与读取、SSL讲解
2020/02/17 Python
python dict乱码如何解决
2020/06/07 Python
全球速卖通西班牙站:AliExpress西班牙
2017/10/30 全球购物
Kaufmann Mercantile官网:家居装饰、配件、户外及更多
2018/09/28 全球购物
五年后的职业生涯规划
2014/03/04 职场文书
广告词串烧
2014/03/19 职场文书
小学数学课后反思
2014/04/23 职场文书
2014年妇幼保健工作总结
2014/12/08 职场文书
教师学习十八届五中全会精神心得体会
2016/01/05 职场文书
vue完美实现el-table列宽自适应
2021/05/08 Vue.js
微软Win11有哪些隐藏功能? windows11多个功能汇总
2021/11/21 数码科技