JavaScript异步编程笔记
栏目: JavaScript · 发布时间: 6年前
内容简介:事件!事件到底是怎么工作的?JavaScript出现了多久,对JavaScript异步事件模型就迷惘了多久。迷惘导致bug,bug导致加班,加班导致没时间撩妹子,这不是js攻城狮想要的生活。==为了妹子,一定要理解好JavaScript事件==先来看一个烂大街的面试题
事件!事件到底是怎么工作的?JavaScript出现了多久,对JavaScript异步事件模型就迷惘了多久。迷惘导致bug,bug导致加班,加班导致没时间撩妹子,这不是js攻城狮想要的生活。
==为了妹子,一定要理解好JavaScript事件==
JavaScript事件的运行
先来看一个烂大街的面试题
for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i); }, 200); } // 3 3 3 复制代码
为什么输出的全都是3??
- 只有一个名为i的变量,其作用域由声明语句var i定义(var定义的i作用城不是循环内部,而是扩散至其所在的整个作用域)。
- 循环结束后,i++还在执行,直到i<3返回false为止。
- JavaScript事件处理器在线程空闲之前不会运行。
再来看一段代码
var start = new Date(); setTimeout(function () { console.log("回调触发间隔1:", new Date() - start, "ms"); }, 500); setTimeout(function () { console.log("回调触发间隔2:", new Date() - start, "ms"); }, 800); setTimeout(function () { console.log("回调触发间隔3:", new Date() - start, "ms"); }, 1100); while (new Date() - start < 1000) { } 回调触发间隔1: 1002 ms 回调触发间隔2: 1003 ms 回调触发间隔3: 1101 ms 复制代码
最终输出的毫秒数在不同环境下会有所不同,但是最终数字肯定至少是1000,因为在while循环阻塞了线程(JavaScript是单线程运行),在循环结束运行之前,setTimeout的处理器不会被触发。
为什么会这样??
调用setTimeout的时候,会有一个延时事件排入队列,然后setTimeout调用之后的代码运行,然后之后之后的代码运行,然后之后之后之后...
直到再也没有要运行的代码,这个时候队列事件才会被记起。
如果队列事件中至少有一个事件适合被触发(如前面代码中的500和800毫秒的延时事件),则JS线程会挑选一个事件,并调用事件的处理器(回调函数)。
执行完毕后,回到事件队列中,继续下一个...
也就是说:setTimeout 只能保证在指定的时间后将任务(需要执行的函数)插入任务队列中等候,但是不保证这个任务在什么时候执行。
大家可以猜想下,用户单击一个已附加有单击事件处理器的DOM元素时,程序是如何工作的???
- 用户单击一个已附加有单击事件处理器的DOM元素时,会有一个单击事件排入队列。
- 该单击事件处理器要等到当前所有正在运行的代码均已结束后(可能还要等其他此前已排队的事件也依次结束)才会执行。
恩,用专业点的术语来说,就是事件循环,js不断的从队列中循环取出处理器运行。
所以,setTimeout(fn,0)只是指定某个任务在主线程空闲时,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。
异步函数的类型
JavaScript提供的异步函数分为两类:I/O函数、计时函数
最为常见的异步I/O模型是ajax,它是网络IO的一种,在nodejs中最为常见的是文件IO。
最为常见的异步计时函数为setTimeout与setInterval,除了前面的示例,这两个函数还存在一些无法弥补的精度问题。
看下如下两段代码:
var fireCount = 0; var start = new Date(); var timer = setInterval(function () { if (new Date() - start > 1000) { clearInterval(timer); console.log(fireCount); return; } fireCount++; },0); // node环境输出:860 // chrome环境输出:252 var fireCount = 0; var start = new Date(); var flag = true; while (flag) { if (new Date() - start > 1000) { console.log(fireCount); flag = false; } fireCount++; } // node环境输出:4355256 // chrome环境输出:4515852 复制代码
为什么???
以下信息引用自网络
事实上HTML5标准规定setTimeout的最短时间间隔是4毫秒;setInterval的最短间隔时间是10毫秒。
在此之前,老版本的浏览器都将setTimeout最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16.6毫秒执行一次(大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升)。这时使用requestAnimationFrame()的效果要好于setTimeout()。
nodejs提供了更细粒度的立即异步执行函数,process.nextTick,setImmediate
浏览器提供了一个新的函数requestAnimationFrame函数,它允许以60+帧/秒的速度运行JavaScript动画;另一方面,它也可以避免后台选项卡运行这些动画,节约CPU周期。详情
console.log是异步吗? 在nodejs中是严格的同步函数,而在浏览器端,则依赖具体浏览器的实现,根据测试,基本是同步!!
什么是异步函数:函数会导致将来再运行另一个函数,而另一个函数取自于事件队列(我们一般称为回调)。异步函数一般满足下面的模式。
var functionHasReturned=false; asyncFunction(){ console.log(functionHasReturned); // true } functionHasReturned=true; 复制代码
异步的错误处理
JavaScript中也有try/catch/finally,也存在throw,如果在一次异步操作中抛出错误,会发生什么??
下面看两个《async javascript》书中的例子:
代码1:
function getObj(str){ return JSON.parse(str); } var obj = getObj("{"); 复制代码
在node下运行,输出的错误堆栈信息:
undefined:1 { SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) at getObj (/home/xingmu/ws/practice/myapp/test/test.js:2:14) at Object.<anonymous> (/home/xingmu/ws/practice/myapp/test/test.js:4:11) at Module._compile (module.js:652:30) at Object.Module._extensions..js (module.js:663:10) at Module.load (module.js:565:32) at tryModuleLoad (module.js:505:12) at Function.Module._load (module.js:497:3) at Function.Module.runMain (module.js:693:10) at startup (bootstrap_node.js:188:16) 复制代码
代码2:
setTimeout(function a(){ setTimeout(function b(){ setTimeout(function c(){ throw new Error("我犯错误了,快来抓我!"); },0); },0); },0); 复制代码
输出:
/home/xingmu/ws/practice/myapp/test/test.js:4 throw new Error("我犯错误了,快来抓我!"); ^ Error: 我犯错误了,快来抓我! at Timeout.c [as _onTimeout] (/home/xingmu/ws/practice/myapp/test/test.js:4:10) at ontimeout (timers.js:482:11) at tryOnTimeout (timers.js:317:5) at Timer.listOnTimeout (timers.js:277:5) 复制代码
为什么代码2输出的错误堆栈信息只有c ?
因为在运行时,c是从队列中取出来的,而这个时候a和b还在队列中,并不知道c运行出错了。
下面再看一段代码:
try{ setTimeout(function(){ throw new Error("我犯错误了,快来抓我!"); },0); }catch(e){ console.log(e); console.log("抓到你了!"); }finally{ console.log("我是终结者!"); } 复制代码
输出信息:
我是终结者! /home/xingmu/ws/practice/myapp/test/test.js:3 throw new Error("我犯错误了,快来抓我!"); ^ Error: 我犯错误了,快来抓我! at Timeout._onTimeout (/home/xingmu/ws/practice/myapp/test/test.js:3:9) at ontimeout (timers.js:482:11) at tryOnTimeout (timers.js:317:5) at Timer.listOnTimeout (timers.js:277:5) 复制代码
从这里可以看出,try/catch块只会捕获setTimeout函数自身内部发生的错误,而setTimeout的回调是异步运行的,即使抛出错误,也无法捕获。
所以说对异步执行的函数,使用try/catch块并不能达到我们想要的效果, 那么对于异步回调的错误该怎么处理呢??
下面来看下,在nodejs的API中比较常见的错误处理模式:
var fs = require("fs"); fs.readFile("abc.text", function (err, data) { if (err) { console.log(err); return; } console.log(data.toString("utf8")); }); // { Error: ENOENT: no such file or directory, open 'abc.text' errno: -2, code: 'ENOENT', syscall: 'open', path: 'abc.text' } 复制代码
在nodejs中,类似这样的API非常多,在回调函数中,第一个参数总是接收一个错误,这样就可以让回调函数自己决定怎么处理这个错误。
而在浏览器中,我们最熟悉的回调错误处理模式是像jquery中的ajax一样,针对成功和失败,各定义一个单独的回调:
$.ajax({ type:'POST', url:'/data', data: $('form').serialize(), success:function(response,status,xhr){ //dosomething... }, error:function (textStatus) {//请求失败后调用的函数 //dosomething... } }); 复制代码
不管是那个一个运行环境,对于异步的错误处理有一点是一致的: 只能在回调的内部处理源于回调的错误。
未捕获异常的处理
是的,总会有意想不到的错误发生,这时候该怎么处理??
- 浏览器环境中,我们经常可以在浏览器控制台看到很多未捕获的错误信息,在开发环境这些信息可以帮助我们调试,如果想修改这种行为,可以给window.error添加一个处理器,用来全局处理未捕获异常。
window.onerror = function(error){ // do something // 比如向服务器报告出现的未捕获异常 // 比如给用户统一的消息处理 // return true; 返回true,可以阻止浏览器的默认行为,彻底忽略所有的错误 } 复制代码
看一段示例代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> try { setTimeout(function () { throw new Error("我犯错误了,快来抓我!"); }, 0); } catch (e) { console.log(e); console.log("抓到你了!"); } finally { console.log("我是终结者!"); } window.onerror = function (error) { alert("页面出错了"); // do something other return true; }; </script> </body> </html> 复制代码
- node环境中,有domain和process.onuncaughtexception两种方式来处理未捕获异常,但是后端的处理比较复杂,javascript作为一个单线程程序,对于异常的处理更要慎重。
恩,意思就是我也没有最好的方案。。。
当然很多 工具 也可以帮我们简化处理,比如pm2,会自动重启挂掉的线程
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Go Web编程
谢孟军 / 电子工业出版社 / 2013-6-1 / 65.00元
《Go Web编程》介绍如何用Go语言进行Web应用的开发,将Go语言的特性与Web开发实战组合到一起,帮读者成功地构建跨平台的应用程序,节省Go语言开发Web的宝贵时间。有了这些针对真实问题的解决方案放在手边,大多数编程难题都会迎刃而解。 在《Go Web编程》中,读者可以更加方便地找到各种编程问题的解决方案,内容涵盖文本处理、表单处理、Session管理、数据库交互、加/解密、国际化和标......一起来看看 《Go Web编程》 这本书的介绍吧!