You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效优雅地实现JavaScript脚本按顺序执行?

按顺序加载并执行脚本的高效优雅实现方式

问题背景

我需要按顺序执行多个脚本(比如3个,生产环境可能多达20个),每个后续脚本必须等前一个完全执行完毕才能启动。另外要注意:给脚本添加defer属性无法保证严格的执行顺序。

现有可行实现示例

下面是一个通过事件触发实现顺序加载的示例:

index.html

<html>
<head>
    <title>按顺序加载脚本</title>
    <script>
        const scripts = ["script1.js", "script2.js", "script3.js"]
        for (let i = 1; i < scripts.length; i++) {
            document.head.addEventListener(scripts[i - 1], () => {
                console.log(`完成处理 ${scripts[i - 1]}`)
                const nextScript = document.createElement("script")
                nextScript.src = scripts[i]
                document.head.appendChild(nextScript)
            })
        }
    </script>

    <!-- 加载第一个脚本,启动顺序加载流程 -->
    <script src="script1.js"></script>
</head>
<body>
    <h1>Hello world</h1>
</body>
</html>

script1.js

const foobar = async () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        }, 3000)
        setTimeout(() => {
            reject(new Error("未在规定时间内完成。"))
        }, 10000);
    })
}

foobar().then(() => {
    console.log("script1.js 加载执行完成")
    document.head.dispatchEvent(new Event("script1.js"))
})

script2.js

const foobar2 = async () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        }, 3000)
        setTimeout(() => {
            reject(new Error("未在规定时间内完成。"))
        }, 10000);
    })
}

foobar2().then(() => {
    console.log("script2.js 加载执行完成")
    document.head.dispatchEvent(new Event("script2.js"))
})

script3.js

console.log("hello world")

执行日志

script1.js 加载执行完成
完成处理 script1.js
script2.js 加载执行完成
完成处理 script2.js
hello world

更高效优雅的实现方案

上面的事件监听方式虽然可行,但当脚本数量达到20个时,每个脚本都要手动触发事件,维护成本会很高。下面推荐两种更优的实现方式:

1. Promise + async-await 循环

封装一个异步加载脚本的函数,通过async-await按顺序遍历脚本列表,代码简洁易读,无需每个脚本手动触发事件。

实现代码:

<html>
<head>
    <title>用async-await顺序加载脚本</title>
    <script>
        // 封装加载并执行脚本的异步函数
        async function loadScript(src) {
            return new Promise((resolve, reject) => {
                const script = document.createElement('script');
                script.src = src;
                // 脚本加载并执行完成后resolve
                script.onload = () => {
                    console.log(`脚本 ${src} 加载执行完成`);
                    resolve();
                };
                // 加载失败时reject
                script.onerror = () => reject(new Error(`加载脚本 ${src} 失败`));
                document.head.appendChild(script);
            });
        }

        // 按顺序加载所有脚本
        async function loadScriptsSequentially(scripts) {
            for (const script of scripts) {
                await loadScript(script);
            }
            console.log("所有脚本已按顺序加载完成");
        }

        // 脚本列表
        const scripts = ["script1.js", "script2.js", "script3.js", /* ... 其他脚本 */];
        // 启动加载流程
        loadScriptsSequentially(scripts).catch(err => console.error(err));
    </script>
</head>
<body>
    <h1>Hello world</h1>
</body>
</html>

脚本改造(以script1.js为例):

无需手动触发事件,只需保证脚本核心逻辑执行完成即可(若有异步逻辑,需等待异步操作结束):

const foobar = async () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        }, 3000)
        setTimeout(() => {
            reject(new Error("未在规定时间内完成。"))
        }, 10000);
    })
}

// 等待异步逻辑完成
await foobar();
console.log("script1.js 加载执行完成");

注意:如果脚本只有同步逻辑,直接保留原有代码即可,script.onload会在同步代码执行完毕后触发;如果有需要等待的异步逻辑,需确保脚本内的异步操作完成后,才算真正执行完毕。

2. 动态import(现代浏览器支持)

如果项目环境支持ES模块,可以使用import()函数动态加载脚本——它本身返回Promise,结合async-await能轻松实现顺序加载,代码最简洁。

实现代码:

<html>
<head>
    <title>用动态import顺序加载脚本</title>
    <script type="module">
        async function loadScriptsSequentially(scripts) {
            for (const script of scripts) {
                try {
                    await import(`./${script}`);
                    console.log(`脚本 ${script} 加载执行完成`);
                } catch (err) {
                    console.error(`加载脚本 ${script} 失败`, err);
                }
            }
            console.log("所有脚本已按顺序加载完成");
        }

        const scripts = ["script1.js", "script2.js", "script3.js", /* ... 其他脚本 */];
        loadScriptsSequentially(scripts);
    </script>
</head>
<body>
    <h1>Hello world</h1>
</body>
</html>

脚本改造(以script1.js为例):

改为ES模块,确保异步逻辑完成后脚本才算执行完毕:

export const init = async () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        }, 3000)
        setTimeout(() => {
            reject(new Error("未在规定时间内完成。"))
        }, 10000);
    })
}

await init();
console.log("script1.js 加载执行完成");

方案对比

  • 事件监听方式:兼容性好,但维护成本高,每个脚本需手动触发事件,适合老项目兼容场景。
  • Promise+async-await:代码简洁易读,无需大量修改原有脚本逻辑,兼容性较好(支持ES6的浏览器均可运行)。
  • 动态import:现代浏览器支持,代码最简洁,适合使用ES模块的新项目,天然支持异步加载与顺序执行。

内容的提问来源于stack exchange,提问作者Alexa Jenasoba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 22:05:21