[Javascript] 非同步處理: jQuery Promise轉bluebird Promise

使用一段 jQuery 時間至今,終於到了感嘆技術歲月的無情時刻(感謝 jQuery 過去那段輝煌),現在已一堆資料驅動的框架出現,幫助我們更容易開發複雜的前後端系統,慢慢也因為效能問題,最近開始要淘汰 jQuery 語法。

參考下面兩篇文章之後︰

原生js替換jQuery各種方法-中文版
https://segmentfault.com/a/1190000016594035

jQuery Promise 與 bluebird Promise 對應說明
https://github.com/petkaantonov/bluebird/blob/master/docs/docs/coming-from-other-libraries.md

決定先把使用 jQuery Promise 的部份先替換掉,我這邊改用 bluebird。不過,就再替換之後發現有一些重大問題,例如把 jQuery 的 done, fail 改為 bluebird 的 then, catch 時,竟然再某些情況下的觸發機制不同,這實在讓我找了許久才發現不同處。

主要為 bluebird 為實做 Promises/A+ 規範,其 catch 會把錯誤狀態視為已處理,之後可繼續使用 then 監聽後續事件消息。而 jQuery 的 fail 後不同,Promise 狀態依然是 reject,故再之後所接續監聽鏈式消息都是只有 fail,而若我們依照 jQuery 所設計開發的程序,採用硬轉語法 done, fail 成為 bluebird 的 then, catch 時,就會遇到這問題。

以下為單html檔測試範例︰
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <title>jQuery與bluebird Promise Demo</title>

    <!--使用jquery-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <!--使用bluebird-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.5.2/bluebird.min.js"></script>
    <script type="text/javascript">
        let B = Promise.noConflict(); //重新命名
        B.onPossiblyUnhandledRejection(function (e, promise) { }); //對promise全部預處理Unhandled Rejection, 避免沒catch跳出警告
    </script>

    <!--使用lodash-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

    <script>


        function genDF_jq() {
            //回傳jQuery Deferred, 預設能鏈式處理

            return $.Deferred();
        }


        function genDF_bb() {
            //回傳bluebird Promise, 使能支援鏈式處理

            let resolve, reject;
            let promise = new B(function () {
                resolve = arguments[0];
                reject = arguments[1];
            });
            promise.resolve = resolve;
            promise.reject = reject;

            return promise;
        }


        function jqtest_L1() {
            //jQuery測試第1層呼叫

            let df = genDF_jq();

            //延遲1秒就reject
            _.delay(function () {
                let msg = 'jqtest_L1 reject';
                console.log(msg);
                df.reject(msg);
            }, 1000);

            return df;
        }
        function jqtest_L2() {
            //jQuery測試第2層呼叫

            //回傳L1並先監聽done與fail事件
            return jqtest_L1()
                .done(function () {
                    console.log('jqtest_L2 done');
                })
                .fail(function () {
                    console.log('jqtest_L2 fail');
                });
        }
        function jqtest_L3() {
            //jQuery測試第3層呼叫

            //回傳L2並先監聽done與fail事件
            return jqtest_L2()
                .done(function () {
                    console.log('jqtest_L3 done');
                })
                .fail(function () {
                    console.log('jqtest_L3 fail');
                });
        }


        function bbtest_L1() {
            //bluebird測試第1層呼叫

            let df = genDF_bb();

            //延遲1秒就reject
            _.delay(function () {
                let msg = 'bbtest_L1 reject';
                console.log(msg);
                df.reject(msg);
            }, 1000);

            return df;
        }
        function bbtest_L2() {
            //bluebird測試第2層呼叫

            //回傳L1並先監聽then與catch事件
            return bbtest_L1()
                .then(function () {
                    console.log('bbtest_L2 then');
                })
                .catch(function () {
                    console.log('bbtest_L2 catch');
                });
        }
        function bbtest_L3() {
            //bluebird測試第3層呼叫

            //回傳L2並先監聽then與catch事件
            return bbtest_L2()
                .then(function () {
                    console.log('bbtest_L3 then');
                })
                .catch(function () {
                    console.log('bbtest_L3 catch');
                });
        }


        $(function () {

            jqtest_L3();
            bbtest_L3();

        });


    </script>

</head>
<body>
</body>
</html>

載入後畫面為︰


由 console.log 可看到 jqtest_L2 與 bbtest_L2 都是能接收到錯誤狀態(fail, catch),但 jqtest_L3 與 bbtest_L3 確觸發到不同事件,jQuery依然是觸發到 fail,但 bluebird 反而是觸發 then 事件。

若要避免上述此問題,記得改為 bluebird 後,函數各層(以本例來說是L2, L3層)使用或回傳 bluebird Promise 時,要接收 catch 消息就再另外包一層後,在裡面呼叫 reject 才回傳 Promise,其非同步訊息處理流程就會等同於 jQuery 版的運行方式。


#Javascript, jQuery, Deferred, bluebird, Promise, done, fail, then, catch, 觸發機制

留言