JavaScript异步编程(一) 深入理解JavaScript事件
JavaScript异步编程
深入理解JavaScript事件
・事件的调度
JavaScript事件处理器在线程空闲之前不会运行
线程的阻塞
var start = new Date(); // setTimeout和setInterval的计时精度比期望值差 setTimeout(function(){ var end = new Date(); console.log('Time elapsed', end - start, 'ms'); }, 500); while(new Date - start < 1000) {};
结果上看出setTimeout没有使用另一线程
队列
调用setTimeout时,会有一个延时事件排入队列;
输入事件的工作方式完全一样,如单机事件发生时,会有一个单击事件排入队列。但是,单击事件处理器要等到当前所有正在运行的代码均结束后才会执行。
・异步函数类型
两大类:I/O函数和计时函数
异步的I/O函数
Node.js的出现是为了建立一个在某高级语言之上的事件驱动型服务器框架。
JavaScript语言可以完美地实现非阻塞式I/O。
如:
var ajaxRequest = new XMLRequest();
ajaxRequest.open(‘GET’, url);
ajaxRequest.send(null);
// 只需要附加一个事件处理器,随即返回事件队列
ajaxRequest.onreadystatechange = function() {
// …
}
因此,开发者需要做的只是定义一个回调就可以了。
WebKit的console.log是异步的吗?
var a = {name: '1'}; console.log(a); a.name = '2'; console.log(a);
执行后打开控制台结果:
先打开控制台,然后再执行的结果:
原因:
Console是浏览器的控制台所提供的对象,只有在控制台打开时才起作用;
console.log并没有立即拍摄对象快照,只是存储了一个对象的引用;
异步的计时函数
可能为了作动画或模拟;
setTimeout与setInterval(不精确的计时工具)
以上两个函数的缺陷:即使时延设置为0,在浏览器中的执行频率也大约为200次/秒;
原因:HTML规范推行的延时/时隔最小值为4毫秒;
需要更细粒度计时的方案:requestAnimationFrame函数
・异步函数的编写
任何函数只要使用了异步的函数,从上到下都是异步的。
异步函数的特性:非阻塞
非阻塞强调了异步函数的高速度
间或异步的函数
有些函数某些时候是异步的,但其他时候却不然;
例:jQuery的同名函数可用于延迟函数直到DOM已经结束加载;
但DOM如果早已结束了加载,则不存在任何延迟,$的回调将会立即触发;
// application.js
$(function() {
utils.log('Ready');
});
// utils.js
window.utils = {
log: function() {
if(window.console)
console.log.apply(console, arguments);
}
};
<script src="application.js" type="text/javascript"> </script>
<script src="utils.js" type="text/javascript"> </script>
这段代码运行得很好,但前提是浏览器并未从缓存中加载页面(这会导致DOM 早在脚本运行之前就已加载就绪)。如果出现这种情况,传递给$的回调就会在设置utils.log 之前运行,从而导致一个错误。
缓存型异步函数
function runCalculation(formula, callback) {
if (formula in calculationCache) {
return callback(calculationCache[formula]);
};
if (formula in calculationCallbacks) {
return setTimeout(function() {
runCalculation(formula, callback);
}, 0);
};
mathWorker.postMessage(formula);
calculationCallbacks[formula] = callback;
}
公式已经计算完成,于是结果位于calculationCache 中。这种情况下,runCalculation 是同步的。
公式已经发送给Worker 对象,但尚未收到结果。这种情况下,runCalculation 设定了一个延时以便再次调用自身;重复这一过程直到结果位于calculationCache 中为止。
异步递归与回调存储
避免异步递归,尽量采用回调存储。
返值与回调
永远不要定义一个潜在同步而返值却有可能用于回调的函数。
避免使用计时器方法来等待某个会变化的东西。如果同一个函数既返值又运行回调,则请确保回调在返值之后才运行。
・异步错误的处理
大多数JavaScript环境会提供一个有用的堆栈轨迹。
回调内抛出的错误
setTimeout(function A() { setTimeout(function B() { setTimeout(function C() { throw new Error('Something terrible has happened!'); }, 0); }, 0); }, 0);
A和B并未出现在堆栈轨迹中,因为这三个函数都是从事件队列直接运行的;
同理,try/catch语句块不能捕获从异步回调中抛出的错误;
要记住的是,只能在回调内部处理源于回调的异步错误。
windows.onerror 处理器返回true,能阻止浏览器的默认错误处理行为。
避免两层以上的函数嵌套。关键是找到一种在激活异步调用之函数的外部存储异步结果的方式,这样回调本身就没有必要再嵌套了。