Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。javascript
js是單線程的java
JS運行在瀏覽器中,是單線程的,每一個window一個JS線程,既然是單線程的,在某個特定的時刻只有特定的代碼可以被執行,並阻塞其它的代碼。而瀏覽器是事件驅動的,瀏覽器中不少行爲是異步的,會建立事件並放入執行隊列中,JavaScript引擎是單線程處理它的任務隊列。當異步事件發生時,鼠標點擊事件發生、定時器觸發事件發生、XMLHttpRequest完成回調觸發等,將他們放入執行隊列,等待當前代碼執行完成。編程
瀏覽器不是單線程的promise
雖然JS運行在瀏覽器中,是單線程的,但瀏覽器不是單線程的,例如Web kit引擎,可能有以下線程:瀏覽器
當一個異步事件發生的時候,它就進入事件隊列。瀏覽器有一個內部大消息循環,Event Loop(事件循環),會輪詢事件隊列並處理事件。好比,瀏覽器當前正在忙於處理onclick事件,這時window onSize事件發生了,這個異步事件就被放入事件隊列等待處理,只有前面的處理完畢了,空閒了纔會執行這個事件。bash
爲何JavaScript是單線程的卻能讓AJAX異步發送和回調請求,爲何setTimeout也看起來像是多線程的?多線程
Ajax請求確實是異步的,這請求是由瀏覽器新開了一個線程請求,事件回調的時候是放入Event loop單線程事件隊列等候處理。當瀏覽器空閒的時候出隊列任務被處理,JavaScript引擎始終是單線程運行回調函數、單線程處理它的任務隊列。異步
setTimeout(func, 0)
神奇在哪兒?那就是告訴js引擎,在0ms之後把func放到主事件隊列中,等待當前的代碼執行完畢再執行,注意:重點是改變了代碼流程,把func的執行放到了主事件隊列中。這就是它的神奇之處了。它的用處有三個:模塊化
詳細解釋見下一篇文章《巧用setTimeout(func, 0)》。(2017-11-30注:原本想寫的,偶然翻到一篇文章《這一次,完全弄懂 JavaScript 執行機制》以爲已經寫得很好了,就收藏啦(#^.^#))異步編程
這是異步編程最基本的方法。
假定有兩個函數f1和f2,後者等待前者的執行結果。
f1();
f2();複製代碼
若是f1是一個很耗時的任務,能夠考慮把f2寫成f1的回調函數。
function f1(callback){
setTimeout(function () {
// f1的任務代碼
callback();
}, 1000);
}複製代碼
執行代碼就變成下面這樣:
f1(f2);複製代碼
採用這種方式,咱們把同步操做變成了異步操做,f1不會堵塞程序運行。回調函數的優勢是簡單、容易理解和部署,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合,流程會很混亂,並且每一個任務只能指定一個回調函數。
另外一種思路是採用事件驅動模式。任務的執行不取決於代碼的順序,而取決於某個事件是否發生。
仍是以f1和f2爲例。首先,爲f1綁定一個事件(這裏採用的jQuery的寫法)。
f1.on('done', f2);複製代碼
上面這行代碼的意思是,當f1發生done事件,就執行f2。而後,對f1進行改寫:
function f1(){
setTimeout(function () {
// f1的任務代碼
f1.trigger('done');
}, 1000);
}複製代碼
f1.trigger('done')表示,執行完成後,當即觸發done事件,從而開始執行f2。
這種方法的優勢是比較容易理解,能夠綁定多個事件,每一個事件能夠指定多個回調函數,並且能夠"去耦合",有利於實現模塊化。缺點是整個程序都要變成事件驅動型,運行流程會變得很不清晰。
Promise 是異步編程的一種解決方案,比傳統的解決方案「回調函數」和「事件」——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。
基本用法以下:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操做成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});複製代碼
下面列出異步操做失敗、抓捕異常的另外一種寫法
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});複製代碼
這樣寫的優勢在於,回調函數變成了鏈式寫法,程序的流程能夠看得很清楚,能夠實現許多強大的功能。
好比,指定多個回調函數等等。