个人网站logo需要备案吗,南京网站c建设云世家,请人做外贸网站应注意什么问题,网站源码上传安装本文介绍了异步相关的内容#xff0c;包括#xff1a;
回调函数与回调地狱Promise详解async/await语法Generator函数事件循环机制异步编程最佳实践
1、回调函数与回调地狱
JavaScript最初是为处理网页交互而设计的语言#xff0c;异步编程是其核心特性之一。最早的异步编…本文介绍了异步相关的内容包括
回调函数与回调地狱Promise详解async/await语法Generator函数事件循环机制异步编程最佳实践
1、回调函数与回调地狱
JavaScript最初是为处理网页交互而设计的语言异步编程是其核心特性之一。最早的异步编程方式是使用回调函数。
什么是回调函数
回调函数是作为参数传递给另一个函数的函数并在特定事件发生后执行。 // 基本回调示例function fetchData(callback) {setTimeout(() {const data { name: 张三, age: 30 };callback(data);}, 1000);}fetchData(function(data) {console.log(数据获取成功:, data);});
回调函数使我们能够非阻塞地执行代码这对于网络请求、文件操作等耗时任务尤为重要。
回调地狱问题
当多个异步操作需要依次执行时回调函数会嵌套在一起形成所谓的回调地狱(Callback Hell) fetchUserData(function(user) {console.log(获取用户信息:, user);fetchUserPosts(user.id, function(posts) {console.log(获取用户文章:, posts);fetchPostComments(posts[0].id, function(comments) {console.log(获取文章评论:, comments);fetchCommentAuthor(comments[0].authorId, function(author) {console.log(获取评论作者:, author);// 还可以继续嵌套...});});});});
回调地狱带来的问题 代码可读性差形成金字塔结构 错误处理复杂 变量作用域容易混淆 代码维护困难
2、Promise详解
Promise是JavaScript中解决回调地狱的第一个标准方案ES6正式将其纳入规范。
Promise的基本概念
Promise是一个代表异步操作最终完成或失败的对象。它有三种状态 pending: 初始状态既未完成也未失败 fulfilled: 操作成功完成 rejected: 操作失败
一旦Promise状态改变就不能再变这就是承诺的含义。 // 创建Promiseconst promise new Promise((resolve, reject) {// 异步操作setTimeout(() {const success Math.random() 0.5;if (success) {resolve(操作成功); // 成功时调用} else {reject(操作失败); // 失败时调用}}, 1000);});// 使用Promisepromise.then(result {console.log(result); // 操作成功}).catch(error {console.log(error); // 操作失败}).finally(() {console.log(无论成功失败都会执行);});
Promise链式调用
Promise的优势之一是支持链式调用可以优雅地处理依赖于前一个异步操作结果的多个异步操作 fetchUserData(userId).then(user {console.log(用户数据:, user);return fetchUserPosts(user.id); // 返回新的Promise}).then(posts {console.log(用户文章:, posts);return fetchPostComments(posts[0].id);}).then(comments {console.log(文章评论:, comments);return fetchCommentAuthor(comments[0].authorId);}).then(author {console.log(评论作者:, author);}).catch(error {// 捕获链中任何位置的错误console.error(发生错误:, error);});
这种链式写法将原本嵌套的回调拍平提高了代码的可读性。
Promise常用方法
Promise类提供了几个实用的静态方法
Promise.all()
Promise.all(): 并行执行多个Promise当所有Promise都成功时返回结果数组 // 同时发起多个请求const promises [fetch(https://api.example.com/users),fetch(https://api.example.com/posts),fetch(https://api.example.com/comments)];Promise.all(promises).then(responses Promise.all(responses.map(res res.json()))).then(data {const [users, posts, comments] data;console.log(users, posts, comments);}).catch(error {// 任一请求失败都会进入catchconsole.error(至少有一个请求失败:, error);});
Promise.race()
Promise.race(): 返回最先解决或拒绝的Promise结果 // 实现超时功能function fetchWithTimeout(url, ms) {const fetchPromise fetch(url);const timeoutPromise new Promise((_, reject) {setTimeout(() reject(new Error(请求超时)), ms);});return Promise.race([fetchPromise, timeoutPromise]);}fetchWithTimeout(https://api.example.com/data, 3000).then(response response.json()).then(data console.log(data)).catch(error console.error(error));
Promise.allSettled()
Promise.allSettled(): ES2020引入等待所有Promise完成(无论成功或失败) Promise.allSettled([Promise.resolve(1),Promise.reject(错误),Promise.resolve(3)]).then(results {console.log(results);// [// { status: fulfilled, value: 1 },// { status: rejected, reason: 错误 },// { status: fulfilled, value: 3 }// ]});
Promise.any()
Promise.any(): ES2021引入返回首个成功的Promise结果 // 尝试从多个源获取数据返回最先成功的Promise.any([fetch(https://api-1.example.com/data).then(r r.json()),fetch(https://api-2.example.com/data).then(r r.json()),fetch(https://api-3.example.com/data).then(r r.json())]).then(data console.log(获取到数据:, data)).catch(errors console.error(所有请求均失败:, errors));
4、Generator函数
Generator函数是ES6引入的新特性它允许函数在执行过程中暂停和恢复这使得它特别适合实现异步控制流。
Generator基础
Generator函数在声明时使用星号(*标记内部使用yield关键字暂停执行 function* numberGenerator() {yield 1;yield 2;yield 3;}const gen numberGenerator();console.log(gen.next()); // { value: 1, done: false }console.log(gen.next()); // { value: 2, done: false }console.log(gen.next()); // { value: 3, done: false }console.log(gen.next()); // { value: undefined, done: true }
生成器返回一个迭代器调用next()方法会执行代码直到遇到下一个yield语句。
使用Generator实现异步流程控制
Generator可以用来处理异步操作但通常需要一个运行器函数 function fetchData(url) {return new Promise((resolve, reject) {setTimeout(() {if (Math.random() 0.3) {resolve(来自${url}的数据);} else {reject(获取${url}失败);}}, 1000);});}function* fetchSequence() {try {const user yield fetchData(/api/user);console.log(user);const posts yield fetchData(/api/posts);console.log(posts);const comments yield fetchData(/api/comments);console.log(comments);return 所有数据获取完成;} catch (error) {console.error(出错了:, error);return 数据获取过程出错;}}// 手动运行生成器function runGenerator(generatorFn) {const generator generatorFn();function handle(result) {if (result.done) return Promise.resolve(result.value);return Promise.resolve(result.value).then(res handle(generator.next(res))).catch(err handle(generator.throw(err)));}return handle(generator.next());}runGenerator(fetchSequence).then(result console.log(result)).catch(err console.error(err));
4、async/await语法
尽管Promise已经比回调函数有了很大改进但ES2017引入的async/await语法进一步简化了异步编程使异步代码看起来更像同步代码。
async/await基础 async声明一个异步函数它会返回一个Promise await暂停异步函数的执行等待Promise解决
实现原理
1生成器与迭代器
async/await 的核心原理是利用生成器函数Generator的暂停和恢复能力
function* genFunc() {yield 1;yield 2;
}生成器可以通过 yield 暂停执行并在之后通过 next() 恢复执行。
2Promise 结合
async/await 将 Generator 与 Promise 结合
async 标记的函数总是返回 Promiseawait 操作会暂停函数执行等待 Promise 完成
3自动执行器
关键环节是一个自动执行器负责
执行生成器函数当遇到 yield 时暂停等待 Promise 解决将结果传回生成器并恢复执行
简化版实现
一个简化的 async/await 实现可以是
function asyncToGenerator(generatorFunc) {return function() {const gen generatorFunc.apply(this, arguments);return new Promise((resolve, reject) {function step(key, arg) {let result;try {result gen[key](arg);} catch (error) {return reject(error);}const { value, done } result;if (done) {return resolve(value);} else {return Promise.resolve(value).then(val step(next, val),err step(throw, err));}}step(next);});};
}Babel 转译示例
以下是 Babel 如何将 async/await 转译为 ES5 代码简化版
// 原始 async 函数
async function foo() {const result await someAsyncFunc();return result;
}// 转译后
function foo() {return _asyncToGenerator(function* () {const result yield someAsyncFunc();return result;});
}工作流程
当调用 async 函数时自动创建一个 Promise 对象函数体内代码正常执行直到遇到 await 表达式await 表达式会暂停当前函数执行await 后的表达式会被转换成 Promise如果不是已经是 Promise当该 Promise 完成时恢复函数执行并返回 Promise 的结果如果 Promise 被拒绝await 表达式会抛出错误 // 使用async/await重写前面的例子async function getUserInfo(userId) {try {const user await fetchUserData(userId);console.log(用户数据:, user);const posts await fetchUserPosts(user.id);console.log(用户文章:, posts);const comments await fetchPostComments(posts[0].id);console.log(文章评论:, comments);const author await fetchCommentAuthor(comments[0].authorId);console.log(评论作者:, author);return author;} catch (error) {console.error(发生错误:, error);}}// 调用异步函数getUserInfo(123).then(result console.log(最终结果:, result));
相比Promise链async/await的优势 代码结构清晰接近同步写法 便于使用条件语句和循环 易于进行错误处理 调试更简单
并行执行
虽然await会暂停函数执行但有时我们需要并行执行多个异步操作 async function fetchAllData() {// 错误示范串行执行效率低const users await fetchUsers();const posts await fetchPosts();const comments await fetchComments();// 正确示范并行执行const [users, posts, comments] await Promise.all([fetchUsers(),fetchPosts(),fetchComments()]);return { users, posts, comments };}
错误处理
async函数中可以使用try/catch来捕获错误也能捕获await的Promise拒绝 async function fetchWithErrorHandling() {try {const response await fetch(https://api.example.com/data);if (!response.ok) {throw new Error(HTTP错误: ${response.status});}const data await response.json();return data;} catch (error) {console.error(获取数据失败:, error);// 可以返回默认值return { error: true, message: error.message };}}
Generator vs async/await
在ES2017引入async/await之前Generator曾经是实现异步控制流的重要工具。现在async/await基本上取代了Generator在异步编程中的角色因为 async/await是基于Generator和Promise的语法糖更易于使用 async函数无需运行器浏览器原生支持 错误处理更加直观
然而Generator在某些场景如惰性计算、状态机实现中仍然非常有用。
5、事件循环机制
要真正理解JavaScript的异步编程必须了解底层的事件循环机制。JavaScript是单线程的依靠事件循环来处理异步操作。
事件循环的关键组件
事件循环机制涉及以下几个关键组件 执行栈(Call Stack)管理函数调用的栈结构遵循后进先出原则 宏任务队列(Macrotask Queue)存放宏任务如setTimeout、setInterval、I/O等 微任务队列(Microtask Queue)存放微任务如Promise回调、MutationObserver等 事件循环(Event Loop)持续检查执行栈和任务队列的循环过程
宏任务与微任务
宏任务(Macrotask)包括 script(整体代码) setTimeout/setInterval setImmediate(Node.js环境) I/O操作 UI渲染(浏览器) requestAnimationFrame(浏览器)
微任务(Microtask)包括 Promise.then/catch/finally MutationObserver process.nextTick(Node.js环境) queueMicrotask()
事件循环的基本流程
(1) 开始执行第一个宏任务即全局代码(script)
(2) 同步代码执行 所有同步代码进入执行栈按顺序执行 如遇异步API其回调函数被分发到对应的任务队列中
(3) 执行栈清空
同步代码执行完毕执行栈清空
(4) 处理微任务 检查微任务队列有微任务则依次执行所有微任务 执行过程中产生的新微任务也会在当前循环中执行
(5) UI渲染(仅浏览器环境)
如有必要进行页面渲染更新
(6) 处理宏任务 从宏任务队列取出一个任务执行 执行完后返回步骤3检查微任务队列
(7) 循环往复
事件循环无限继续直到所有任务队列清空
事件循环流程图 实际例子解析 console.log(1. 开始); // 同步代码setTimeout(() {console.log(2. 第一个宏任务);Promise.resolve().then(() {console.log(3. 宏任务中的微任务);});}, 0);Promise.resolve().then(() {console.log(4. 第一个微任务);setTimeout(() {console.log(5. 微任务中的宏任务);}, 0);});console.log(6. 结束); // 同步代码// 输出顺序: 1 - 6 - 4 - 2 - 3 - 5
(1) 第一个宏任务(script全局代码) 执行同步代码打印1. 开始 遇到setTimeout其回调被添加到宏任务队列 遇到Promise.then其回调被添加到微任务队列 执行同步代码打印6. 结束 同步代码执行完毕执行栈清空
(2) 检查微任务队列 执行微任务打印4. 第一个微任务 遇到setTimeout其回调被添加到宏任务队列 微任务队列清空
(3) 进行UI渲染(如需)
(4) 取出下一个宏任务 执行第一个setTimeout的回调打印2. 第一个宏任务 遇到Promise.then其回调被添加到微任务队列
(5) 再次检查微任务队列 执行微任务打印3. 宏任务中的微任务 微任务队列清空
(6) 进行UI渲染(如需)
(7) 取出下一个宏任务
执行第二个setTimeout的回调打印5. 微任务中的宏任务
关于async/await在事件循环中的位置
前面讲到async/await 就是生成器和Promise的语法糖它的工作流程中讲到await 表达式会暂停当前函数执行await 后的表达式会被转换成 Promise如果不是已经是 Promise所以
当函数遇到 await 时会将后续代码作为微任务放入事件循环这就是为什么 await 之后的代码总是在当前同步代码执行完毕后执行
6、异步编程最佳实践
使用Promise而非回调
所有新代码应该优先使用Promise API而非传统回调 // 不推荐function fetchData(callback) {setTimeout(() {callback(null, { data: success });}, 1000);}// 推荐function fetchData() {return new Promise((resolve) {setTimeout(() {resolve({ data: success });}, 1000);});}
优先使用async/await
对于大多数异步操作使用async/await可以使代码更清晰 // Promise链function getUserData(userId) {return fetchUser(userId).then(user {return fetchPosts(user.id).then(posts {user.posts posts;return user;});});}// 使用async/awaitasync function getUserData(userId) {const user await fetchUser(userId);user.posts await fetchPosts(user.id);return user;}
正确处理错误
异步代码中的错误处理尤为重要 // Promise错误处理fetchData().then(data processData(data)).then(result displayResult(result)).catch(error {console.error(发生错误:, error);showErrorMessage(error);});// async/await错误处理async function handleData() {try {const data await fetchData();const result await processData(data);displayResult(result);} catch (error) {console.error(发生错误:, error);showErrorMessage(error);}}
避免嵌套async函数
当不需要等待内部异步操作时避免嵌套async函数 // 不好的实践async function processItems(items) {const results [];for (const item of items) {// 没必要使用async函数results.push(await (async () {const data await fetchData(item.id);return processData(data);})());}return results;}// 更好的实践async function processItems(items) {const results [];for (const item of items) {const data await fetchData(item.id);results.push(processData(data));}return results;}
合理使用Promise并行执行
当多个异步操作相互独立时应该并行执行它们 // 低效方式串行执行async function loadData() {const users await fetchUsers();const products await fetchProducts();const categories await fetchCategories();return { users, products, categories };}// 高效方式并行执行async function loadData() {const [users, products, categories] await Promise.all([fetchUsers(),fetchProducts(),fetchCategories()]);return { users, products, categories };}
避免不必要的async/await
不是所有返回Promise的函数都需要async关键字 // 不必要的asyncasync function getData() {return fetch(/api/data).then(r r.json());}// 简化版本function getData() {return fetch(/api/data).then(r r.json());}
使用Promise工具方法
利用Promise提供的静态方法简化常见任务 // 并行请求并使用所有结果Promise.all([fetchUsers(), fetchPosts(), fetchComments()]).then(([users, posts, comments]) {// 处理所有数据});// 超时处理function fetchWithTimeout(url, timeout 5000) {return Promise.race([fetch(url),new Promise((_, reject) {setTimeout(() reject(new Error(请求超时)), timeout);})]);}// 任一请求成功即可function fetchFromMultipleSources(urls) {return Promise.any(urls.map(url fetch(url)));}
编写可测试的异步代码
良好的异步代码应该易于测试 // 可测试的异步函数async function processUserData(userId) {const user await fetchUser(userId);if (!user) {throw new Error(用户不存在);}user.lastActive new Date();return saveUser(user);}// 测试代码test(processUserData成功处理用户, async () {// 使用mock替换真实APIfetchUser jest.fn().mockResolvedValue({ id: 1, name: 张三 });saveUser jest.fn().mockResolvedValue({ success: true });const result await processUserData(1);expect(result.success).toBe(true);expect(saveUser).toHaveBeenCalledWith(expect.objectContaining({id: 1,lastActive: expect.any(Date)}));});
总结
JavaScript异步编程经历了从回调函数、Promise、Generator到async/await的演进。这些技术的发展使得异步代码越来越接近同步代码的直观性和可维护性同时保留了非阻塞执行的优势。
理解事件循环机制是掌握JavaScript异步编程的关键它解释了不同类型任务的执行顺序。在实际开发中合理选择异步编程技术、遵循最佳实践可以帮助我们编写出高效、可靠和易于维护的异步代码。