“同步请求”,“异步请求”相信这两词在程序猿的世界中频频出现,究竟是词性的妖娆,仍是撸代码的基础要求,下面直接分享本人学习的好东西,保证让你深刻浅出,爽得不要不要的。ajax
1、单线程编程
咱们常说的“JavaScript是单线程的”。所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个,通常称它为主线程。可是实际上还存在其余的线程,例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程等等。这些线程可能存在于JS引擎以内,也可能存在于JS引擎以外,在此咱们不作区分,不妨称它们为工做线程吧。异步
2、同步和异步函数
假设存在一个函数A:学习
A(args...){url
...spa
};线程
同步:若是在函数A返回的时候,调用者就可以获得预期的结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。对象
例:一、 Math.sqrt(2); blog
二、console.log('这是咱们常常用的好东西');
第一个函数返回时,就拿到了咱们预期的返回值,2的平方;第二个函数返回时,就能看到预期的结果,咱们在控制台打印了一个字符串,因此这两个函数都是同步的。
异步:若是在函数A返回的时候,调用者还不能立刻获得预期的结果,而是须要在未来经过必定的手段获得,那么这个函数就是异步的 。
例:fs.readFile( 'foo.txt', 'utf8', function(err, data) {
console.log(data);
});
在上面的栗子中,咱们但愿经过fs.readFile函数读取文件foo.txt中的内容,并打印出来。可是在fs.readFile函数返回值以前,咱们指望的结果并不会发生,而是要等到文件所有读取完成以后(若是文件很大的话,可能须要的时间会长一点),这就是异步。
下面以AJAX请求为例,来看一下同步和异步的区别:
异步AJAX:
主线程:“你好,AJAX线程,请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”
AJAX线程:“好的,主线程。我立刻去发,可是可能须要花点时间呢,你能够先去忙别的。”
主线程:“谢谢,你拿到响应以后告诉我一声哈。”
(注:主线程和AJAX线程都各自同步干活,一段时间后,主线程就能收到AJAX线程响应的通知,而后继续执行相应的工做)。
同步AJAX:
主线程:“你好,AJAX线程,请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”
AJAX线程:“......”
主线程:“喂,AJAX线程,你怎么不说话?”
AJAX线程:“......”
主线程:“喂!喂喂喂!”
AJAX线程:“......”
(一炷香的时间后)
主线程:“喂!求你说句话吧!”
AJAX线程:“主线程,很差意思,我在工做的时候不能说话,你的请求已经发完了,拿到响应的数据了,给你。”
(注:同步AJAX的主线程和AJAX线程不能同步干活,只能等AJAX线程干完活拿到响应后,只线程才能接着干活)
正是因为JavaScript是单线程,而异步容易实现非阻塞,因此在JavaScript中对于耗时的操做或者时间不肯定的操做,实用异步就成了必然的选择。
3、异步过程的构成要素
从上文能够看出,异步函数实际上很快就调用完成了,可是后面还有工做线程执行异步任务,通知主线程,主线程调用回调函数等不少步骤。咱们把整个过程叫作异步过程,异步函数的调用在整个异步过程当中只是一小部分。
总结一下,一个异步过程的整个过程:主线程发一块儿一个异步请求,相应的工做线程接收请求并告知主线程已收到通知(异步函数返回);主线程能够继续执行后面的代码,同时工做线程执行异步任务;工做线程完成工做后,通知主线程;主线程收到通知后,执行必定的动做(调用回调函数)。
异步函数一般具备如下的形式:A(args..., callbackFn);
它能够叫作异步过程的发起函数,或者叫作异步任务注册函数。args是这个函数须要的参数,callbackFn(回调函数)也是这个函数的参数,可是它比较特殊因此单独列出来。因此,从主线程的角度看,一个异步过程包括下面两个要素:
一、发起函数(或叫注册函数)A;
二、回调函数callbackFn;
它们都是主线程上调用的,其中注册函数用来发起异步过程,回调函数用来处理结果。
举个具体的栗子:
setTimeout(function,1000);
其中setTimeout就是异步过程的发起函数,function是回调函数。
注:前面说得形式A(args..., callbackFn)只是一种抽象的表示,并不表明回调函数必定要做为发起函数的参数,例如:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回调函数
xhr.open('GET', url);
xhr.send(); // 发起函数
发起函数和回调函数是分离的。
4、消息队列和事件循环
上文讲到,异步过程当中,工做线程在异步操做完成后须要通知主线程。那么这个通知机制是怎样实现的呢?答案是利用消息队列和事件循环。
工做线程将消息放到消息队列,主线程经过事件循环过程去取消息。
消息队列:消息队列是一个先进先出的队列,它里面存放这各类消息。
事件循环:事件循环是指主线程重复从消息队列中取消息,执行的过程。
实际上,主线程只会作一件事情,就是从消息队列里面取消息、执行消息、再取消息、再执行消息。当消息队列为空时,就会等待直到消息队列变成非空。并且主线程只有在将前面的消息执行完成后,才会去去下一个消息。这种机制就叫作事件循环机制,取一个消息并执行的过程叫作一次循环。
事件循环代码表示大概是这样的:
while(true){
var message = queue.get();
execute(message);
}
那么,消息队列中放的消息具体是什么?消息的具体结构固然跟具体实现有关,可是为了简单起见,咱们能够认为:消息就是注册异步任务时添加的回调函数。
再次以异步AJAX为例,假设存在以下的代码:
$.ajax('http://baidu.com',function(resp){
console.log('我是响应',resp);
})
// 其余代码
...
...
主线程在发起AJAX请求后,会继续执行其余代码,AJAX线程负责请求http://baidu.com,拿到响应后,它会把响应封装成一个JavaScript对象,而后构造一条消息:
var message = function(){
callbackFn(response);
}
其中callbackFn就是就是前面代码中获得成功响应时的回调函数。
主线程在执行完当前循环中的全部代码后,就会到消息队列取出这条消息(也就是message函数),并执行它。到此为止,就完成了工做线程对主线程的通知,回调函数也就获得了执行。若是一开始主线程就没有提供回调函数,AJAX线程在收到HTTP响应后,也就不必通知主线程,从而也不必往消息队列放消息。
用图表示这个过程就是:
从上文中咱们也能够获得这样一个明显的结论,就是:异步过程的回调函数,必定不在当前这一轮事件循环中执行。
五. 异步与事件
上文中说的“事件循环”,为何里面有个事件呢?那是由于:消息队列中的每条消息实际上都对应着一个事件。上文中一直没有提到一类很重要的异步过程:DOM事件。
举个栗子:
var button = document.getElement('#btn');
button.addEventListener('click', function(e) {
console.log();
});
从事件的角度来看,上述代码表示:在按钮上添加了一个鼠标单击事件的事件监听器;当用户点击按钮时,鼠标单击事件触发,事件监听器函数被调用。
从异步过程的角度看,addEventListener函数就是异步过程的发起函数,事件监听器函数就是异步过程的回调函数。事件触发时,表示异步任务完成,会将事件监听器函数封装成一条消息放到消息队列中,等待主线程执行。
事件的概念实际上并非必须的,事件机制实际上就是异步过程的通知机制。我以为它的存在是为了编程接口对开发者更友好。另外一方面,全部的异步过程也均可以用事件来描述。例如:setTimeout能够当作对应一个时间到了!的事件。前文的setTimeout(fn, 1000);能够当作:
timer.addEventListener('timeout', 1000, fn);
六. 生产者与消费者
从生产者与消费者的角度看,异步过程是这样的:工做线程是生产者,主线程是消费者(只有一个消费者)。工做线程执行异步任务,执行完成后把对应的回调函数封装成一条消息放到消息队列中;主线程不断地从消息队列中取消息并执行,当消息队列空时主线程阻塞,直到消息队列再次非空。
七. 总结一下
最后再用一个生活中的例子总结一下同步和异步:在公路上,汽车一辆接一辆,有条不紊的运行。这时,有一辆车坏掉了。假如它停在原地进行修理,那么后面的车就会被堵住无法行驶,交通就乱套了。幸亏旁边有应急车道,能够把故障车辆推到应急车道修理,而正常的车流不会受到任何影响。等车修好了,再从应急车道回到正常车道便可。惟一的影响就是,应急车道用多了,原来的车辆之间的顺序会有点乱。
这就是同步和异步的区别。同步能够保证顺序一致,可是容易致使阻塞;异步能够解决阻塞问题,可是会改变顺序性。改变顺序性其实也没有什么大不了的,只不过让程序变得稍微难理解了一些。