深入理解JS异步编程(一)

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/article/details/51516121

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&lt;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。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我分享我快乐

解决IE不支持HTML5的办法

解决IE不支持HTML5的办法,有3种,如下: 第一种方法(原理:识别标签): 引用Google的html5.js文件到head部分,记住一定要是head部分(...

3429
来自专栏前端新视界

关于 devbridge-autocomplete 插件多选操作的实现方法

目前据我所知最好用的 autocomplete 插件就是 jquery-ui 的 autocomplete 以及 devbridge 的 autocomplet...

2098
来自专栏一个会写诗的程序员的博客

正则表达式 删除 Java 代码中的注释

想如何删掉所有java 或xml 中的注释,还在寻找eclipse 中的快捷键了吗,你out了,现在都用正则表达式了、

2064
来自专栏九彩拼盘的叨叨叨

写出好的前端代码不是件容易事

什么样的代码算是好代码? 在我看来,易于维护的代码就是好代码。当然代码还可以从性能,安全等方面来考量。这些不在本文的讨论范围之内。

833
来自专栏IT探索

excel

2. 在excel2007中打开mysql导出utf-8编码的csv文件,避免乱码:

1201
来自专栏九彩拼盘的叨叨叨

在 Sublime 中使用 Vim 指南

Sublime 中自带支持 Vim 的插件,但默认是关闭的。开启方式为:在菜单 Preferences/Settings-User 中设置

972
来自专栏草根专栏

Python数据分析(一): ipython 技巧!

不一定非得使用Jupyter Notebook,试试ipython命令行 安装 ipython 我只试过Windows 10环境下的。 1.安装python安装...

3546
来自专栏快乐八哥

jQuery.unique引发一个血案

项目开发过程中,PM说系统只要在一个特定的浏览器中运行就好,但是在其他的浏览器中不能出现逻辑的错误,所以在开发过程中,前端和后台选择是Chrome浏览器,没有仔...

2178
来自专栏python全栈布道师

使用python读取和写入Excel

4286
来自专栏前端架构

再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

ng-bind 单向数据绑定($scope -> view),用于数据显示,简写形式是 {{}}。

1953

扫码关注云+社区

领取腾讯云代金券