JavaScript的Callback機制深刻人心。而ECMAScript的世界一樣充斥的各類異步操做(異步IO、setTimeout等)。異步和Callback的搭載很容易就衍生"回調金字塔"。——由此產生Deferred/Promise。 javascript
Deferred起源於Python,後來被CommonJS挖掘併發揚光大,獲得了大名鼎鼎的Promise,而且已經歸入ECMAScript 6(JavaScript下一版本)。html
Promise/Deferred是當今最著名的異步模型,不只強壯了JavaScript Event Loop(事件輪詢)機制下異步代碼的模型,同時加強了異步代碼的可靠性。—— 匠者爲之,以惠匠者。本文內容以下:java
- Promise應對的問題
- Promise的解決
- ECMAScript 6 Promise
- 參考和引用
JavaScript充斥着Callback,例以下面的代碼:es6
(function (num) {//從外面接收一個參數 var writeName = function (callback) { if (num === 1) callback(); } writeName(function () {//callback console.log("i'm linkFly"); }); })(1);
把一個函數經過參數傳遞,那麼這個函數叫作Callback(回調函數)。ajax
JavaScript也充斥着異步操做——例如ajax。下面的代碼就是一段異步操做:編程
var name; setTimeout(function () { name = 'linkFly'; }, 1000);//1s後執行 console.log(name);//輸出undefined
這段代碼的運行邏輯是這樣的:
promise
咱們的老是碰見這樣的狀況:一段代碼異步執行,後續的代碼卻須要等待異步代碼的,若是在異步代碼以前執行,就會如上面的console.log(name)同樣,輸出undefined,這並非咱們想要的效果。瀏覽器
相似的狀況老是發生在咱們常常要使用的ajax上:ruby
$.ajax({ url: 'http://www.cnblogs.com/silin6/map', success: function (key) { //咱們必需要等待這個ajax加載完成才能發起第二個ajax $.ajax({ url: 'http://www.cnblogs.com/silin6/source/' + key, success: function (data) { console.log("i'm linkFly");//後輸出 } }); } }); console.log('ok');//ok會在ajax以前執行
異步操做有點相似這一段代碼被掛起,先執行後續的代碼,直到異步獲得響應(例如setTimeout要求的1s以後執行,ajax的服務器響應),這一段異步的代碼纔會執行。關於這一段異步代碼的執行流程,請參閱JavaScript大名鼎鼎的:Event Loop(事件輪詢)。服務器
Promise優雅的修正了異步代碼,咱們使用Promise重寫咱們setTimeout的示例:
var name, p = new Promise(function (resolve) { setTimeout(function () {//異步回調 resolve(); }, 1000);//1s後執行 }); p.then(function () { name = 'linkFly'; console.log(name);//linkFly }).then(function () { name = 'cnBlog'; console.log(name); }); //這段代碼1s後會輸出linkFly,cbBlog
咱們先不要太過在乎Promise對象的API,後續會講解,咱們只須要知道這段代碼完成了和以前一樣的工做。咱們的console.log(name)正確的輸出了linkFly,而且咱們還神奇的輸出了cnBlog。
或許你以爲這段代碼實在繁瑣,還不如setTimeout來的痛快,那麼咱們再來改寫上面的ajax:
var ajax = function (url) { //咱們改寫ajax,讓它以Promise的方式工做 return new Promise(function (resolve) { $.ajax({ url: url, success: function (data) { resolve(data); } }); }); }; ajax('http://www.cnblogs.com/silin6/map') .then(function (key) { //咱們獲得key,發起第二條請求 return ajax('http://www.cnblogs.com/silin6/source/' + key); }) .then(function (data) { console.log(data);//這時候咱們會接收到第二次ajax返回的數據 });
或許它晦澀難懂,那麼咱們嘗試用setTimeout來模擬此次的ajax,這個例子演示了Promise數據的傳遞,一如ajax:
var name, ajax = function (data) { return new Promise(function (resolve) { setTimeout(function () {//咱們使用setTimeout模擬ajax resolve(data); }, 1000);//1s後執行 }); }; ajax('linkFly').then(function (name) { return ajax("i'm " + name);//模擬第二次ajax }).then(function (value) { //2s後,輸出i'm linkFly console.log(value); });
上面的代碼,從代碼語義上達到了下面的流程:
咱們僅觀察代碼就知道如今的它變得很是優雅,兩次異步的代碼被完美的抹平。但咱們應該時刻謹記,Promise改變的是你異步的代碼和編程思想,而並無改變異步代碼的執行——它是一種由卓越的編程思想所衍生的對象。
下面一張圖演示了普通異步回調和Promise異步的區別,Promise實現的異步從代碼運行上來講並沒有太大區別,但從編程思想上來講差別巨大。
Promise對象表明了將來某個將要發生的事件(一般是一個異步操做),抹平了異步代碼的金字塔,它從模型上解決了異步代碼產生的"回調金字塔"。
Promise是ECMAScript 6規範內定義的,因此請使用現代瀏覽器測試,它的兼容性能夠在這裏查看。
Promise.constructor
Promise是一個對象,它的構造函數接收一個回調函數,這個回調函數參數有兩個函數:分別在成功狀態下執行和失敗狀態下執行,Promise有三個狀態,分別爲:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected)。
var p = new Promise(function (resolve,reject) { console.log(arguments); //resolve表示成功狀態下執行 //reject表示失敗狀態下執行 });
傳遞的這個回調函數,等同被Promise從新封裝,並傳遞了兩個參數回調,這兩個參數用於驅動Promise數據的傳遞。resolve和reject自己承載着觸發器的使命:
Promise.prototype.then
生成的promise實例(如上面的變量p)擁有方法then(),then()方法是Promise對象的核心,它返回一個新的Promise對象,所以能夠像jQuery同樣鏈式操做,很是優雅。
Promise是雙鏈的,因此then()方法接受兩個參數,分別表示:
p.then(function () { //咱們返回一個promise return new Promise(function (resolve) { setTimeout(function () { resolve('resolve'); }, 1000);//異步1s }); }, function () { console.log('rejected'); }) //鏈式回調 .then(function (state) { console.log(state);//若是爲執行態,輸出resolve }, function (data) { console.log(data);//若是爲拒絕態,輸出undefined });;
then()方法的返回值由它相應狀態下執行的函數決定:這個函數返回undefined,則then()方法構建一個默認的Promise對象,而且這個對象擁有then()方法所屬的Promise對象的狀態。
var p = new Promise(function (resolve) { resolve();//直接標誌執行態 }), temp; temp = p.then(function () { //傳入執行態函數,不返回值 }); temp.then(function () { console.log('fulfilled');//擁有p的狀態 }); console.log(temp === p);//默認構建的promise,但已經和p不是同一個對象,輸出false
若是對應狀態所執行的函數返回一個全新的Promise對象,則會覆蓋掉當前Promise,代碼以下:
var p = new Promise(function (resolve) { resolve();//直接標誌執行態 }), temp; temp = p.then(function () { //返回新的promise對象,和p的狀態無關 return new Promise(function (resolve, reject) { reject();//標誌拒絕態 }); }); temp.then(function () { console.log('fulfilled'); }, function () { console.log('rejected');//輸出 });
即then()方法傳遞的進入的回調函數,若是返回promise對象,則then()方法返回這個promise對象,不然將默認構建一個新的promise對象,並繼承調用then()方法的promise的狀態。
咱們應該清楚Promise的使命,抹平了異步代碼的回調金字塔,咱們會有不少依賴上一層異步的代碼:
var url = 'http://www.cnblogs.com/silin6/'; ajax(url, function (data) { ajax(url + data, function (data2) { ajax(url + data2, function (data3) { ajax(url + data3, function () { //回調金字塔 }); }); }); });
使用Promise則抹平了代碼:
promise.then(function (data) { return ajax(url + data); }).then(function (data2) { return ajax(url + data2); }).then(function (data3) { return ajax(url + data3); }).then(function (data) { //扁平化代碼 });
Promise還有更多更強大的API。但本文的目的旨在讓你們感覺到Promise的魅力,而並不是講解Promise對象自身的API,關於Promise其餘輔助實現API請查閱本文最下方的引用章節,Promise其餘API以下:
但願你們一點點的接受Promise,因此沒有講太多,咱們對於Promise的理解不該該僅僅是一個異步模型,咱們更關注應該是Promise/Deferred的編程思想,因此後續幾篇會逐漸深刻講解Promise的前生今世。