流程控制: jQ Deffered 與 ES6 Promise 使用入坑!
從 jQuery $.Deferred() 開始
說到異步流程控制,之前用的比較多的是jQ的Deferred。那Deferred是個啥呢,控制臺來打印看下:
喔!看得出$.Deferred()后是個對象,其 下面 有著熟悉的 done , fail , always 字眼(對,沒錯,ajax里經常用到的就是這些貨色), 當然了,還有最最重要的 reject 和 resolve 方法,說到這兩個方法,就得引出下Deferred的狀態機制了,其實很簡單,實例化后用上圖中的 state 方法就可以查看( $.Deferred().state() ),有三種狀態
-
執行resolve/reject前,返回值是pending
-
執行了resolve,返回值是resolved
-
執行了reject,返回值是rejected
來試著用下!
function log (msg) {
console.log(msg);
}
// 申明個異步操作
var Async = function () {
// 生成一個0到5秒的延遲
var delay = Math.floor(Math.random() * 5);
// 創建一個Deffered對象
var dfd = $.Deferred();
// 這里調用一個異步操作
setTimeout(function(){
if (delay <= 2) {
// 置dfd狀態為resolved
dfd.resolve('一切正常!');
} else {
// 置dfd狀態為rejected
dfd.reject('超時了!');
}
}, delay * 1000)
// 這里要返回Deferred下的promise對象Dererred對象的原因下面會解釋
return dfd.promise();
}
Async()
.done(function (data) {
log(data) // 如果延遲不大于三秒 輸出dfd.resolve()中的數據 '一切正常!'
})
.fail(function (err) {
log(err) // 反之則 輸出dfd.reject()中的數據 '超時了!'
})
.always(function () {
log('執行完畢!'); // 總是輸出 '執行完畢!'
})
嘗試下通俗理解整個流程就是
-
在某個操作 開始前 創建一個 Deferred 對象,然后執行操作
-
操作間可根據情況給dfd執行 relove 或者 reject 方法改變狀態并傳入數據
-
最后返回出dfd的對象下的一個promise對象,這里不直接返回dfd對象是因為dfd對象的狀態是在第一次resolve或者reject后還可以更改的(不過里面的數據以第一次為準)!!
-
操作執行后用 done 和 fail 方法分別接受resolve和reject狀態和數據(一一對應)然后執行回調(其實1.8還有個 then 方法,接受兩個參數,第一個參數為 resolve 的回調,第二個為 reject 的)
-
always 是無論 resolve 還是 reject 都會執行。
講個比較爛的比喻啊,我是一個流水線車間質檢工人,就在平常的這樣的一天,來了一批玩具熊,嗯,接下來應該是這樣的
-
來了一個檢查目標( $.Dererred() ),這時你還不知道它是好是壞
-
我靠我幾十年的新東方炒菜技巧檢驗產品并給良品貼上了合格標簽( dfd.res* olve(合格標簽) ),次品貼上回廠標簽* ( dfd.reject(回廠標簽及原因) )
-
然后通過的良品和次品都來到了各自的包裝口打好包,不能對里面的標簽做更改了!( dfd.promise() )去往自己下一個目的地( return dfd.promise )
-
再然后良品來到了熊孩子手中( .done() ),次品回到了廠里( .fail() ),最后不管玩具熊到了哪里,其實都會被開膛破肚( .always() 好吧這里有點牽強)
這里再上一張圖來解釋下!
還有值得說一下的是 always 里的回調,我在實際中使用時發現總是在 done 和 fail 里的回調(假設為同步)執行完畢后后執行的。
接下來是ES6 Promise!
不說什么,先打印一下!
可以看到Promise下也有熟悉的 resolve 和 reject 方法,好像和jQ的 Deferred 頗為相似!但是不是少了點什么呢? done 或者 fail 之類的流程控制的方法呢??
不急,其實展開 prototype 原型上就可以看到掛載著的 then 方法了!(像極了jQ1.8后那個 then ,不過我覺得應該說是jQ來遵循 Promise 才對)
Promise其實就是個構造函數,還是之前的例子,這里我們分三步走
var Async = function () {
// 第一步,新建個promise對象,所需的異步操作在其中進行
var prms = new Promise(function(resolve, reject){
// 生成一個0到5秒的延遲
var delay = Math.floor(Math.random() * 5);
// 這里調用一個異步操作
setTimeout(function(){
// 第二步, 根據情況置promise為resolve或者reject
if (delay <= 2) {
// 置dfd狀態為resolved
resolve('一切正常!');
} else {
// 置dfd狀態為rejected
reject('超時了!');
}
}, delay * 1000)
})
// 第三步,返回這個Promise對象
return prms
}
// 強大的來了
Async()
// then接受兩個函數分別處理resolve和reject兩種狀態
.then(
function(data) {
console.log(data) // 一切正常!
},
function(err) {
console.log(err) // 超時了!!
})
粗粗一看好像和 Dererred 不能更像了,,不過細心點的話可以發現我們在函數里直接返回了 prms 這個對象,而不是像之前把包裝了一層。。。對!因為 Promise 的特性就是一旦第一次賦予了狀態后面就無法更改了,這也算省心多了吧。但是問題來了,我為什么要選擇用 Promise 呢??
這么說吧, 它是原生的 它是原生的 它是原生的! ,還有 可以鏈式鏈式鏈式鏈式調用! ,我們可以把每一個 then 或者 catch 當做一個處理器, 比如這樣
Async()
// 這里暫時只處理resolve
.then(function(data) {
console.log(data) // 一切正常!
return Promise.resolve('隨便什么');
})
// 下一個then處理器接收到上一個處理器發出的數據
.then(function(data2) {
console.log(data2) // 隨便什么
return Promise.reject('錯誤數據');
})
...
對!沒看錯,其實在 then 里面你還可以 return 其他的 promise 對象傳并遞數據!更有甚你甚至可以什么都不返回,比如說這樣
Async()
.then(function(data) {
console.log(data) // 一切正常!
})
// 上面那個處理器如果不return任何東西 就會默認返回個resolve(undefined)
// 然后下面的處理器就會接收到這個resolve(undefined)
.then(function(data2) {
console.log(data2) // undefined
// 雖然沒有數據來處理,但是你還可以在這里做一些事情啊,例如
return Promise.reject('錯誤數據');
})
// 嗒噠,catch就這么登場了,這里用catch處理上個then處理器發出的reject
.catch(fucntion(err){
console.log(err) // 錯誤數據
return '那直接返回個字符串呢?'
})
// 上個catch處理器返回了個字符串其實也會被下個處理器接受
// 相當于resolve('那直接返回個字符串呢?')
.then(function(data3){
console.log(data3) // 那直接返回個字符串呢?
})
// 好,接著我們來試試在沒有返回任何東西的情況下接一個catch處理器
.catch(function(err2){
console.log(err2)
// 我們可以來猜一下上面會輸出什么,undefined嗎?
// 錯,其實這里什么都不會輸出,因為這個catch接收的是resolve
// 但它并不會吞沒這個resolve而是選擇跳過,例如我們這里再返回
return Promise.resolve('這個字符串會被跳過')
})
// 這里緊接著個then處理器,它接受到的數據呢
// 其實并不是上個catch返回的resolve('這個字符串會被跳過')
// 而是catch之前那個then處理器默認返回的resolve(undefined)
.then(function(data4){
console.log(data4) // undefined
})
有點被繞暈了吧
我們用一句話來梳理下:
鏈式調下會有一串 then 和 catch ,這些 then 和 catch 處理器會 按照順序 接受 上個處理器 所產生的返回值,并且根據 傳入的狀態 做出 不同 響應,要么跳過,要么處理(所以上面23行處的 catch 處理器被跳過了)
ps: 上面我們用的 then 處理器只有一個函數參數,所以只會處理 resolve 狀態,如果是兩個 then 就可以處理 reject 了。
寫的還是很粗糙,有錯誤的地方希望多多指教!