深入理解Javascript-JS异步和事件循环
前言
想想接触js已经很久了,也没有写过一篇关于js异步的文章。于是今天准备写一写js的异步,希望对想了解的同行们有所帮助
首先需要说明的是这里讨论的是浏览器里的js,而不是服务端上的js,比如node。
node和浏览器中的js的异步还是多少有些不同的,如果想了解node中的异步或者事件循环,可以参考我的另一篇文章Node 事件循环
正文
Sync
众所周知的js是一种单线程的语言,单线程,如果说它只能一次做一件事情,那也是再适合不过了。比如我们去请求一个远程的资源,这里我们是同步请求,假设有一个find函数作为拉取远程资源的函数,它接收一个url作为参数,假设从建立连接到拉取成功需要10s
let src = find('http://xxx/xxx/x/x/x');
//wait 10s
console.log(src)
上面已经说了js是单线程的(虽然现在有技术能让js运用多线程技术来提高计算能力,比如web worker,但是需要注意的是,web worker还是浏览器创建的线程,根本来说,js是单线程是不会变的),js运行在各大浏览器的解释器里,在chrome里,js运行在v8解释器里(得益于v8,js才能从前端领域脱离,在后端领域也颇具影响力)。
来看看上述代码的执行过程。
当前栈里只有一个js程序的入口。
接下来是find函数执行
这里会进行阻塞,它不会立即弹出栈,等待10s后才会弹出栈。
最后是打印src
打印完毕后弹出
最后弹出main()
整个程序执行结束。
这里会发现一个致命的问题,由于js是单线程的,拉取网络资源会在这里阻塞,阻塞的这段时间,js什么事情也不能做,只能在这里等待,等待资源拉取完毕,如果是我,上一个网站,我需要获取一些资源,按钮点击后10s内什么事情都干不了,我会马上离开。。谁不是呢?对吧。这是一种极差的体验。为了弥补这个缺陷,就要介绍一下js里的异步了。
ASync
异步的js充斥着我们的代码,比如经常使用的setTimeout,setInterval等,还有经常写的AJAX,这些都是异步的。通俗的讲,比如我需要吃午饭,但是又不想出门,于是我打电话给商家订外卖,等外卖到的时候,外卖小哥会通知我去取外卖,但是等待的这段时间内,我可以做任何我想做的事情,比如我可以睡一觉,看会儿电视,玩会儿手机都行,这就是异步!我只需发出我订外卖的这条消息,而不必一直阻塞等待外卖小哥打电话给我,如果是同步的话,在现实生活中看来就很傻了,如果是同步,那我打电话订完外卖之后,那我什么事情都做不了,只能在那里像个呆子一样的傻等,等待外卖小哥打电话给我(终于不用傻啦吧唧的等待了)于是下楼取外卖。
回到js,如果是异步,那些异步操作不会阻塞,而是会马上返回,然后等待执行完毕,通过你传递的callback的引用,调用callback,进行数据处理。
比如上面的find函数如果是异步的,可以写成下面这样:
asyncFind('http://xxx/xxx/x/x/x',(err, data) => {
if(err) throw err;
console.log(data)
})
回调函数一般会接受两个参数,第二个参数是执行完毕后返回的资源,第一个参数是错误对象。假如此次拉取过程中有错误发生,会创建一个错误对象传入回调的第一个参数。
如果是同步的话,捕获错误只能用try catch来进行捕获了
try{
let src = find('http://xxx/xxx/x/x/x');
console.log(src)
}
catch(e){
throw e;
}
现在回到异步的状态。我们看一下异步是如何战胜同步的(虽然有些时候,我们需要同步的状态),在asyncFind执行后,会立即返回,那么这样,当然不会阻塞。比如下面的代码