深入理解JS异步编程(一)
js事件概念
异步回调
首先了讲讲js中 两个方法 setTimeout()和 setInterval()
定义和用法:
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。
语法:
setTimeout(callback,time)
callback 必需。要调用的函数后要执行的 JavaScript 代码串。
time 必需。在执行代码前需等待的毫秒数。
setInterval() 方法和setTimeout很相似,可按照指定的周期(以毫秒计)来调用函数或计算表达式。
function timeCount()
{console.log("this is setTimeout");
setTimeout(timeCount,1000);
}
timeCount();
function timeCount2(){
console.log("this is setInterval");
}
setInterval("timeCount2()",1000);
//另外一种写法setInterval(timeCount2,1000),callback是eval环境下执行
上述两段代码都可以每隔1000毫秒延迟执行timecount函数。
线程阻塞
JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序.
function f() { console.log("hello world");}
setTimeout(f, 3000);
var t = new Date(); //运行5秒
while(true) {
if(new Date() - t > 5000) {
break; }
}
执行上述代码,可以发现,打印hello world,会在5秒的样子,因为JS是单线程,会在while循环里面消耗5秒的时间,形成阻塞。等到5s过去,发现在队列里的settimeout已经到时间了,会马上执行函数。
队列
浏览器是基于一个事件循环的模型,在这里面,可以有多个任务队列,比如render是一个队列,响应用户输入是一个,script执行是一个。任务队列里放的是任务,同一个任务来源的任务肯定在同一个任务队列里。任务有优先级,鼠标或键盘响应事件优先级高,大概是其他任务的3倍。
而我们常用的setTimeout函数,其本质上也就是向这个任务队列添加回调函数,JavaScript引擎一直等待着任务队列中任务的到来.由于单线程关系,这些任务得进行排队,一个接着一个被引擎处理.
如果队列非空,引擎就从队列头取出一个任务,直到该任务处理完,即返回后引擎接着运行下一个任务,在任务没返回前队列中的其它任务是没法被执行的。
异步函数类型
异步IO
首先来看看很典型的一个例子 ajax
var ajax = new XMLHttpRequest;
ajax.open("GET",url);
ajax.send(null);
ajax.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
return success(request.responseText);
} else {
return fail(request.status);
}
}
}
异步计时
setTimeout,setInterval都是基于事件驱动型的,通常浏览器不会给这个太快的速度,一般是200次/秒,效率太低了是吧如果遇到有密集型的运算的话,那就呵呵了。但是在node.js中还有process.nextTick()这个强大的东西,运行的速度将近10万次/秒,很可观。
process.nextTick(callback)
功能:在事件循环的下一次循环中调用 callback 回调函数。效果是将一个函数推迟到代码书写的下一个同步方法执行完毕时或异步方法的事件回调函数开始执行时;与setTimeout(fn, 0) 函数的功能类似,但它的效率高多了。
基于node.js的事件循环分析,每一次循环就是一次tick,每一次tick时,v8引擎从事件队列中取出所有事件依次进行处理,如果遇到nextTick事件,则将其加入到事件队尾,等待下一次tick到来时执行;造成的结果是,nextTick事件被延迟执行;
nextTick的确是把某任务放在队列的最后(array.push)。
nodejs在执行任务时,会一次性把队列中所有任务都拿出来,依次执行。如果全部顺利完成,则删除刚才取出的所有任务,等待下一次执行,如果中途出错,则删除已经完成的任务和出错的任务,等待下次执行。如果第一个就出错,则throw error。
下面看一下应用场景(包含计算密集型操作,将其进行递归处理,而不阻塞进程):
var http = require('http');
var wait = function (mils) {
var now = new Date;
while (new Date - now <= mils);
};
function compute() {
// performs complicated calculations continuously
console.log('start computing');
wait(1000);
console.log('working for 1s, nexttick');
process.nextTick(compute);
}
http.createServer(function (req, res) {
console.log('new request');
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World');
}).listen(5000, '127.0.0.1');
compute();
异步错误处理
异步异常的特点
由于js的回调异步特性,无法通过try catch来捕捉所有的异常:
try {
process.nextTick(function () {
foo.bar();
});
} catch (err) {
//can not catch it
}
而对于web服务而言,其实是非常希望这样的:
//express风格的路由
app.get('/index', function (req, res) {
try {
//业务逻辑
} catch (err) {
logger.error(err);
res.statusCode = 500;
return res.json({success: false, message: '服务器异常'});
}
});
如果try catch能够捕获所有的异常,这样我们可以在代码出现一些非预期的错误时,能够记录下错误的同时,友好的给调用者返回一个500错误。可惜,try catch无法捕获异步中的异常。
难道我们就这样放弃了么? 其实还有一个办法
onerror事件
我们一般通过函数名传递的方式(引用的方式)将要执行的操作函数传递给onerror事件,如
window.onerror=reportError;
window.onerror=function(){alert('error')}
但我们可能不知道该事件触发时还带有三个默认的参数,他们分别是错误信息,错误页面的url和错误行号。
window.onerror=testError;
function testError(){
arglen=arguments.length;
var errorMsg="参数个数:"+arglen+"个";
for(var i=0;i<arglen;i++){
errorMsg+="\n参数"+(i+1)+":"+arguments[i];
}
alert(errorMsg);
window.onerror=null;
return true;
}
function test(){
error
}
test()
嵌套式回调的解嵌套
JavaScript中最常见的反模式做法是,回调内部再嵌套回调。
function checkPassword(username, passwordGuess, callback) {
var queryStr = 'SELECT * FROM user WHERE username = ?';
db.query(queryStr, username, function (err, result) {
if (err) throw err;
hash(passwordGuess, function(passwordGuessHash) {
callback(passwordGuessHash === result['password_hash']);
});
});
}
这里定义了一个异步函数checkPassword,它触发了另一个异步函数db.query,而后者又可能触发另外一个异步函数hash。它能用,而且简洁明了。但是,如果试图向其添加新特性,它就会变得毛里毛躁、险象环生,比如去处理那个数据库错误,而不是抛出错误、记录尝试访问数据库的次数、阻塞访问数据库,等等。
下面我们换一种写法,虽然这种写法很啰嗦但是可读性更高而且更易扩展。
function checkPassword(username, passwordGuess, callback) {
var passwordHash;
var queryStr = 'SELECT * FROM user WHERE username = ?';
db.query(qyeryStr, username, queryCallback);
function queryCallback(err, result) {
if (err) throw err;
passwordHash = result['password_hash'];
hash(passwordGuess, hashCallback);
}
function hashCallback(passwordGuessHash) {
callback(passwordHash === passwordGuessHash);
}
}
在平时写嵌套时,我们应该尽量避免多层嵌套,不然中间某个地方出错了将会导致你投入更多的时间去debug。
深入理解JS异步编程(一)的更多相关文章
- 理解js异步编程
Promise 背景 javascript语言的一大特点就是单线程,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码,也就是说,同一个时间只能做一件事. 怎么做到异步编程?回调函数.直到no ...
- 深入理解JS异步编程五(脚本异步加载)
异步脚本加载 阻塞性脚本 JavaScript在浏览器中被解析和执行时具有阻塞的特性,也就是说,当JavaScript代码执行时,页面的解析.渲染以及其他资源的下载都要停下来等待脚本执行完毕 浏览器是 ...
- 深入理解JS异步编程四(HTML5 Web Worker)
>Web Workers 是 HTML5 提供的一个javascript多线程解决方案,我们可以将一些大计算量的代码交由web Worker运行而不冻结用户界面. 一:如何使用Worker We ...
- 深入理解JS异步编程二(分布式事件)
PubSub模式 从原生的js角度,我们要监听某事件的方法就是利用addEventListener方法,但是当我们的页面趋于复杂,比如要向某个元素添加多个处理事件,那么就要用一个封装函数汇集多个处理函 ...
- 深入理解JS异步编程三(promise)
jQuery 原本写一个小动画我们可能是这样的 $('.animateEle').animate({ opacity:'.5' }, 4000,function(){ $('.animateEle2' ...
- js异步编程
前言 以一个煮饭的例子开始,例如有三件事,A是买菜.B是买肉.C是洗米,最终的结果是为了煮一餐饭.为了最后一餐饭,可以三件事一起做,也可以轮流做,也可能C需要最后做(等A.B做完),这三件事是相关的, ...
- 前端分享----JS异步编程+ES6箭头函数
前端分享----JS异步编程+ES6箭头函数 ##概述Javascript语言的执行环境是"单线程"(single thread).所谓"单线程",就是指一次只 ...
- JS异步编程 (2) - Promise、Generator、async/await
JS异步编程 (2) - Promise.Generator.async/await 上篇文章我们讲了下JS异步编程的相关知识,比如什么是异步,为什么要使用异步编程以及在浏览器中JS如何实现异步的.最 ...
- JS异步编程 (1)
JS异步编程 (1) 1.1 什么叫异步 异步(async)是相对于同步(sync)而言的,很好理解. 同步就是一件事一件事的执行.只有前一个任务执行完毕,才能执行后一个任务.而异步比如: setTi ...
随机推荐
- Poisson Distribution——泊松分布
老师留个小作业,用EXCEL做不同lambda(np)的泊松分布图,这里分别用EXCEL,Python,MATLAB和R简单画一下. 1. EXCEL 运用EXCEL统计学公式,POISSON,算出各 ...
- Java技术的特点
Java技术是一套完整的IT行业解决方案,其中包含了很多技术.最初是从解决家电设备联网通讯的方案发展起来的,其特点适用于Internet,于是在Internet广泛应用的环境下,迅速发展成为一种计算机 ...
- C# 获取MAC地址
/********************************************************************** * C# 获取MAC地址 * 说明: * 在C#中获取本 ...
- CentOS 6.6 中中文输入法设置
排版比较乱你,参见 https://www.zybuluo.com/Jpz/note/144597 Linux开发环境配置 安装完系统之后,我们需要设置中文输入法,中文输入法是系统自带的,设置步骤如下 ...
- 《C与指针》第六章练习
本章问题 1.如果一个值的类型无法简单的通过观察它的位模式来判断,那么机器是如何知道应该怎样对这个值进行操纵的? answer:The machine doesn't make this determ ...
- svn服务器迁移(生成dump)
首先介绍一下dump文件 一定要进入VisualSVN服务端的安装目录里的bin目录下面,然后再执行svnadmin 相关命令. 不然会出现下图中的“svnadmin不是内部命令或外部命令,也不是可 ...
- asp.net httpmodule 访问页面控件 备忘
用到的时候发现还得找代码,存一个例子方便自己和他人修改: using System; using System.Data; using System.Configuration; using Syst ...
- ABBYY有哪些图像处理选项
ABBYY PDF Transformer+ 这款Ocr图文识别软件提供多种图像处理选项,可提高源图像的质量,便于准确地识别光学字符.我们扫描纸质文档或从图像文件创建 PDF 时,务必选择合适的图像处 ...
- C++模板编程里的主版本模板类、全特化、偏特化(C++ Type Traits)
1. 主版本模板类 首先我们来看一段初学者都能看懂,应用了模板的程序: 1 #include <iostream> 2 using namespace std; 3 4 template ...
- SSIS 基础知识
微软 BI 系列随笔 - SSIS 2012 基础 - SSIS 基础知识 SSIS 介绍 SSIS - SQL Server Integration Services 是用于实现企业级数据集成和数据 ...