前沿技术交流,资源分享
公众号内回复:全栈
????全栈资源????
前言
博客地址:www.illgo.cn
在Javascript这样类型的语言中编程最重要但最常被人误解的部分之一,就是如何控制在一段时间内程序的行为次序.同时,JavaScript中的异步,也经常被人和并行搞混.今天,我们来谈一下JavaScript中的异步.
自JS开始以来,异步编程一直存在.然而,但是大多数JS开发人员从未真正仔细考虑过在程序中如何以及为何出现问题,也没有去探索各种其他处理方法。 比较好的方法一直是稀里糊涂的使用回调函数.到今天为止,许多人会坚持认为回调使用起来就已经绰绰有余了.
什么是异步?
首先,一段JavaScript程序是由多个块(chunk)组成的,最常见的块就是function–函数.
我们把一段时间内,程序要执行的任务分为两部分: 1.执行部分(现在执行的),2.等待部分(剩下的将来要执行的).而我们面临的问题是,当现在执行部分执行完后,程序并不是严格地立马去完成等待部分.换句话就是,这些块是异步执行的.我们不会像预期的那样阻塞地完成一个接一个的任务.
例如:
//ajax是某些JavaScript框架(如:jQurey)中实现Ajax的函数
let data = ajax( "http://some.url.1" );
//控制台输出data内容
console.log(data)
如果运行这段JavaScript代码会发现,打印出来的data通常没有我们想要的ajax请求结果.
这是因为,Ajax请求并不是同步(synchronously,相对于异步asynchronously)完成的,当执行console.log()
的时候,我们想要的data还没有返回.我们想要的其实是ajax(...)
函数能够阻塞,一直到请求结果返回,最简单的解决方法就是回调(callback).
//回调方式的一个示例,具体回调方式根据具体来定.
ajax( "http://some.url.1", function myCallbackFunction(data){
console.log( data );
} );
这时我们会发现,data就是我们想要的了.
注意:我们是可以同步地请求Ajax的,比如:jQurey中的ajax()
将async: false
加入设置.但是这样做的后果就是浏览器的UI操作(按钮,滚动等)以及用户交互等都会被阻塞等待锁死.我们应该避免这种情况,一团乱麻的回调函数也不应成为使用同步Ajax的理由.
记下来我们再考虑另一个例子帮助理解:
function now() {
return 21;
}
function later() {
answer = answer * 2;
console.log( "answer:", answer );
}
var answer = now();
setTimeout( later, 1000 ); // answer: 42
我们再用刚才的思路去理解这个程序:分为两个部分:执行部分,等待部分.
执行部分是:
//回调方式的一个示例,具体回调方式根据具体来定.
ajax( "http://some.url.1", function myCallbackFunction(data){
console.log( data );
} );
等待部分就是later()
中的内容:
answer = answer * 2;
console.log( "answer:", answer );
执行部分会立刻执行,而setTimeout(...)
会设定一个事件(timeout事件),在1000ms后执行later()
.就像这样,每当我们在function
中写一段代码,并让它在事件(timer,鼠标事件,Ajax响应等)响应后执行,我们就创造了一个等待部分,也就是在程序中使用了异步.
Event Loop
虽然我们在这里谈异步,但是,直到ES6*,JavaScript本身并没有内置异步的概念.听起来很震惊,但事实确实是这样的.我们会问:那我们讨论的异步是怎么实现的呢??
我们都知道的是JavaScript引擎从来不是独立执行,总要依赖于一个环境,比如,我们最熟悉的web浏览器.以及服务器上的Node.js.这些环境会用一个机制来随时间使用JavaScript引擎处理我们的多个程序块,这个机制我们管它叫Event Loop.
换句话说,JavaScript引擎并不知道什么时候执行,而是被执行环境的线程来安排处理哪些程序块,执行环境根据事件来调度JavaScript引擎处理.
那么什么是Event Loop呢?
我们通过一段伪代码来了解它的概念:
//eventLoop是事件排成的先进先出的队列(queue)
var eventLoop = [ ];
var event;
while(true) {
// 处理完一个事件
if (eventLoop.length > 0) {
// 获取队列中的下个动作
event = eventLoop.shift();
// 处理刚才取出的动作
try {
event();
}
catch (err) {
reportError(err);
}
}
}
我们通过这段伪代码大体了解它的机制.我们有一个循环,循环的每一个迭代中,如果在等待队列中存在事件,就会被取出并处理,event()
就是各种回调函数.
因此,到这儿我们就可以明白了,setTimeout(..)
不是把设定好的回调函数安排到event loop
中,而是将一个计时器(timer)安排在event loop
中,当计时器到期,执行环境将回调推入event loop,
这样,在将来某个时间会被取出并执行.
假如,现在event loop中已经存在20个等待的成员,那么这个回调就应该等待,通常没有方法能将他移动到队列头部,让他立马执行.这样就产生了,哪怕用了setTimeout(..),指定的回调并不会在指定时间后立即执行的现象,当然也不会提前,至于是否要等待,等待多久,要根据具体情况来说.
注意:之所以说是”直到ES6”,是因为ES6引入了Promise机制,ES6通过Promise将event loop的工作机制纳入到了JavaScript引擎的工作范围,而不只是执行环境的工作.关于Promise以后有机会再谈.
并行
有一个常见的现象就是,人们经常把”异步”和”并行”混为一谈,其实他们大不相同.”异步”,指的是执行部分和等待部分中间有时间差,并不是立即执行.而并行则是指一起执行.
并行计算中最常见的单位是进程(process)和线程(thread),进程和线程之间可以是独立执行,也可以在一个处理器中,或者一台电脑中同时执行.通常,多个线程可以共享单个进程的内存.
相比之下,event loop是将一个工作分解成多个任务,并组成队列串行执行,不能并行访问和更改共享的内存.它的并行性和”串行性”可以在不同线程下的event loop上体现(一个线程可以创立一个event loop,不同线程下的event loop具有并行性,单个event loop具有串行性).
并行地执行线程和异步地交错处理事件在粒度级别上有着很大的不同.线程是表达式操作级别,而异步是函数级别。
如有收获,请打赏作者
作者:Illgo
博客地址:www.illgo.cn
通知:欢迎大家踊跃投稿,打赏归你哦!
—— End ——
本文分享自微信公众号 - Java后端(web_resource)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。