[jQuery] 把setTimeout包裝成Promise、等待所有setTimout的callback function執行完程式流程才往下執行

setTimeout Promise,wait all setTimeout callback function over

前言

Javascript是單一執行緒程式,它沒辦法像C#那樣開多執行緒並同時間處理事情

如果在程式裡寫下

<html>
<body> 
    <!--引用jQuery-->
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js" ></script>
    <script type="text/javascript">
        $(function () {
             
            setTimeout(function () {
                console.log("執行setTimeout裡的function1");
            }, 0);

            setTimeout(function () {
                console.log("執行setTimeout裡的function2");
            }, 0);


            console.log("main thread done!");
        });
    </script>
</body>
</html>

類似setTimeout(或$.ajax() )這種有callback function的執行

Javascript會先把callback function 佇列起來,等待主執行緒全部執行完畢後,再一一執行佇列裡的callback function

所以執行結果↓

以上話句話說,如果主執行緒沒執行完畢(例如跑了無窮迴圈),callback function就不會被執行

有興趣的人可自行在console.log("main thread done!")前面加一行while(true){}試試

setTimeout()和$.ajax又有點微妙地不同,setTimeout()只有callback function,$.ajax()則會先發出request而callback function則等待主執行緒執行完畢才執行

不過本文要討論的是setTimeout(),最近工作上有需求,得等待所有setTimeout()的callback function執行完畢,再執行主執行緒後續的程式

原以為setTimeout()會像$.ajax()一樣回傳Promise物件,所以寫了以下錯誤寫法Orz

<html>
<body>
    <!--引用jQuery-->
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script type="text/javascript">
        $(function () {

            //Promise集合
            let myPromises = new Array();

            let p1 = 
            setTimeout(function () {
                console.log("執行setTimeout裡的function1");
                }, 500);
            myPromises.push(p1);


            let p2 = 
            setTimeout(function () {
                console.log("執行setTimeout裡的function2");
            }, 100);
            myPromises.push(p2);

            //等待所有setTimeout callback function執行完畢才執行
            $.when.apply(undefined, myPromises).then(function () {
                //let args = arguments;
                console.log("main thread done!");
            });
          
        });
    </script>
</body>
</html>

執行結果不是預期我想要的(setTimeout callfunction 都執行完畢再執行「main thread done!」)

實作

上網找到jQuery官方文件才知道要把setTimeout()包裝成Promise物件才行

<html>
<body>
    <!--引用jQuery-->
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script type="text/javascript">
        $(function () {

            //Promise集合
            let myPromises = new Array();
            let promiseTemp = {};//暫存變數
             
            let p1 = function () {
                //建立Deferred物件
                let dfd = jQuery.Deferred();

                setTimeout(function () {
                    console.log("執行setTimeout裡的function1");

                    //標註成功
                    dfd.resolve();
                }, 500);

                //回傳promise
                return dfd.promise();
            };
            promiseTemp = p1();//執行並取得Promise物件
            myPromises.push(promiseTemp);


            let p2 = function () {
                //建立Deferred物件
                let dfd = jQuery.Deferred();

                setTimeout(function () {
                    console.log("執行setTimeout裡的function2");

                    //標註成功
                    dfd.resolve();
                }, 100);
                //回傳promise
                return dfd.promise();
            };
            promiseTemp = p2();//執行並取得Promise物件
            myPromises.push(promiseTemp);

            //等待所有setTimeout callback function執行完畢才執行
            $.when.apply(undefined, myPromises).then(function () {
                //let args = arguments;
                console.log("main thread done!");
            });

        });
    </script>
</body>
</html>

執行結果如預期我想要的,setTimeout callback function全都執行完畢,才執行「main thread done!」

如果工作專案不在乎非同步函數執行順序的話,其實這樣打完就可以收工

但如果想要非同步函數有順序地逐一執行而且用戶可能使用IE11瀏覽器(也就是程式無法使用ES7規格的async、await)

程式碼得改寫如下:

<html>
<body>
    <!--引用jQuery-->
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script type="text/javascript">
        $(function () {

            //Promise集合
            let myPromises = new Array();

            let p1 = function () {
                //建立Deferred物件
                let dfd = jQuery.Deferred();

                setTimeout(function () {
                    console.log("執行setTimeout裡的function1");

                    //標註成功
                    dfd.resolve();
                }, 500);

                //回傳promise
                return dfd.promise();
            };
            myPromises.push(p1);
             
            let p2 = function () {
                //建立Deferred物件
                let dfd = jQuery.Deferred();

                setTimeout(function () {
                    console.log("執行setTimeout裡的function2");

                    //標註成功
                    dfd.resolve();
                }, 100);
                //回傳promise
                return dfd.promise();
            };
            myPromises.push(p2);

            waitAllAsyncFunc(myPromises, function () {
                console.log("main thread done!");
            });  

        });

        //所有非同步function按照順序地逐一執行
        function waitAllAsyncFunc(myPromises, allDoneFunc) {
            if (myPromises !== undefined && myPromises !== null && myPromises.length > 0) {
                //等待第一個setTimeout callback function執行完畢才執行下一個setTimeout callback function
                $.when(myPromises[0]()).then(function () {
                    //let args = arguments;
                    myPromises.splice(0, 1);
                    waitAllAsyncFunc(myPromises, allDoneFunc)
                });
            } else {
                allDoneFunc();
            }

        }
    </script>
</body>
</html>

執行結果↓

結語

本文主要記錄幾個重點:

1.把setTimeout函數包裝成Promise

2.使用$.when()等待所有非同步函數執行完畢

3.如何有順序地執行非同步函數