使用 Promise 達到同步等待 js 資源載入


前言

瀏覽器解析 HTML 是一行一行依序向下讀取,在傳統的寫法中,當瀏覽器讀到 <script> 時,便會暫停解析 DOM,開始下載 <script> 的資源 ,並在下載完成後立刻執行。由於這樣的特性,便可能造成在 DOM 樹建構不完全時就執行 JavaScript,其中需要操作 DOM 的程式可能就因此無法正確運作 ;又或者是在等待 <script> 資源下載、執行的過程中,使用者便會卡在白畫面。

解決的方法便是把 <script> 的位置都放到 <body> 的最後一行,來避免在 DOM 樹未建構完就開始執行程式的問題 但如果HTML,JavaScript檔案非常大,如果等到DOM載入完成後才開始下載,那讀取完成到可操作的期間會造成明顯的延遲。

Promise 建構式

Promise 建構函式建立同時,必須傳入一個函式作為參數(executor function),此函式的參數包含 resolve, reject, 這兩個方法分別代表成功與失敗的回傳結果,特別注意這兩個僅能回傳其中之一,回傳後表示此 Promise 事件結束。


    new Promise(function(resolve, reject) { 
        resolve(); // 正確完成的回傳方法
        reject();  // 失敗的回傳方法
    });

結合script async

在HTML5新增的script async屬性,可用來幫助開發者控制 <script> 內資源的載入及執行順序,會在背景執行下載,當下載完成會馬上暫停 DOM 解析 ,並開始執行 JavaScript。也因為下載完成後會立即執行,加上 async 屬性後,就無法保證執行順序了,但可以透過屬性設定將它關閉。


    let scripts = [`你的js路徑..`,`你的js路徑..`,...];
    await Promise.all(scripts.map(async (url) => {
        await loadScript(url);
    }));

    function loadScript(url) {
        return new Promise(function (resolve, reject) {
            let script = document.createElement('script');
            script.src = url;
            script.async = false; // 讓瀏覽器保持我們設定的 js 順序

            // 判斷 JS 是否載入完成
            script.onload = function () {
                console.log('JS Loading succ:', url);
                resolve(url);
            };
            script.onerror = function () {
                console.log('JS Loading error:', url);
                reject(url);
            };

            document.body.appendChild(script);
        });
    }
An unhandled error has occurred. Reload 🗙