主页 > 创业  > 

async/await:在前端开发中的应用

async/await:在前端开发中的应用

目录

1. async await 

async

await

总结: 

2. 为什么用async / await 

3. 代码示例说明

3.1  Promise.all 

3.2  用 async await 改写 Promise.all

4.  async/await可以解决哪些异步问题

5.  哪些会产生回调地狱的问题

6.  为了避免回调地狱的问题,可以采用以下几种方法

6.1  使用传统回调函数方式导致的回调地狱:

6.2  使用Promise链式调用避免回调地狱


1. async await  async ‌定义‌:async 是一个用于声明异步函数的关键字。当一个函数被 async 修饰时,它会自动返回一个 Promise 对象。如果函数内部显式地返回了一个非 Promise 值,那么这个值会被自动包装成一个已解决的 Promise 对象。‌用法‌:将 async 关键字放在函数声明或函数表达式之前。 async function myFunction() { return 'Hello, world!'; } myFunction().then(console.log); // 输出: Hello, world! await ‌定义‌:await 是一个用于等待 Promise 解决(resolve)或拒绝(reject)的操作符。它只能在 async 函数内部使用,并且会暂停 async 函数的执行,直到等待的 Promise 有结果。‌用法‌:将 await 关键字放在 Promise 调用或任何返回 Promise 的表达式之前。‌返回值‌:如果 Promise 被解决,await 表达式的结果就是 Promise 解决的值。如果 Promise 被拒绝,await 表达式会抛出一个异常,这个异常可以被 try...catch 语句捕获。 async function fetchData() { try { let response = await fetch(' api.example /data'); let data = await response.json(); console.log(data); } catch (error) { console.error('Error:', error); } } fetchData();

总结:  async、await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。使用 async和 await可以让异步代码看起来几乎和同步代码一样,从而大大简化了代码的结构和可读性。你不再需要嵌套回调函数或链接多个Promise,代码变得更加直观和线性。async 用来声明一个 function 是异步的,await 用来等待一个异步方法执行完成。await 只能出现在 async 函数中。 下面图片:async 声明的函数,返回的结果是一个 Promise 对象。如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。

下图:如果 async 函数没有返回值

await 等待的是它右侧的一个表达式的返回值。这个表达式的计算结果是 Promise 对象或者其他值。

await 只能用于 Promise,如果你尝试在非 Promise 上使用 await,它会导致运行时错误。async/await 不会阻塞主线程,它们是基于事件循环的,允许其他代码在异步操作等待期间继续执行。在 async 函数内部,你可以使用多个 await 表达式,它们会按顺序执行。错误处理通常通过 try...catch 语句来实现,以捕获 await 表达式可能抛出的异常。

async/await 使得异步代码更加清晰和易于理解,它减少了回调地狱和 Promise 链的复杂性,是现代 JavaScript 开发中处理异步操作的首选方法。

注:Promise.resolve(x) 等价于 new Promise(resolve => resolve(x)),用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。

2. 为什么用async / await 

async和await是JavaScript中用于处理异步操作的现代方法,它们提供了一种更简洁、更易读的方式来写异步代码,相比于传统的回调函数(callbacks)和Promise链,具有显著的优势。以下是使用async和await的几个主要原因:

‌简洁性‌:

使用async和await可以让异步代码看起来几乎和同步代码一样,从而大大简化了代码的结构和可读性。你不再需要嵌套回调函数或链接多个Promise,代码变得更加直观和线性。

‌错误处理‌:

使用try...catch语句可以很容易地捕获await表达式中抛出的异常,这使得错误处理更加直接和集中。相比之下,在Promise链中处理错误可能需要多个.catch()方法,或者在回调函数中手动检查错误。

‌调试方便‌:

由于async函数在执行时会返回一个Promise,并且可以在任何await表达式处暂停,这使得在调试器中步进代码时更容易理解异步操作的执行流程。你可以逐步执行代码,看到每个异步操作的结果,而不需要跳过复杂的回调或Promise逻辑。

‌并发执行‌:

虽然await会暂停async函数的执行,等待Promise的解决,但你可以通过不在每个异步操作后都使用await,而是将它们存储在变量中,然后在需要时一起等待它们解决(例如使用Promise.all()),来实现并发执行 3. 代码示例说明 3.1  Promise.all 

        是 JavaScript 中用于处理多个 Promise 对象的一个方法。它接受一个包含多个 Promise 的数组作为输入,并且只有当这个数组中的所有 Promise 都成功完成时,它才会返回一个新的 Promise,该 Promise 的结果是一个包含所有原 Promise 结果的数组。如果任何一个 Promise 失败了,Promise.all 返回的 Promise 会立即被拒绝,返回那个失败的 Promise 的原因。

const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'first'); }); const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 200, 'second'); }); const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 300, 'third'); }); Promise.all([promise1, promise2, promise3]) .then((values) => { console.log(values); // ['first', 'second', 'third'] }) .catch((error) => { console.error(error); });

创建了三个 Promise,它们分别在 100 毫秒、200 毫秒和 300 毫秒后完成。我们使用 Promise.all 来等待这三个 Promise 全部完成,然后打印出它们的结果。由于所有的 Promise 都成功完成了,所以 Promise.all 返回的 Promise 也成功了,并且它的结果是一个包含所有原 Promise 结果的数组。

如果其中一个 Promise 失败了,比如我们修改 promise2 让它被拒绝:

const promise2 = new Promise((resolve, reject) => { setTimeout(reject, 200, 'Error in second promise'); });

那么 Promise.all 返回的 Promise 会立即被拒绝,并且它的拒绝原因会是那个失败的 Promise 的原因,即 'Error in second promise'。在这个情况下,then 方法不会被调用,而是会调用 catch 方法来处理错误。

3.2  用 async await 改写 Promise.all function waitFor(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async functiom fetchAllPromise () { try { const first = await waitFor(100).then(() => 'first') const second = await waitFor(100).then(() => 'second') const third = await waitFor(100).then(() => 'third') // 这里所有的await都顺序执行了,因此会按照100ms, 200ms, 300ms的顺序等待 // 然后打印出 'first', 'second', 'third' console.log([first, second, third]); } catch { console.error('An error occurred:', error); } } fetchAllPromises();

按照上述这样写,fetchAllPromise 函数中,因为每个 await 都会顺序地等待前一个 Promise 解决后再继续执行下一个。为了并发地等待所有 Promise,我们应该先启动所有的等待任务,然后再使用 await 等待它们全部完成。这可以通过将 Promise 存储在数组中,然后使用 Promise.all 来实现:

function waitFor(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function fetchAllPromisesConcurrently() { try { const promise1 = waitFor(100).then(() => 'first'); const promise2 = waitFor(200).then(() => 'second'); const promise3 = waitFor(300).then(() => 'third'); // 并发等待所有Promise const results = await Promise.all([promise1, promise2, promise3]); // 打印所有结果 console.log(results); // ['first', 'second', 'third'] } catch (error) { console.error('An error occurred:', error); } } fetchAllPromisesConcurrently();

在这个例子中,fetchAllPromisesConcurrently 函数会并发地启动三个等待任务,并且使用 Promise.all 来等待它们全部完成。由于 Promise.all 是并发执行的,所以它会等待最长时间的那个 Promise(在这个例子中是 300 毫秒的 promise3)完成后,再一次性返回所有结果。这样,我们就可以更高效地处理多个异步操作了。

4.  async/await可以解决哪些异步问题

‌避免阻塞线程或进程‌: 使用 async/await 可以避免在等待异步操作完成时阻塞线程或进程,使得应用程序能够同时执行多个异步操作,提高了程序的并发性和响应性。

‌简化代码结构‌: async/await 提供了一种更直观、易于理解的代码结构,使开发者能够以顺序方式编写异步代码,而不是嵌套回调函数。这大大简化了代码结构,使代码更加简洁和易于维护1。

‌简化错误处理和异常传播‌: 使用 async/await 可以简化错误处理和异常传播的过程。通过结合 try...catch 语句,开发者可以方便地捕获和处理异步操作中的错误,使得异常处理更加清晰和可维护1。

‌减少回调地狱‌: async/await 可以减少回调地狱的问题。在传统的异步编程中,如果多个异步操作需要依次执行,往往会形成嵌套回调函数,导致代码结构复杂且难以维护。而 async/await 可以让异步代码看起来像同步代码一样,按照顺序执行多个异步操作,从而避免了回调地狱的问题。

‌提高代码的可读性和可维护性‌: async/await 支持了异步代码的可读性和可维护性。通过简化代码结构、减少回调地狱以及简化错误处理,async/await 使得异步代码更加清晰、易于理解和维护。

在使用 async/await 时也需要注意异步代码中的错误处理、死锁和资源管理等问题,以确保代码的正确性和性能。

5.  哪些会产生回调地狱的问题

‌多层嵌套的回调函数‌: 当多个异步操作需要按顺序执行时,每个操作的回调函数可能会嵌套在另一个回调函数中,形成多层嵌套的结构。随着嵌套层级的加深,代码的可读性和可维护性会急剧下降。

‌错误处理复杂‌: 在回调地狱中,每个异步操作可能都会失败,而错误处理通常需要在每个回调函数中单独进行。这不仅增加了代码的复杂度,也使得错误追踪和调试变得更加困难。

‌难以扩展和维护‌: 随着业务逻辑复杂度的增加,回调地狱中的代码会变得难以理解和维护。添加新功能或修改现有逻辑可能会变得非常困难,因为需要仔细处理多层嵌套的回调函数和错误处理逻辑。

‌代码结构不清晰‌: 回调地狱中的代码往往呈现出金字塔形状的结构,这使得代码的结构不清晰,难以阅读和理解。开发者需要花费更多的时间和精力来理解代码的执行流程和逻辑。

6.  为了避免回调地狱的问题,可以采用以下几种方法

‌使用Promise‌(下面有示例): Promise是一种用于处理异步操作的对象,它可以将嵌套的回调转换为链式的.then调用,从而避免回调地狱。

‌使用async/await‌(下面有示例): async/await是基于Promise的语法糖,它可以使异步代码看起来更像同步代码,进一步简化了异步编程。通过使用async/await,可以避免回调函数的嵌套,使代码更加简洁易读。

‌模块化和分解‌: 将复杂的业务逻辑拆分成更小的、独立的函数或方法,可以减少回调地狱的发生。每个函数或方法只处理一个特定的异步操作,并通过返回Promise来与其他函数或方法进行交互。

‌使用响应式编程库‌: 如Reactor或RxJava等响应式编程库提供了声明式的方式来处理异步流和事件,可以极大地简化复杂业务逻辑的处理,从而避免回调地狱的问题。

示例:先获取用户数据后才能获取订单数据

6.1  使用传统回调函数方式导致的回调地狱: // 假设有两个函数,getUserData和getUserOrders,它们都是异步的并且接受回调函数 function getUserData(callback) { setTimeout(() => { // 异步操作完成后,调用回调函数并传递结果 callback(null, { userId: 123, userName: 'John Doe' }); }, 1000); } function getUserOrders(userId, callback) { setTimeout(() => { callback(null, [ { orderId: 456, product: 'Laptop' }, { orderId: 789, product: 'Smartphone' } ]); }, 1000); } // 使用回调函数方式获取用户数据和订单数据 getUserData((err, userData) => { if (err) { console.error('Error getting user data:', err); return; } // 用户数据获取成功后,会进行订单数据的获取 getUserOrders(userData.userId, (err, orders) => { if (err) { console.error('Error getting user orders:', err); return; } console.log('User data:', userData); console.log('User orders:', orders); }); });

在这个例子中,有两个嵌套的回调函数。首先,我们调用getUserData来获取用户数据,然后在它的回调函数中调用getUserOrders来获取用户的订单数据。这种嵌套结构就是回调地狱的简化版。

使用async/await来重写上面的代码:

// 将异步函数转换为返回Promise的函数 // await 等待的是它右侧的一个表达式的返回值。这个表达式的计算结果是 Promise 对象或者其他值。 // await 只能用于 Promise,如果你尝试在非 Promise 上使用 await,它会导致运行时错误。 function getUserDataAsync() { return new Promise((resolve) => { setTimeout(() => { resolve({ userId: 123, userName: 'John Doe' }); }, 1000); }); } function getUserOrdersAsync(userId) { return new Promise((resolve) => { setTimeout(() => { resolve([{ orderId: 456, product: 'Laptop' }, { orderId: 789, product: 'Smartphone' }]); }, 1000); }); } // 使用async/await方式获取用户数据和订单数据 async function fetchUserDataAndOrders() { try { const userData = await getUserDataAsync(); // 获取用户 const orders = await getUserOrdersAsync(userData.userId); // 获取订单 console.log('User data:', userData); console.log('User orders:', orders); } catch (err) { console.error('An error occurred:', err); } } fetchUserDataAndOrders(); 6.2  使用Promise链式调用避免回调地狱

使用Promise是避免回调地狱的一种有效方法。Promise提供了一种更清晰、更线性的方式来处理异步操作,使得代码更易于阅读和维护。以下是如何使用Promise来避免回调地狱的步骤和示例:

‌将异步函数转换为返回Promise的函数‌: 如果你有一个使用回调函数的异步函数,你可以将其改写为返回一个Promise的函数。这样,你就可以使用.then()链式调用来处理异步结果,而不是嵌套回调函数。

‌使用.then()链式调用‌: Promise的.then()方法允许你在Promise解决(resolve)后执行一个回调函数,并且这个回调函数可以返回一个新的Promise,从而允许你链式调用多个异步操作。

‌处理错误‌: 使用.catch()方法来捕获Promise链中任何一步发生的错误,这样你就不需要在每个异步操作中都显式地处理错误了。

// 使用Promise方式的异步函数 function getUserData() { return new Promise((resolve, reject) => { // 模拟异步操作,比如网络请求或数据库查询 setTimeout(() => { // 异步操作完成后,调用resolve传递结果 resolve({ userId: 123, userName: 'John Doe' }); }, 1000); }); } function getUserOrders(userId) { return new Promise((resolve, reject) => { // 模拟异步操作 setTimeout(() => { // 异步操作完成后,调用resolve传递结果 resolve([ { orderId: 456, product: 'Laptop' }, { orderId: 789, product: 'Smartphone' } ]); }, 1000); }); } // 使用Promise链式调用获取用户数据和订单数据 getUserData() .then(userData => { // 在获取到用户数据后,再获取用户订单数据 return getUserOrders(userData.userId); }) .then(orders => { // 成功获取到用户数据和订单数据 console.log('User data:', userData); // 注意:这里的userData是从闭包中获取的,或者你可以在上一步中返回{ userData, orders } console.log('User orders:', orders); }) .catch(err => { // 处理任何一步发生的错误 console.error('Error:', err); });

在这个例子中,getUserData和getUserOrders函数都返回了一个Promise。我们使用.then()方法来链式调用这两个异步操作,并在最后使用.catch()方法来捕获任何可能发生的错误。这样,我们的代码就避免了回调地狱,变得更加清晰和易于维护。

标签:

async/await:在前端开发中的应用由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“async/await:在前端开发中的应用