【理解异步函数async和await的用法

本文详细介绍了JavaScript中的async和await关键字,如何将异步逻辑转化为同步形式,以及它们在处理Promise和控制异步流程中的作用,包括await的原理和优势,以及async/await与Promise的比较和使用场景的选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


定义

1. async 是异步的意思,await则可以理解为 async wait。所以可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行
2. async作为一个关键字放在函数前面,表示该函数是一个异步函数,异步函数意味着该函数的执行不会阻塞后面代码的执行;而 await 用于等待一个异步方法执行完成;
3. 当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续往下执行。并会阻塞该函数内后面的代码。
4. 因此async/await的作用就是将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。
4. 为了优化 .then 链而开发出来的。

 一. 关于async

async的用法,语法很简单,在函数前面加上async关键字,表示函数是异步的。

async function timeout() {
     return 'hello world!'
 }

只有一个作用,他的调用会返回一个promise对象。
那怎么调用呢?async 函数也是函数,平时我们怎么使用函数就怎么使用它,直接加括号调用就可以了,为了表示它没有阻塞它后面代码的执行,我们在async 函数调用之后加一句console.log;

async function timeout() {
     return 'hello world!'
 }
 timeout()
 console.log('我虽然在后面,但是先执行')

打印结果:

原来async 函数返回的是一个promise 对象,并且Promise还有state和result,如果async函数中有返回值,当调用该函数时,内部会调用Promise.resolve()方法把它转化成一个promise对象作为返回,但如果timeout函数内部抛出错误呢? 那么就会调用Promise.reject() 返回一个promise 对象
 

async function timeout() {
    throw new Error('rejected');
}
console.log(timeout());

就会调用Promise.reject() 返回一个promise 对象

 那么要想获取到async 函数的执行结果,就要调用promise的then 或 catch 来给它注册回调函数

继续修改代码

async function timeout() {
      return 'hello world!'
    }
    timeout().then(val => {
      console.log(val)
    })
    console.log('我虽然在后面,但是先执行')

 打印结果:

我们获取到了"hello world!', 同时timeout的执行也没有阻塞后面代码的执行,和 我们刚才说的一致。

如果async 函数执行完,返回的promise 没有注册回调函数,比如函数内部做了一次for 循环,你会发现函数的调用,就是执行了函数体,和普通函数没有区别,唯一的区别就是函数执行完会返回一个promise 对象。 

 async function timeout () {
      for (let index = 0; index < 3; index++) {
        console.log('async', +index)
      }
    }
    console.log(timeout())
    console.log('outer')

 

另外,async函数返回一个promise对象,下面两种方法是等效的 

/ 方法1
function f() {return Promise.resolve('TEST');
}// asyncF is equivalent to f!

// 方法2
async function asyncF() {return 'TEST';
}

 二. 关于await

1) await 到底在等啥?
async 关键字差不多了,最重要的就是async函数的执行会返回promise对象,并且把内部的值进行promise的封装。如果promise对象通过then或catch方法又注册了回调函数,async函数执行完以后,注册的回调函数就会放到异步队列中,等待执行。
如果只是async,和promise差不多,但有了await就不一样了,await关键字只能放到async函数里面,await是等待的意思,那么它等待什么呢?它后面跟着什么呢?其实await不仅仅用于等Promise对象,还可以等任意表达式,所以await后面实际是可以接普通函数调用或者直接量的,不过我们更多的是放一个返回promise 对象的表达式。他等待的是promise对象执行完毕,并返回结果。
 

//所以下面这个示例完全可以正确运行
    function getSomething () {
      return 'something'
    }
    async function testAsync () {
      return Promise.resolve('hello async')
    }
    async function test () {
      const v1 = await getSomething()
      const v2 = await testAsync()
      console.log(v1, v2)
    }
    test()

 2) await 等到了要等的,然后呢?

await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?

  1. 如果它等到的不是一个Promise对象,那么await表达式的运算结果就是它等到的东西。
  2. 如果它等到的是一个Promise对象,await就忙起来了,它会阻塞函数后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果。
3) async/await 帮我们干了啥?

做个简单的比较
现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写

function takeLongTime () {
      return new Promise(resolve => {
        setTimeout(() =>
          resolve('long_time_value'), 1000
        )
      })
    }
takeLongTime().then(val => {
   console.log(val, 'val')
})

 如果改用 async/await 呢,会是这样

   function takeLongTime () {
      return new Promise(resolve => {
        setTimeout(() =>
          resolve('long_time_value'), 1000
        )
      })
    }
     async function test () {
      let v = await takeLongTime()
      console.log(v, 'v')
    }
    test()

眼尖的已经发现 takeLongTime () 没有申明为async。实际上takeLongTime () 本身就返回Promise对象,加不加async结果都一样。

4) await 优势在于处理 then 链,使代码看起来像同步代码一样,下面是实例应用

现在写一个函数,让它返回promise 对象,该函数的作用是2s 之后让数值乘以2

 

// 2s 之后返回双倍的值
function doubleAfter2seconds (num) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(num * 2)
        }, 2000)
      })
    }

现在再写一个async 函数,从而可以使用await 关键字, await 后面放置的就是返回promise对象的一个表达式,所以它后面可以写上 doubleAfter2seconds 函数的调用

async function testResult() {
    let result = await doubleAfter2seconds(30);
    console.log(result); //2s后打印60
}
testResult();

 代码的执行过程
调用testResult 函数,它里面遇到了await, await 表示等待,代码就暂停到这里,不再向下执行了,它等待后面的promise对象执行完毕,然后拿到promise resolve 的值并进行返回,返回值拿到之后,它继续向下执行。具体到 我们的代码, 遇到await 之后,代码就暂停执行了, 等待doubleAfter2seconds(30) 执行完毕,doubleAfter2seconds(30) 返回的promise 开始执行,2秒 之后,promise resolve 了, 并返回了值为60, 这时await 才拿到返回值60, 然后赋值给result, 暂停结束,代码继续执行,执行 console.log语句。
 

 就这一个函数,我们可能看不出async/await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?

async function testResult() {
    let first = await doubleAfter2seconds(30);
    let second = await doubleAfter2seconds(50);
    let third = await doubleAfter2seconds(30);
    console.log(first + second + third);
}
testResult()

6秒后,控制台输出220, 我们可以看到,写异步代码就像写同步代码一样了,再也没有回调地域了。 

这里强调一下,当js引擎在等待promise.resolve的时候,他并没有真正的暂停工作,它可以处理其他的一些事情,如果我们在testResult函数后面继续执行其他代码,比如console.log一下,会发现console.log代码先执行。 

async function testResult() {
    let first = await doubleAfter2seconds(30);
    let second = await doubleAfter2seconds(50);
    let third = await doubleAfter2seconds(30);
    console.log(first + second + third);
}
testResult()
console.log('我先执行!!!')

先输出 “我先执行!!!”,6s后输出计算结果。

 

三、举例 

 1)当遇到 await 时,会阻塞函数内部处于它后面的代码(而非整段代码),去执行该函数外部的同步代码;当外部的同步代码执行完毕,再回到该函数执行剩余的代码。并且当 await 执行完毕之后,会优先处理微任务队列的代码。

async function fn1 (){
  console.log(1)
  await fn2() // 遇到fn2,进入执行
  console.log(2) // 阻塞,加入微任务队列
}
async function fn2 (){
  console.log('fn2')
}
fn1()
console.log(3)

// 输出结果:1,fn2,3,2

上面的例子中,await 会阻塞它下面的代码(即加入微任务队列),先执行 async 外面的同步代码,同步代码执行完,再回到 async 函数中,再执行之前阻塞的代码。

2)

  async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
    setTimeout(() => {
      console.log('timer1')
    }, 0)
  }
  async function async2() {
    setTimeout(() => {
      console.log('timer2')
    }, 0)
    console.log('async2')
  }
  async1()
  setTimeout(() => {
    console.log('timer3')
  }, 0)
  console.log('start')
// async1 start => async2 => start => async1 end => timer2 => timer3 => timer1

代码的执行过程:

首先进入async1,打印出 async1 start。
然后遇到async2,进入async2,遇到定时器 timer2,加入宏任务队列,之后打印 async2。
由于 async2阻塞了后面的代码执行,将后面的代码加入微任务队列。所以执行后面的定时器 timer3,将其加入宏任务队列,之后打印同步代码 start。
同步代码执行完毕,先执行微任务队列,打印出 async1 end,遇到定时器 timer1,将其加入宏任务队列。
最后再执行宏任务队列,宏任务队列有3个任务,先后顺序是 timer2,timer3,timer1,分别按顺序执行,任务队列按照先进先出原则执行。
 

 总结:

async 函数
1)函数的返回值为Promise对象
2)Promise对象的结果由async函数执行的返回值决定
await 表达式
1)正常情况下,await右侧的表达式一般为 promise对象 , 但也可以是其它的值
2)如果表达式是promise对象,await就忙起来了,它会阻塞函数后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果。
3)如果表达式是其它值, 直接将此值作为await的返回值
async和await基于promise的。使用async的函数将会始终返回一个 promise 对象。这一点很重要,要记住,可能是你遇到容易犯错的地方。
在使用await的时候我们只是暂停了函数,而非整段代码。这里经常会是容易犯错的地方。
async和await是非阻塞的
仍然可以使用 Promise,例如Promise.all(p1, p2, p3).,接受一个数组作为参数,p1、p2、p3 都是 Promise 实例,如果不是,就会先调用 Promise .resolve方法,将参数转为 Promise 实例,再进一步处理。只要 p1、p2、p3 之中有一个被 rejected,整个状态就变成 rejected。
注意
1)await必须写在async函数中, 但async函数中可以没有await
2)如果await的promise失败了, 就会抛出异常, 需要通过try…catch来捕获处理
 

async/await or promise使用时到底选择哪个呢?

大部分复杂情况下async/await 的确是最优解,个人觉得也不是所有情况下都是 async/await 写起来更爽,最不爽的就是他的错误处理,try...catch这个代码看起来就很奇怪(当然也有很多人喜欢这种错误处理方式)。所以我个人的习惯,当只有一个异步请求,且需要做错误处理的情况下,更倾向于使用 promise。比如
 

// promise
getInfo()
  .then(res => {
    //do somethings
  })
  .catch(err => {
    //do somethings
  })

// async/await
try {
  const res = await getInfo()
  //do somethings
} catch (error) {
  //do somethings
}

在有嵌套请求的情况下,肯定是 async/await 更直观的。

// promise
a(() => {
  b(() => {
    c()
  })
})

// async/await
await a()
await b()
await c()

 当然代码写的好与不好还是取决于写代码的人的。
比如一个常见的业务场景:有两个并发的异步请求,在都完成后do something。但很多人会错误的用串行的方式实现了。

//错误
await a()
await b()
//这样变成了 a().then(() => b() )
// a 好了才会执行 b
done()

//正确
await Promise.all([a(), b()])
done()

 

还有一个小细节async/await打包后的代码其实会比 promise 复杂很多, 当然这个是一个忽略不计得问题。

总结:
我认为它们两个人并不是 or 的关系,在特定的业务场景下,选择相对而言代码可读性更好地解决方案。具体该怎么选择还是需要大家更具自己团队的风格或者自己的理解来判断。
 

 

 

<think>好的,我现在需要帮助用户理解Python中的asyncawait关键字以及异步编程的概念。首先,我得回忆一下自己之前学过的相关知识,确保信息准确。根据用户提供的引用资料,特别是引用34,async用来定义异步函数await用来等待其他协程执行,而asyncio是它们的实现之一。 首先,我应该从基础概念开始解释,比如什么是异步编程,为什么需要它。用户可能已经知道同步编程的局限性,比如I/O操作阻塞整个程序,导致效率低下。异步编程可以在等待I/O时释放CPU资源,处理其他任务,提高吞吐量。这部分需要简明扼要,不要过于技术化。 接下来,要介绍asyncawait的具体用法。根据引用34,async def用于声明协程函数await用于挂起当前协程,等待其他协程完成。需要举例说明,比如用户提供的引用5中的例子,展示如何定义异步函数,使用await调用,以及事件循环的处理流程。代码示例很重要,用户可能希望看到实际的用法,所以需要确保代码正确,并符合Python的语法规范。 然后,需要讲解事件循环的概念,这是异步编程的核心。用户可能不太清楚事件循环如何管理协程,需要解释事件循环如何调度任务,当遇到await时暂停当前任务,执行其他任务,直到原任务就绪。这部分可以结合引用5中的main函数Task对象的说明,说明事件循环如何管理协程状态。 另外,异常处理也是关键部分。根据引用2,异步任务中的异常处理需要使用try-except块来捕获,确保程序稳定性。需要提供一个代码示例,比如用户提到的error_prone_operation函数,在main函数中捕获异常。这样可以展示异步编程中的错误处理机制,避免程序崩溃。 还需要提到asyncio库的作用,引用1指出asyncawait是API,而asyncio是它们的实现。用户可能混淆这两个概念,需要说明它们的关系,强调async/await是语法,而asyncio提供了底层的支持,比如事件循环任务管理。 此外,可能需要比较同步异步的区别,帮助用户理解异步的优势。例如,同步代码按顺序执行,而异步在等待时切换任务,提高效率。可以举一个简单的例子,比如同时处理多个网络请求,异步可以显著减少总时间。 最后,用户可能需要知道如何开始使用异步编程。步骤包括定义async函数,使用await调用,运行事件循环。需要强调asyncio.run()作为入口点,以及如何组织异步函数。常见问题如忘记使用await,或者错误地混合同步异步代码,也需要提醒用户注意。 在总结时,确保覆盖了基本概念、语法、事件循环、异常处理,以及最佳实践。同时,根据用户提供的引用资料,正确标注引用来源,比如引用1到5的相关内容,确保信息的准确性可信度。最后生成相关问题,帮助用户进一步学习,比如如何创建任务、处理多个异步操作、关闭事件循环等。</think>### Python异步编程与async/await使用指南 #### 一、基础概念 异步编程的核心目标是**在I/O等待时释放CPU资源**,通过协程(Coroutine)实现非阻塞操作。与传统同步编程不同,异步模型使用单线程并发执行多个任务,在等待I/O时自动切换任务[^3]。 #### 二、语法规则 1. **定义协程函数** 使用`async def`声明异步函数: ```python async def fetch_data(): await asyncio.sleep(1) return "data" ``` 2. **等待协程执行** `await`关键字用于挂起当前协程,直到被等待的协程完成: ```python async def main(): result = await fetch_data() # 等待数据返回 print(result) ``` #### 三、事件循环机制 事件循环(Event Loop)是异步编程的调度核心: ```python import asyncio async def task1(): print("开始任务1") await asyncio.sleep(2) print("结束任务1") async def task2(): print("执行任务2") asyncio.run(task1()) # 启动事件循环 ``` 执行流程: 1. `task1()`进入事件循环 2. 遇到`await asyncio.sleep(2)`时暂停 3. 事件循环寻找其他可执行任务 4. 2秒后恢复`task1()`执行[^5] #### 四、异常处理 异步任务需要特殊异常捕获方式: ```python async def error_task(): raise ValueError("模拟错误") async def main(): try: await error_task() except ValueError as e: print(f"捕获错误: {e}") # 输出:捕获错误: 模拟错误[^2] ``` #### 五、最佳实践 1. **避免阻塞操作**:不要在协程中使用`time.sleep()`等同步阻塞方法 2. **任务分组**:使用`asyncio.gather()`并发执行多个任务 ```python async def main(): await asyncio.gather(task1(), task2(), task3()) ``` 3. **上下文管理**:使用`async with`管理异步资源 #### 六、性能对比 通过HTTP请求示例比较同步/异步耗时: ```python # 同步版本(约3秒) def sync_fetch(): for _ in range(3): requests.get(url) # 假设每次请求1秒 # 异步版本(约1秒) async def async_fetch(): await asyncio.gather(*[session.get(url) for _ in range(3)]) ``` #### 七、常见错误 1. **忘记使用await**:导致协程未实际执行 2. **混合同步/异步代码**:在协程中调用阻塞函数 3. **未启动事件循环**:直接调用协程函数而不使用`asyncio.run()` [^1]: async/await应视为API接口而非具体实现 [^2]: 异步异常必须通过try-except捕获 [^3]: async关键字定义协程函数 [^5]: 事件循环通过Task对象管理协程状态
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值