Javascript語言的執行環境是「單線程」。javascript
單線程: 一次只能完成一個任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務。html
單線程的好處是執行環境簡單,壞處是在一些耗時的任務上會堵塞進程。好比讀取一個大文件,線程卡在這個任務上,形成頁面卡死。java
那如何在讀取文件的同時,又能查看圖片,作一些其餘的事呢?編程
這就到了「同步」和「異步」之爭:
同步:後一個任務等待前一個任務結束,而後再執行,程序的執行順序與任務的排列順序是一致的、同步的。
異步:每個任務有一個或多個回調函數(callback),前一個任務結束後,不是執行後一個任務,而是執行回調函數,後一個任務則是不等前一個任務結束就執行,因此程序的執行順序與任務的排列順序是不一致的、異步的。promise
舉個栗子:
去餐廳吃飯
同步:你們依次排隊,前一我的付完錢,等待領取食物。。。漫長等待。。。食物作好了,後一我的跟進。
異步:你們依次排隊,前一我的付完錢,服務員給他一個餐牌,後一我的跟進。餐牌對應的食物作好了,再去領取。異步
如下總結了「異步編程」的4種方式:async
回調函數:異步編程的最基本的方式。模塊化
下面經過樣例做爲演示:咱們定義三個方法「作飯」(cook)、「吃飯」(eat),「洗碗」(wash)三個方法,它們是層層依賴的關係,下一步的的操做須要使用上一部操做的結果(這裏使用 setTimeout 模擬異步操做)。異步編程
// 作飯 function cook() { console.log('開始作飯...'); sleep(2000); // 等待2秒 console.log('作飯完畢'); } // 吃飯 function eat() { console.log('開始吃飯...'); sleep(2000); // 等待2秒 console.log('吃飯完畢'); } // 洗碗 function wash() { console.log('開始洗碗...'); sleep(2000); // 等待2秒 console.log('洗碗完畢'); } // 阻塞等待(毫秒) function sleep(delay) { let start = (new Date()).getTime(); while((new Date()).getTime() - start < delay) { continue; } } cook(); eat(); wash(); // 開始作飯... // 作飯完畢 // 開始吃飯... // 吃飯完畢 // 開始洗碗... // 洗碗完畢
上面是「同步」的寫法,下面咱們改寫成「異步」:回調函數函數
// 作飯 function cook(callback) { console.log('開始作飯...'); setTimeout(function() { console.log('作飯完畢'); // 這裏是回調,執行吃飯的方法 callback(); }, 2000); } // 吃飯 function eat(callback) { console.log('開始吃飯...'); setTimeout(function() { console.log('吃飯完畢'); // 這裏是回調,執行洗碗的方法 callback(); }, 2000); } // 洗碗 function wash() { console.log('開始洗碗...'); setTimeout(function() { console.log('洗碗完畢'); // 洗碗以後的其餘動做,這裏就不寫了 }, 2000); } cook(function() { eat(function() { wash(); }) }); // 開始作飯... // 作飯完畢 // 開始吃飯... // 吃飯完畢 // 開始洗碗... // 洗碗完畢
回調函數的優勢是簡單、容易理解和部署,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程會很混亂,並且每一個任務只能指定一個回調函數。
事件監聽:採用事件驅動模式,任務的執行不取決於代碼的順序,而取決於某個事件是否發生。
let events = require('events'); let eventEmitter = new events.EventEmitter(); // 作飯 var cook = function() { console.log('開始作飯...'); setTimeout(function() { console.log('作飯完畢'); // 執行eat事件 eventEmitter.emit('eatEvent'); }, 2000); } // 吃飯 var eat = function() { console.log('開始吃飯...'); setTimeout(function() { console.log('吃飯完畢'); // 執行wash事件 eventEmitter.emit('washEvent'); }, 2000); } // 洗碗 var wash = function() { console.log('開始洗碗...'); setTimeout(function() { console.log('洗碗完畢'); // 洗碗以後的其餘動做,這裏就不寫了 }, 2000); } // 綁定cook事件 eventEmitter.on('cookEvent', cook); // 綁定eat事件 eventEmitter.on('eatEvent', eat); // 綁定wash事件 eventEmitter.on('washEvent', wash); // 執行cook事件 eventEmitter.emit('cookEvent'); // 開始作飯... // 作飯完畢 // 開始吃飯... // 吃飯完畢 // 開始洗碗... // 洗碗完畢
這種方法的優勢是比較容易理解,能夠綁定多個事件,每一個事件能夠指定多個回調函數,並且能夠"去耦合"(Decoupling),有利於實現模塊化。缺點是整個程序都要變成事件驅動型,運行流程會變得很不清晰
發佈/訂閱:又稱「觀察者模式」。咱們假定,存在一個"信號中心",某個任務執行完成,就向信號中心"發佈"(publish)一個信號,其餘任務能夠向信號中心"訂閱"(subscribe)這個信號,從而知道何時本身能夠開始執行。
和上一個「事件監聽」很相似,本人對這種模式理解有限,這裏就不列代碼誤導你們了。
Promise: 由 CommonJS 小組的成員在 Promise/A 規範中提出,目的是爲異步編程提供統一接口。
根據 Promise/A 規範,promise 是一個對象,只須要 then 這一個方法。
// 作飯 function cook() { console.log('開始作飯...'); let promise = new Promise(function(resolve, reject) { setTimeout(function() { console.log('作飯完畢'); resolve(); }, 2000); }); return promise; } // 吃飯 function eat(callback) { console.log('開始吃飯...'); let promise = new Promise(function(resolve, reject) { setTimeout(function() { console.log('吃飯完畢'); resolve(); }, 2000); }); return promise; } // 洗碗 function wash() { console.log('開始洗碗...'); let promise = new Promise(function(resolve, reject) { setTimeout(function() { console.log('洗碗完畢'); // 洗碗以後的其餘動做,這裏就不寫了 resolve(); }, 2000); }); return promise; } cook() .then(function() { return eat(); }) .then(function() { return wash(); }) .then(function() { console.log('結束...'); }) .catch(function() { console.log('好像出什麼問題了'); });
優勢:
Promise的功能不單單隻上面用到的,諸如其餘all(), race()之類,限於篇幅,你們能夠翻看其餘文章查看。
參考文章:Javascript異步編程
參考文章:Promise使用詳解