Hypecode Hypecode
首页
  • 前端基础笔记

    • JavaScript文章
    • CSS文章
    • Typescript文章
  • 前端框架笔记

    • Vue3笔记
    • Vue源码(理)笔记
    • echarts使用笔记
  • 前端架构笔记

    • Vite文章
    • pnpm文章
  • Node
  • Git
  • 学习规划
  • 友情链接
  • 面试记录
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Hypeng

看看远处的雪山吧
首页
  • 前端基础笔记

    • JavaScript文章
    • CSS文章
    • Typescript文章
  • 前端框架笔记

    • Vue3笔记
    • Vue源码(理)笔记
    • echarts使用笔记
  • 前端架构笔记

    • Vite文章
    • pnpm文章
  • Node
  • Git
  • 学习规划
  • 友情链接
  • 面试记录
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 理解async&await
    • async、await是做什么的
      • await
      • async
      • generator
      • CO
  • JavaScript 文章
Coderhyp
2021-09-13
目录

理解async&await

# async、await是做什么的

首先阅读在红宝书上的概念是,async和await是ES8规范新增的语法,这个特性从行为和语法上都增强了 Javascript,能够以同步的方式写的代码能够异步执行。 虽然promise为我们很好地解决了回调地狱的问题,但是在有些需求场景下,还是会存在多层嵌套的情况。 比如如下情况:

function request(item) {
  return new Promise(resolve => {
    setTimeout(()=>{
      resolve(item+'res')
    },1000)
  })
}
// 以下模拟先请求一个接口后请求另一个接口
request('req1').then(r1=>{
  console.log(r1)   //1s后 req1res
  request('req2').then(r2=>{
    console.log(r2) // 2s后 req2res
  })
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

上面的情况使用async和await是如下写法

async function fn(){
  const res1 = await request('req1')
  console.log(res1) //1s后 req1res
  const res2 = await request('req2')
  console.log(res2) //2s后 req2res
}
fn()
1
2
3
4
5
6
7

# await

可以看出 使用async和await可以良好解决嵌套层级的问题,await后面跟着的那句代码相当于resolve()那里的代码 再下面的代码相当于then里面的代码 await会等待后面那句代码执行完之后再向下执行之后的代码 需要注意的是 通常我们在使用await时,await any(就是await后面跟着的)中的any一般都是异步操作,但是如果any是同步操作,可以理解成any被Promise.resolve(any)这样处理过,因此它也会被放到微任务队列。 例如:

function request(item) {
    setTimeout(()=>{
      console.log(item+'res')
    },1000)
}
async function fn(){
  await request('req1')
  await request('req2')
}
fn() // 同时输出req1res,req2res
1
2
3
4
5
6
7
8
9
10

需要注意的是,只有在async定义的函数中才可以使用await 想完全理解await,我们还需要知道要它并非只是等待一个值可用那么简单。

  • JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。
  • 等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。
  • 因此,即使 await 后面跟着一个立即可用的值,函数的其余部分也会被异步求值。 如下是在JS高程中的例子:
async function fn(){
  console.log(2)
  await null    //(在 foo()中)await 关键字暂停执行,为立即可用的值 null 向消息队列中添加一个任务
  console.log(4)
}
console.log(1)
fn()
console.log(3)  // 同步线程的代码执行完成后,JavaScript从消息队列中取出任务,来恢复异步函数的执行
// 输出 1 2 3 4 

1
2
3
4
5
6
7
8
9
10

# async

我们可能会好奇,为什么只有使用async定义的函数才能实现异步同步形式,首先可以看一下async的返回值

async function fn(item){ return item+'res' }
console.log(fn)        //async ƒ fn(item){ return item+'res' }
console.log(fn('req')) //Promise {<fulfilled>: 'reqres'}
fn('req').then((res)=>{
  console.log(res)     // reqres
})

1
2
3
4
5
6
7
  • 可以看出async的返回值是一个fulfilled状态的Promise,且在赋予async方法返回值之后可以使用then方法来输出
  • async函数如果使用return关键字返回了值(如果没有return会返undefined),这个返回值会被包装成一个期约对象,并且async函数始终返回的是Promise对象
  • 使用 async 关键字可以让函数具有异步特征,但总体上其代码仍然是同步求值的。而在参数或闭包方面,async函数仍然具有普通 JavaScript 函数的正常行为。
  • async/await 中真正起作用的是 await。async 关键字,无论从哪方面来看,都不过是一个标识符。毕竟,异步函数如果不包含 await 关键字,其执行基本上跟普通函数没有什么区别

那么async/await到底是怎么实现的的呢? 在了解async/await实现原理之前,需要先了解generator函数

# generator

# 基本用法

生成器的形式是一种函数且要在函数前面加上一个 * 来表示这是一个生成器 只要是可以定义函数的地方就可以定义生成器 (箭头函数不能定义生成器函数)

  • 在生成器函数中有一个yield关键字,通过yield可以让生成器停止和开始执行
  • 当生成器执行时遇到yield,遇到这个关键字后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用 **next()**方法来恢复执行
  • next方法的执行会返回一个对象 包括value和done两个属性
function* gen() {
  yield 1
  yield 2
  yield 3
  return 4
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: 4, done: true }   如果没有返回值那么value为undefined

1
2
3
4
5
6
7
8
9
10
11
12
  • 如果后面是函数
function fn(n){
  console.log(n)
  return n+1
}
function* gen(){
  yield fn(0)
  yield fn(1)
  return 3
}
const ge = gen()
console.log(ge.next()) // 0  {value: 1, done: false}
console.log(ge.next()) // 1  {value: 2, done: false}
console.log(ge.next()) // 2  {value: 3, done: true}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 如果是Promise
function fn(n){
  return new Promise(resolve=>{
    setTimeout(()=>{
      resolve(n+1)
    },1000)
  })
}
function* gen(){
  yield fn(0)
  yield fn(1)
  return 3
}
const ge = gen()
console.log(ge.next())  // {value: Promise, done: false}
console.log(ge.next())  // {value: Promise, done: false}
console.log(ge.next())  // {value: 3, done: true}
//获取promise的结果
ge.next().value.then(res1=>{
  console.log(res1)    //1s后输出 1
  ge.next().value.then(res2=>{
    console.log(res2)  //2s后输出 2
    console.log(ge.next())  //2s后输出 {value: 3, done: true}
  })
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • 使用next传参 generator函数能通过next传参 通过yield来接受参数
function* gen() {
  const num1 = yield 
  console.log(num1)
  const num2 = yield 1
  console.log(num2)
  return 2
}
const ge = gen()
console.log(ge.next())     // {value: undefined,done:false}
console.log(ge.next(111))  // 111    {value: 1,done: false}
console.log(ge.next(222))  // 222    {value: 2,done: true}
1
2
3
4
5
6
7
8
9
10
11
  • 使用Promise且next传参
function fn(num){
  return new Promise((resolve)=>{
    setTimeout(()=>{
      resolve(num*2)
    },1000)
  })
}
function * gen(){
  const num1 = yield fn(1)
  const num2 = yield fn(3)
  const num3 = yield fn(6)
  return num3
}
const ge = gen()
const next1 = ge.next()  //第一次执行next()
next1.value.then(res=>{
  console.log(next1)  // 一秒后输出  {value: Promise{<fullfilled>:2}, done: false}
  console.log(res)    // 一秒后输出  2
  const next2= ge.next(res)  
  next2.value.then(res2=>{
    console.log(next2)  // 两秒后输出  {value: Promise{<fullfilled>:6},done:false}
    console.log(res2)   // 两秒后输出 6
    const next3 = ge.next(res2)
    next3.value.then(res3=>{
      console.log(next3)  //三秒后输出  {value: Promise{<fullfilled:12>},done:false}
      console.log(res3)   //三秒后输出  12

      console.log(ge.next(res3)) //三秒后输出{value: 12, done: true}
    })
  })
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# CO

实际上在generator函数中使用Promise和next传参的模式 已经接近async/await的方式了,但是generator需要通过调用next()方法执行yield,但是async不需要,这其中就有CO的帮助,CO是由TJ大神编写的JavaScript异步解决方案的库,CO实际上就是一个generator自执行器,通过递归的方式自动处理每一步yield。 所以大概就是 generator+Promise+CO=>async/await Promise让代码扁平化,CO让代码同步化 举🌰:

function fn(num){
  return new Promise((resolve)=>{
    setTimeout(()=>{
      resolve(num*2)
    },1000)
  })
}
function * gen(){
  const num1 = yield fn(1)
  console.log(num1)
  const num2 = yield fn(2)
  console.log(num2)
  return num2
}
//常规写法
let ge = gen()
let {value} = ge.next()
value.then(res1=>{
  console.log(res1) //2
  const next2 = ge.next(res1)
  next2.value.then(res2=>{
    console.log(res2)//4
    console.log(ge.next(res2))
  })
})
//CO写法
co(gen()).then(res=>{
    console.log(res); //4
    //CO会帮助我们自执行
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
编辑 (opens new window)
#JavaScript
上次更新: 2021/09/23, 14:08:55
最近更新
01
Typescript面向对象
02-05
02
动态规划
12-18
03
pnpm
11-08
更多文章>
Theme by Vdoing | Copyright © 2021-2023 Coderhyp | GITHUB
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×