async 异步并发的终极解决方案

本文详细解释了在JavaScript中如何使用async函数和try-catch处理异步操作中的错误,探讨了try-catch在异步编程中的必要性,介绍了Promise的各种用法如Promise.all、Promise.race以及如何使用await和to函数简化错误处理。
摘要由CSDN通过智能技术生成

1、使用async表示异步函数,如果使用try-catch

在异步函数(通常在JavaScript中用关键字 async 表示)中,如果您使用 try-catch 块处理异常,那么 catch 块中的代码会在捕获到异常时执行,就像在同步函数中一样。当 catch 块执行完毕后,除非你在 catch 块中使用 return 语句提前退出函数或者抛出另一个异常,否则 catch 块后面的代码也会继续执行。

直接看图控制台直接执行。

async function asyncFunction() {
    try {
        // 可能抛出错误的异步操作
        const result = await Promise.all([new Promise((reject,rej)=>{setTimeout(()=>{rej('cuowu')},4000)})])
        console.log('操作成功:', result);
    } catch (error) {
        // 如果上述异步操作抛出错误,控制权将移动到这个 catch 块
       console.error('捕获到错误:', error);
    }

    // `catch` 块后面的代码,在 `try` 块成功或 `catch` 坐藏错误之后继续执行
    console.log('执行 `catch` 块后面的代码');
}

asyncFunction();

2、异步编程中为什么要使用try-catch

在异步编程中使用try-catch主要是为了处理错误和异常。异步代码,尤其是在处理网络请求、文件操作、数据库交互或其他可能导致失败的操作时,要考虑到操作可能不会按预期成功完成。使用try-catch可以优雅地捕获并处理这些潜在的错误,而不是让程序崩溃或进入不稳定的状态。

下面总结了为什么在异步编程中使用try-catch的几个理由:

  1. 错误处理:
    try-catch能够捕获异步代码块(例如,在async函数中使用的await表达式)中发生的错误。没有适当的错误处理,程序可能会因未处理的异常而终止,导致不可预测的行为。

  2. 可读性和维护性:
    使用try-catch可以在发生错误时提供清晰的结构,使代码更易于理解。这有助于其他开发者(或未来的你)更好地维护和调试代码。

  3. 提供更详细的错误上下文:
    使用try-catch可以捕获错误,并根据错误出现的上下文添加额外的错误信息或日志,这将帮助调试和解决问题。

  4. 故障恢复:
    try-catch不仅可以捕获错误,还可以让你编写代码来响应错误。这意味着你可以决定在出现问题时如何优雅地恢复,例如回滚事务、重试异步操作或是通知用户错误发生。

  5. 保护程序的其他部分:
    如果某个异步操作失败了,使用try-catch可以阻止错误影响到程序的其他部分。这是良好的隔离实践,保证程序的鲁棒性。

  6. 遵守最佳实践:
    在编程中,错误处理是一种最佳实践,try-catch提供了一种结构化的方式来实现这一点。

async function fetchUserData(userId) {
    try {
        const response = await fetch(`/users/${userId}`);
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        const userData = await response.json();
        return userData;
    } catch (error) {
        console.error('Could not fetch user data:', error);
        // 可以在这里处理错误、重试或返回备用数据
    }
}

在这个例子中,如果网络请求失败了,或者fetchresponse.json()在解析期间发生了错误,它们会被catch块内的代码捕获并处理。

重要的是要注意,并不是所有的异步错误都可以用try-catch捕获。特别地,传统的回调风格的异步函数(如setTimeout,或Node.js中的文件操作)在回调中的错误不会被普通的try-catch捕获。对于这种情况,你需要在每个回调函数中自己处理错误,或者使用Promise来包装回调,然后再使用try-catch

3、异步的并行处理方案

  1. Promise.all:
    使用Promise.all可以并行执行多个Promise。当所有的Promise都成功解决时,Promise.all会返回一个包含所有结果的数组。如果任何Promise被拒绝,则整个Promise.all调用立即失败,返回第一个拒绝的原因。

    示例代码:

    Promise.all([asyncTask1(), asyncTask2(), asyncTask3()]) .then(([result1, result2, result3]) => { console.log('所有任务完成', result1, result2, result3); }) .catch(error => { console.error('任务失败', error); });
    
  2. Promise.allSettled:
    类似于Promise.all,但是它等待所有的Promise都已经完成,无论是解决(fulfilled)还是被拒绝(rejected),并返回一个包含其结果(成功或错误)的数组。

    示例代码:

    Promise.allSettled([asyncTask1(), asyncTask2(), asyncTask3()]) .then(results => { results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`任务${index + 1}完成:`, result.value); } else { console.log(`任务${index + 1}失败:`, result.reason); } }); });
    
  3. Promise.race:
    Promise.race接受多个Promise作为输入,并返回一个新的Promise。这个新的Promise会在原有的Promise中的任何一个最先解决或被拒绝时就立即解决或被拒绝。

    示例代码:

    Promise.race([asyncTask1(), asyncTask2(), asyncTask3()]) .then(result => { console.log('最快的任务完成了', result); }) .catch(error => { console.error('最快的任务失败了', error); });
    
  4. 并行执行无关联任务:
    如果你有多个任务需要并行执行,但是你不需要等待它们全部完成,你可以单独调用它们而不用组合它们的结果。

    asyncTask1().then(result1 => console.log(result1)); 
    asyncTask2().then(result2 => console.log(result2)); 
    asyncTask3().then(result3 => console.log(result3));
    
  5. 利用async/await以并发方式运行循环中的异步任务:
    如果你在处理一个循环,并且每次迭代是一个异步操作,则可以收集所有的Promise,然后用Promise.all来等待它们全部完成。

    async function runAsyncTasks(tasks) { const promises = tasks.map(async (task) => { return await task(); }); const results = await Promise.all(promises); console.log(results); }
    

4、asyc中的try-catch为什么能捕获promise中的异常

在JavaScript中,使用async函数时,可以通过await关键字暂停函数的执行直到一个Promise被解决(即fulfilled)或拒绝(即rejected)。async函数中的try-catch语句能够捕获Promise中的异常,是因为当使用await时,如果Promise被拒绝,则会抛出一个异常,这个异常可以被try-catch语句捕获。

这实际上是async/await语法糖如何工作的一部分。在底层,await表达式等待一个Promise对象,并将其余的async函数体封装在Promise的.then().catch()调用中。如果在等待的Promise被拒绝,则内部生成的Promise会被拒绝,并抛出一个异常,这个异常可以被函数体中的try-catch语句捕获。

async function asyncFunction() {
    try {
        // 如果这个Promise被拒绝(rejected),await会抛出一个异常
        const result = await mightRejectPromise();
        console.log('操作成功:', result);
    } catch (error) {
        // 这里捕获到由await抛出的异常
        console.error('发生错误:', error);
    }
}

function mightRejectPromise() {
    // 返回一个有可能被拒绝的Promise
    return new Promise((resolve, reject) => {
        const shouldReject = Math.random() > 0.5; // 随机决定是否拒绝Promise
        if (shouldReject) {
            reject('Promise被拒绝了');
        } else {
            resolve('Promise成功解决');
        }
    });
}

asyncFunction();

在此代码中,如果mightRejectPromise函数返回的Promise被拒绝,那么等待它的await表达式就会抛出异常,该异常可以被封闭的try块捕获,并且会将控制权交给紧随其后的catch块。

这使得处理异步代码中的错误更加直观,并且代码风格更接近同步代码的错误处理方式。

5、await to

await to并不是JavaScript标准的一部分,然而,这是一个常见的JavaScript模式,用于简化错误处理,使其更像同步代码中的try-catch。它是从Node.js 社区中流行起来的一个模式,通常是在使用async/await时以一种更清晰的方式来处理异步操作的潜在错误。

此模式通常通过一个帮助函数来实现,作用是捕捉await表达式中的错误并将其返回为数组,类似于其他语言中的tuple unpacking。该函数避免了try-catch块的显式使用,并允许你以一种结构化的方式处理错误,使得代码更简洁、更易读。

这里是这个帮助函数的一个示例实现:

function to(promise) { 
   return promise.then(data => ([null, data])).catch(err => ([err])); 
} 

使用to函数的示例代码可能如下:

async function fetchData(url) { 
  const [err, data] = await to(fetch(url)); 
  if (err) { console.error('An error occurred:', err); return; } 
  console.log('Received data:', data); 
} 

在上述示例中,fetchData函数使用了await to(fetch(url))来调用fetch而不是单纯的await fetch(url)。如果fetch成功,to函数会返回一个包含两个元素的数组,其中第一个元素是null(表示没有错误),第二个元素是resolve结果。如果fetch失败,to函数会捕获错误并返回一个数组,第一个元素是错误对象,第二个元素是undefined

这个模式可以帮助程序员写出清晰、连贯的代码,特别是在处理许多异步调用时。不过,它也略去了一些JavaScript try-catch结构的标准用法,因此,这是一种风格和偏好的问题,并非所有的JavaScript开发者都会采用这种方式。此外,这种风格可能隐藏了错误堆栈的一些信息,所以在调试时可能不如标准的try-catch句法那么方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值