流程控制: jQ Deferred 與 ES6 Promise 使用新手向入坑!

謝謝n͛i͛g͛h͛t͛i͛r͛e͛大大指出的關於Promisecatch用的不到位的錯誤,貼上大大推薦的文章Promise中的菜鳥和高階錯誤,文章很詳細說明了一些Promise使用中的錯誤和指導。另外更正內容在後面補充。javascript

從 jQuery $.Deferred() 開始

說到異步流程控制,以前用的比較多的是jQ的Deferred。那Deferred是個啥呢,不清楚不要緊,直接控制檯來打印看下:html

喔!看得出$.Deferred()後是個對象,其下面有着熟悉的done, fail, always字眼(對,,是否是有點熟悉了呢?沒錯!若是常常用ajax的話就會常常接觸到這些貨色)。 固然了,不止這些,還有最最最重要的rejectresolve方法,說到這兩個方法,就得引出下Deferred的狀態機制了——其實很簡單,實例化後用上圖中的state方法就能夠查看($.Deferred().state()),有三種狀態java

  • 執行resolve/reject前,返回值是pendingajax

  • 執行了resolve,返回值是resolvedsegmentfault

  • 執行了reject,返回值是rejectedpromise

直接來試着用下吧!這裏咱們假設執行一個隨機延時的setTimeout的異步操做,setTimeout異步操做結束後,根據延時大小,作出不一樣迴應 ! 代碼:dom

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('執行完畢!'); // 老是輸出 '執行完畢!'
    })

嘗試下通俗理解整個流程就是

  1. 在某個操做開始前建立一個Deferred對象,而後執行操做異步

  2. 操做間可根據狀況給dfd執行relove或者reject方法改變狀態並傳入數據函數

  3. 最後返回出dfd的對象下的一個promise對象,這裏不直接返回dfd對象是由於dfd對象的狀態是在第一次resolve或者reject後還能夠更改的(不過裏面的數據以第一次爲準)!!spa

  4. 操做執行後用donefail方法分別接受resolve和reject狀態和數據(一一對應)而後執行回調(其實1.8還有個then方法,接受兩個參數,第一個參數爲resolve的回調,第二個爲reject的)

  5. always是不管resolve仍是reject都會執行。

講個比較爛的比喻啊

我是一個流水線車間質檢工人,就在日常的這樣的一天,來了一批玩具熊,嗯,接下來應該是這樣的

  1. 來了一個檢查目標($.Dererred()),這時你還不知道它是好是壞

  2. 我靠我幾十年的新東方炒菜技巧檢驗產品並給良品貼上了合格標籤(dfd.res* olve(合格標籤)),次品貼上回廠標籤* (dfd.reject(回廠標籤及緣由)

  3. 而後經過的良品和次品都來到了各自的包裝口打好包,不能對裏面的標籤作更改了!(dfd.promise())去往本身下一個目的地(return dfd.promise)

  4. 再而後良品來到了熊孩子手中(.done()),次品回到了廠裏(.fail()),最後無論玩具熊到了哪裏,其實都會被開膛破肚(.always()好吧這裏有點牽強)

這裏再上一張圖來解釋下!

還有值得說一下的是always裏的回調,我在實際中使用時發現老是在donefail裏的回調(假設爲同步)執行完畢後後執行的。

金掌銀掌仙人掌 掌聲有請 ES6 Promise

和上面同樣,先打印一下!

能夠看到Promise下也有熟悉的resolvereject方法,好像和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
    })

有點被繞暈了吧5.gif

咱們用一句話來梳理下:

鏈式調下會有一串thencatch,這些thencatch處理器會按照順序接受上個處理器所產生的返回值,而且根據傳入的狀態作出不一樣響應,要麼跳過,要麼處理(因此上面23行處的catch處理器被跳過了)

ps: 上面咱們用的then處理器只有一個函數參數,因此只會處理resolve狀態,若是是兩個then就能夠處理reject了。

----更新於5月11日-----

catch使用的注意

上面一塊代碼中引出了catch處理器, 以前覺得 cacth()then(null, ...) 的語法糖, 其實這麼說不徹底正確(功能層面上來講這兩個是徹底相同的沒錯——都是處理reject和異常),可是到了實際使用中Promise中的菜鳥和高階錯誤文章中給出了明確的狀況證實,這裏貼一下:

首先只處理異常狀況,下面兩個是等價的

somePromise().catch(function (err) {
  // 處理異常
});

somePromise().then(null, function (err) {
  // 處理異常
});

可是,若是不僅是處理異常的下面兩種狀況下就不同了

somePromise().then(function () {
  return otherPromise();
}).catch(function (err) {
  // 處理異常
});

somePromise().then(function () {
  return otherPromise();
}, function (err) {
  // 處理異常
});

不夠清楚嗎?那麼若是是這樣呢?若是第一個回調函數拋出一個錯誤會發生什麼?

somePromise().then(function () {
  throw new Error('這裏錯了!');
}).catch(function (err) {
  console.log(err)
  // 這裏錯了! :)
});

somePromise().then(
function () {
  throw new Error('這裏錯了');
}, 
function (err) {
  console.log(err)
  // 未知 :(
  // 並無catch到上面那個Error
});

結論就是,當使用 then(resolveHandler, rejectHandler)rejectHandler 不會捕獲在 resolveHandler 中拋出的錯誤!

貼完了,好吧,這有什麼用呢?

看似這個注意項並不影響日常使用,原文做者也說道:

由於,筆者的我的習慣是從不使用then方法的第二個參數,轉而使用 catch() 方法

那麼,問題來了,如何正確的使用catch呢? 其實我沒有很好的想明白,但願指教,隨便拋兩個磚

// 1
somePromise()
    .then(resolveHandler)
    // 這個catch會處理somePromise或者resolveHandler的異常
    .catch(rejectHandler) 
    .then(otherResolveHandler)
    // 而這個catch呢只會處理resolveHandler的異常
    .catch(otherRejectHandler)
    
// 2
somePromise()
    .then(resolveHandler)
    .then(otherResolveHandler)
    // 至於這個catch則會處理somePromise、resolveHandler和otherResolveHandler的異常
    .catch(rejectHandler)
    
// 3 
somePromise()
    .catch(console.log.bind(console))
    //等價於
    .catch(function(err){
        console.log(err)
    })

哈哈哈哈哈哈,仍是好好再去想一想Promise去了,弄明白了再來補充,再次謝謝@n͛i͛g͛h͛t͛i͛r͛e͛大大,荊柯刺秦王

寫的很粗糙,有錯誤的地方但願多多指教!!

相關文章
相關標籤/搜索