JavaScript 与并发:轻松理解线程、协程和 async/await

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

一个线程统治一切:关于 JavaScript 并发的迷思与真相

\n\n

很久以前,在静态网站和会阻塞整个界面的“提交”按钮时代,JavaScript 快乐地生活在一个单线程中。这对它来说已经足够了:请求来了——执行,请求走了——界面冻结。但时代变了。如今,我们期望 Web 应用能即时响应:滚动不能卡顿,动画必须流畅,数据加载要无感。这时,并发就登场了。

\n\n

许多开发者会混淆并发 (concurrency) 和并行 (parallelism)。在 JavaScript 中,尤其是在浏览器中,我们更多处理的是前者。这就像一个杂耍演员:他并不是同时让所有球都停留在空中(那是并行),而是通过巧妙地切换,制造出同时进行的假象。在本文中,我们将探讨在 JS 中组织并发的三个关键工具:async/await、协程(通过生成器和 Web Workers)以及经典线程。开始吧!

\n\n

Async/Await:优雅的异步代码

\n\n

async/await 是 ES2017 中引入的、基于 Promise 的语法糖。它不会让代码变成多线程,但会让代码变得异步。当你写 await 时,JavaScript 会在这个函数中“休眠”,从而释放线程去处理其他任务。一旦 Promise 被解决,函数就会“醒来”并继续执行。这是处理 I/O 操作(如 API 请求、文件读取、定时器)的理想工具。

\n\n

简单示例:从服务器加载数据

\n\n
// 返回 Promise 的函数\nfunction fetchUser(id) {\n    return new Promise((resolve) => {\n        setTimeout(() => resolve({ id, name: '阿列克谢' }), 1000);\n    });\n}\n\n// 异步函数\nasync function loadUserProfile(userId) {\n    console.log('开始加载...');\n    const user = await fetchUser(userId);\n    console.log('用户已加载:', user.name);\n    return user;\n}\n\nloadUserProfile(42);\nconsole.log('这段代码会立即执行,无需等待加载完成!');\n
\n\n

请注意:console.log('这段代码会立即执行...') 会比 console.log('用户已加载') 先执行。线程没有被阻塞——这就是并发的实际体现。

\n\n

错误处理

\n\n
async function fetchWithRetry(url, retries = 3) {\n    for (let i = 0; i < retries; i++) {\n        try {\n            const response = await fetch(url);\n            if (!response.ok) throw new Error('HTTP 错误');\n            return await response.json();\n        } catch (error) {\n            if (i === retries - 1) throw error;\n            console.log(`第 ${i + 1} 次尝试失败,正在重试...`);\n        }\n    }\n}\n
\n\n

何时使用:任何涉及外部资源的操作(API、数据库、Node.js 中的文件系统)。async/await 是您编写异步代码的主要工具。

\n\n

JavaScript 中的协程:神话还是现实?

\n\n

“协程”这个术语来自 Go 语言,指的是一种轻量级的执行线程。JavaScript 没有原生的协程,但拥有生成器 (Generator Functions) 和 Web Workers,它们可以模拟类似的行为。生成器允许暂停和恢复函数的执行,从而让您能够控制执行流程。

\n\n

生成器作为并发的基础

\n\n
function* taskGenerator() {\n    console.log('启动任务 1');\n    yield '任务 1 完成';\n    console.log('启动任务 2');\n    yield '任务 2 完成';\n    console.log('启动任务 3');\n    yield '任务 3 完成';\n}\n\nconst iterator = taskGenerator();\nconsole.log(iterator.next().value); // 执行任务 1\n// 此时可以做一些其他事情\nconsole.log(iterator.next().value); // 执行任务 2\nconsol                

博客

书籍推荐