剛開始我區(qū)分不了異步任務和異步加載,感覺兩者是很像的東西。
隨著時間發(fā)現(xiàn)兩者是形容不同的事物。
比如異步任務,講的是event loop的執(zhí)行規(guī)則,是怎么讓function異步執(zhí)行。
而異步加載則講的更多的是在渲染頁面時,如何讓瀏覽器在下載JS的同時,還會進行后續(xù)頁面的處理。
一、JS異步任務
JS是單線程語言,如果都按照順序進行,往往會出現(xiàn)瀏覽器無響應的情況,所以就需要異步的形式。
1.JS中任務的形式
JS中所有的任務可以分為兩種:同步任務和異步任務。
同步任務:在主線程上排隊執(zhí)行的任務,只有前一個任務執(zhí)行完畢,才能執(zhí)行后一個任務。
這種模式的好處是實現(xiàn)起來比較簡單,執(zhí)行環(huán)境相對單純。
壞處是只要有一個任務耗時很長,后面的任務都必須排隊等著,會拖延整個程序的執(zhí)行。常見的瀏覽器無響應(假死),往往就是因為某一段Javascript代碼長時間運行(比如死循環(huán)),導致整個頁面卡在這個地方,其他任務無法執(zhí)行。異步任務:不進入主線程,而進入任務隊列中的任務,只有任務隊列通知主線程,某個異步任務可以執(zhí)行了,這個任務才會進入主線程執(zhí)行。
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代表了一個異步操作,可以將異步對象和回調(diào)函數(shù)脫離開來,通過.then方法在這個異步操作上綁定回調(diào)函數(shù),Promise可以讓我們通過鏈式調(diào)用的方法去解決回調(diào)嵌套的問題,而且由于promise.all這樣的方法存在,可以讓同時執(zhí)行多個操作變得簡單。
promise對象存在三種狀態(tài):
1)Fulfilled:成功狀態(tài)
2)Rejected:失敗狀態(tài)
3)Pending:既不是成功也不是失敗狀態(tài),可以理解為進行中狀態(tài)
promise對象的兩個重要方法:resolve/reject
1).resolve方法可以使Promise對象的狀態(tài)改變?yōu)槌晒?,同時傳遞一個參數(shù)用于后續(xù)成功后的操作。
2).reject方法可以將Promise對象的狀態(tài)改變?yōu)槭。瑫r將錯誤信息傳遞到后續(xù)錯誤處理的操作。
3).then可以使用鏈式調(diào)用,原因在于:每一次執(zhí)行該方法時總會返回一個Promise對象。
另外,在then的函數(shù)當中的返回值,可以作為后續(xù)操作的參數(shù)(例如:.then(return a).then(console.log(a+b)))
promise異常處理
如果代碼異步操作拋出錯誤,會調(diào)用catch方法指定的回調(diào)函數(shù),處理這個錯誤,而且then方法指定的回調(diào)函數(shù),如果運行中拋出錯誤,也會被catch捕獲。Promise對象的錯誤具有“冒泡”性質(zhì),會一直向后傳遞,直到被捕獲為止,也就是說,錯誤總是會被下一個catch語句捕獲。
理解Promise用法的關(guān)鍵點:
- 1.then方法是Promise實例的方法,即Promise.prototype上的,它的作用是為Promise實例添加狀態(tài)改變時的回調(diào)函數(shù),這個方法的第一個參數(shù)是resolved狀態(tài)的回調(diào)函數(shù),第二個參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)。
- 2.鏈式中的第二個then開始,它們的resolve中的參數(shù),是前一個then中resolve的return語句的返回值。
- 3.關(guān)于執(zhí)行順序:Promise在實例化的時候就會執(zhí)行,也就是如果Promise的實例化語句中函數(shù)console.log輸出語句,它會比then中的先執(zhí)行。Promise.all中傳入的Promise對象的數(shù)組(假設(shè)為p1、p2),即使p2的運行速度比p1快,Promise.all方法仍然會按照數(shù)組中的順序?qū)⒔Y(jié)果返回。
Promise的缺點:
1.當處于未完成狀態(tài)時,無法確定目前處于哪一階段。
2.如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部的錯誤不會反映到外部。
3.無法取消Promise,一旦新建它就會立即執(zhí)行,無法中途取消。
2.async/await
很多人說async/await是異步編程的終極解決方案、
JavaScript 的 async/await 實現(xiàn),離不開 Promise。
async:定義異步函數(shù)
1)自動把函數(shù)轉(zhuǎn)換為Promise
2)當調(diào)用異步函數(shù)時,函數(shù)返回值會被resolve處理
3)異步函數(shù)內(nèi)部可以使用await
await:暫停異步函數(shù)的執(zhí)行
1)當使用在Promise前面時,await等待Promise完成,并返回Promise的結(jié)果
2)await只能和Promise一起使用,不能和callback一起使用
3)await只能用在async函數(shù)中
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 關(guān)鍵字時,Promise 都會停下在,一直到運行結(jié)束,所以總共花費是 2+4+3 = 9 秒
async function getABC() {
// Promise.all() 允許同時執(zhí)行所有的異步函數(shù)
let results = await Promise.all([ getValueA, getValueB, getValueC ]);
return results.reduce((total,value) => total * value);
}
函數(shù)總耗時為 4 秒(getValueB 的耗時)。
Async 的價值在于用寫同步的方式寫異步,1避免了阻塞,2必免寫回調(diào)
二、異步加載
JS在默認情況下是以同步模式(又稱阻塞模式)加載的,瀏覽器對于代碼請求的資源都是瀑布式的加載,而不是阻塞式的,但是JS的執(zhí)行總是阻塞式的。
這會引起什么問題?如果在頁面中加載一些JS,但其中某個請求遲遲得不到響應,位于此JS后的JS將無法執(zhí)行,同時頁面渲染也不能繼續(xù)。
異步加載又被稱為非阻塞加載,瀏覽器在下載JS的同時,還會進行后續(xù)頁面處理。
1.如何實現(xiàn)JS異步加載
- 動態(tài)生成<script>標簽:這種方法可以兼容所有瀏覽器。
- async屬性
async是HTML5的新屬性,該屬性規(guī)定一旦腳本可用,則會異步執(zhí)行(一旦下載完畢就會立刻執(zhí)行),需要注意的是,async屬性僅適用于外部腳本。
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' async="async"></script>
- defer屬性
defer屬性規(guī)定是否對該腳本的執(zhí)行進行延遲,知道頁面加載為止。
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' defer="defer"></script
來分析一下async和defer屬性:
a.如果沒有async屬性和defer屬性,那么瀏覽器會立即執(zhí)行當前的JS腳本,阻塞后面的腳本;
b.如果有async屬性,加載和渲染后續(xù)文檔的過程和當前JS的加載和執(zhí)行并行進行,它是亂序執(zhí)行的,不管你聲明的順序如何,只要它加載完了就會執(zhí)行。
c.如果有defer屬性,加載后續(xù)文檔元素的過程和JS的加載時并行進行的,但是JS的執(zhí)行是在所有元素解析完成之后進行的,而且它是按照加載順序執(zhí)行腳本的。
簡言之:async是亂序;defer是順序。
-$(document).ready
需要引用jquery
兼容所有瀏覽器