刚开始我区分不了异步任务和异步加载,感觉两者是很像的东西。
随着时间发现两者是形容不同的事物。
比如异步任务,讲的是event loop的执行规则,是怎么让function异步执行。
而异步加载则讲的更多的是在渲染页面时,如何让浏览器在下载JS的同时,还会进行后续页面的处理。
一、JS异步任务
JS是单线程语言,如果都按照顺序进行,往往会出现浏览器无响应的情况,所以就需要异步的形式。
1.JS中任务的形式
JS中所有的任务可以分为两种:同步任务和异步任务。
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
这种模式的好处是实现起来比较简单,执行环境相对单纯。
坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。异步任务:不进入主线程,而进入任务队列中的任务,只有任务队列通知主线程,某个异步任务可以执行了,这个任务才会进入主线程执行。
2.异步的解决方案
1. Promise
function ajax(url){
return new Promise(function(resolve,reject){
var xhr=new XMLHttpRequest();
xhr.onload=function(){
resolve(this.responseText);
};
xhr.onerror=reject;
xhr.open('GET',url);
xhr.send();
});
}
ajax('/echo/json')
.then(function(result){...})
.then(function(){...})
.catch(function(){...});
Promise代表了一个异步操作,可以将异步对象和回调函数脱离开来,通过.then方法在这个异步操作上绑定回调函数,Promise可以让我们通过链式调用的方法去解决回调嵌套的问题,而且由于promise.all这样的方法存在,可以让同时执行多个操作变得简单。
promise对象存在三种状态:
1)Fulfilled:成功状态
2)Rejected:失败状态
3)Pending:既不是成功也不是失败状态,可以理解为进行中状态
promise对象的两个重要方法:resolve/reject
1).resolve方法可以使Promise对象的状态改变为成功,同时传递一个参数用于后续成功后的操作。
2).reject方法可以将Promise对象的状态改变为失败,同时将错误信息传递到后续错误处理的操作。
3).then可以使用链式调用,原因在于:每一次执行该方法时总会返回一个Promise对象。
另外,在then的函数当中的返回值,可以作为后续操作的参数(例如:.then(return a).then(console.log(a+b)))
promise异常处理
如果代码异步操作抛出错误,会调用catch方法指定的回调函数,处理这个错误,而且then方法指定的回调函数,如果运行中抛出错误,也会被catch捕获。Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止,也就是说,错误总是会被下一个catch语句捕获。
理解Promise用法的关键点:
- 1.then方法是Promise实例的方法,即Promise.prototype上的,它的作用是为Promise实例添加状态改变时的回调函数,这个方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
- 2.链式中的第二个then开始,它们的resolve中的参数,是前一个then中resolve的return语句的返回值。
- 3.关于执行顺序:Promise在实例化的时候就会执行,也就是如果Promise的实例化语句中函数console.log输出语句,它会比then中的先执行。Promise.all中传入的Promise对象的数组(假设为p1、p2),即使p2的运行速度比p1快,Promise.all方法仍然会按照数组中的顺序将结果返回。
Promise的缺点:
1.当处于未完成状态时,无法确定目前处于哪一阶段。
2.如果不设置回调函数,Promise内部的错误不会反映到外部。
3.无法取消Promise,一旦新建它就会立即执行,无法中途取消。
2.async/await
很多人说async/await是异步编程的终极解决方案、
JavaScript 的 async/await 实现,离不开 Promise。
async:定义异步函数
1)自动把函数转换为Promise
2)当调用异步函数时,函数返回值会被resolve处理
3)异步函数内部可以使用await
await:暂停异步函数的执行
1)当使用在Promise前面时,await等待Promise完成,并返回Promise的结果
2)await只能和Promise一起使用,不能和callback一起使用
3)await只能用在async函数中
async/await并不会取代promise,因为async/await底层依然使用promise。
下面有两个例子:
async function getABC(){
let A = await getValueA(); // getValueA 花费 2 秒
let B = await getValueB(); // getValueA 花费 4 秒
let C = await getValueC(); // getValueA 花费 3 秒
return A*B*C
}
每次遇到 await 关键字时,Promise 都会停下在,一直到运行结束,所以总共花费是 2+4+3 = 9 秒
async function getABC() {
// Promise.all() 允许同时执行所有的异步函数
let results = await Promise.all([ getValueA, getValueB, getValueC ]);
return results.reduce((total,value) => total * value);
}
函数总耗时为 4 秒(getValueB 的耗时)。
Async 的价值在于用写同步的方式写异步,1避免了阻塞,2必免写回调
二、异步加载
JS在默认情况下是以同步模式(又称阻塞模式)加载的,浏览器对于代码请求的资源都是瀑布式的加载,而不是阻塞式的,但是JS的执行总是阻塞式的。
这会引起什么问题?如果在页面中加载一些JS,但其中某个请求迟迟得不到响应,位于此JS后的JS将无法执行,同时页面渲染也不能继续。
异步加载又被称为非阻塞加载,浏览器在下载JS的同时,还会进行后续页面处理。
1.如何实现JS异步加载
- 动态生成<script>标签:这种方法可以兼容所有浏览器。
- async属性
async是HTML5的新属性,该属性规定一旦脚本可用,则会异步执行(一旦下载完毕就会立刻执行),需要注意的是,async属性仅适用于外部脚本。
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' async="async"></script>
- defer属性
defer属性规定是否对该脚本的执行进行延迟,知道页面加载为止。
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' defer="defer"></script
来分析一下async和defer属性:
a.如果没有async属性和defer属性,那么浏览器会立即执行当前的JS脚本,阻塞后面的脚本;
b.如果有async属性,加载和渲染后续文档的过程和当前JS的加载和执行并行进行,它是乱序执行的,不管你声明的顺序如何,只要它加载完了就会执行。
c.如果有defer属性,加载后续文档元素的过程和JS的加载时并行进行的,但是JS的执行是在所有元素解析完成之后进行的,而且它是按照加载顺序执行脚本的。
简言之:async是乱序;defer是顺序。
-$(document).ready
需要引用jquery
兼容所有浏览器