流程控制: jQ Deffered 與 ES6 Promise 使用入坑!

Arl73H 8年前發布 | 22K 次閱讀 Promise jQuery Ajax框架 ECMAScript

 

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

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

  1. 在某個操作 開始前 創建一個 Deferred 對象,然后執行操作

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

  3. 最后返回出dfd的對象下的一個promise對象,這里不直接返回dfd對象是因為dfd對象的狀態是在第一次resolve或者reject后還可以更改的(不過里面的數據以第一次為準)!!

  4. 操作執行后用 done 和 fail 方法分別接受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 里的回調,我在實際中使用時發現總是在 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 了。

寫的還是很粗糙,有錯誤的地方希望多多指教!

來自: https://segmentfault.com/a/1190000005072394

 本文由用戶 Arl73H 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!