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.如何有順序地執行非同步函數