浅谈 async/await 和生成器

news2024/10/7 18:29:06

浅谈 async/await

async/await 是ES8规范新增的,使得以同步方式写的代码异步运行不再是白日梦,进一步让代码逻辑更加清晰。

为什么新增 async/await

下面有这样一个需求:有两个请求,请求 1 的结果是请求 2 的参数,所以请求 2 必须在请求 1 之后发出,使用 Promise 实现如下:

request('/xxx').then((res) =>{
  request('/yyyy',{res}).then(() => {
    // 对结果进行处理
  })
})

可以看到这里形成了一个嵌套结构,试想如果这样的嵌套层级深了以后就会形成类似“回调地狱”那样的结构,很不利于维护。因此就新增了 async/await 这两个关键字来解决这个问题。上面的需求 async/await 写法如下:

async function fn() {
  let res = await request('/xxx');
  request('/yyyy',{res}).then(() => {
    // 对结果进行处理
  })
}

如何使用

async

async 关键字写在函数名之前(相当于一个标识),让普通函数具有异步的行为特征,但是整体上代码却是同步执行的。它可以用在以下地方:

  • 函数声明
  • 函数表达式
  • 箭头函数
  • 类的属性方法
async function fn1() { } //函数声明
let fn2 = async function () { }//函数表达式
let fn3 = async () => { }//箭头函数
let test = {	// 类中属性方法
  async fn4() { }
}

// 总体同步执行
async function fn5() {
  console.log(1);
}
fn5();// 1
console.log(2);//2

异步函数返回值是一个期约对象,内部逻辑是使用 Promise.resolve方法来处理返回值 。因此可以使用.then方法处理异步任务的结果。

async function fn(){
  console.log('1.异步函数执行了');
  const res = await '2.插队?不讲武德';
}
fn().then(res => console.log(res));
  • 如果没有返回值,则 Promise 的状态为解决(fulfilled),结果为 undefined
async function fn(){
  console.log('异步函数执行了');
}
const res = fn();
console.log(res);

  • 如果有返回值,Promise 的状态为解决(fulfilled),结果为该返回值
async function fn(){
  console.log('异步函数执行了');
  return '我是返回值';
}
const res = fn();
console.log(res);

在这里插入图片描述

  • 如果在异步函数中抛出错误那么会返回一个失败(rejected)状态的期约
async function fn(){
  console.log('异步函数执行了');
  throw new Error('出错了');
}
const res = fn();
console.log(res);

在这里插入图片描述

  • 如果返回一个失败(rejected)状态的期约却并不会被异步函数捕获
async function fn(){
  console.log('异步函数执行了');
  return Promise.reject('出错了');
}
const res = fn();
console.log(res);

在这里插入图片描述

await

当遇到 await 后,会先暂停异步函数的执行,让出 JS 运行时线程,执行别的代码。当异步函数拿到结果又会在合适的时机恢复运行。

async function fn(){
  console.log('1.异步函数执行了');
  const res = await '2.插队?不讲武德';
  console.log(res);
  console.log('3.终于轮到我了')
}
fn();
console.log('4.我先插个队');

在这里插入图片描述
注意:await 关键字只会暂停异步函数的执行,并不会影响异步函数之外的代码。如上面的代码执行 fn 函数打印“1.异步函数执行了”,遇到 await 关键字,就暂停函数中 await 后边的代码执行,转而去打印“4.我先插个队”。

await 使用限制

早期 await 只允许在异步函数中使用,再同步函数使用会报错。
在这里插入图片描述
但是从 ES2022 开始,允许在模块的顶层独立使用await命令。如下所示:
在这里插入图片描述
它的主要目的是使用await解决模块异步加载的问题。

因为 await 等待的是一个异步操作,而异步任务有可能失败(rejected),所以需要把 await 放在 try/catch中。

function fn() {
  try{
    let res = await ···;
    let res1 = await ···;
    let res2 = await ···;
  } catch(err) {
    // 失败处理逻辑
  }
}

题目分析

async function async1() {
  console.log("async1 1");
  await async2();
  console.log("async1 2");
  setTimeout(() => {
    console.log('timeout1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('timeout2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log('timeout3')
}, 0)
console.log("start")

在这里插入图片描述

  1. 执行async1函数,打印“async1 1”,遇到await,暂停async1函数执行,向消息队列添加一个在async2执行完之后执行的任务;
  2. 执行async2函数,遇到setTimeout,将其添加到宏任务队列,打印 “async2”,async2函数执行完毕,把给 await 提供值的任务添加到消息队列,async1退出;
  3. 执行最外层同步代码,又遇到setTimeout,将其添加到宏任务队列,打印“start”,同步代码执行完毕。
  4. JS 运行时从消息队列中取出给 await 提供值的任务,并将 undefined 赋值给它,并添加一个恢复async1函数执行的任务。
  5. 取出恢复async1函数执行的任务,打印“async1 2”;遇到setTimeout添加到宏任务队列。
  6. 宏任务队列不为空,执行队头宏任务,打印“timeout2”;微任务队列为空;
  7. 宏任务队列不为空,执行队头宏任务,打印“timeout3”;微任务队列为空;
  8. 宏任务队列不为空,执行队头宏任务,打印“timeout1”;微任务队列为空;
  9. 程序结束。

await 不会等待 setTimeout

分析上面的代码我发现 await 并没有等待 async2 函数中 setTimeout 函数执行结束,原因是 setTimeout 函数在调用时会同步返回一个随机数,而实际上 await 等待的就是异步任务结果,此时,这个随机数就会被await 认为是 setTimeout 异步任务的结果,就放行了。

async function fn() {
  await fn1();
  await fn2();
}
async function fn1() {
  setTimeout(() => {
    // 省略代码
  }, 200);
}
async function fn2() {
  Math.random();
}

上面的代码 fn1 和 fn2 函数对于 await 来说是等价的。

浅谈生成器

生成器是 ES6 新增的,它拥有在一个函数块内暂停和恢复代码执行的能力。(箭头函数不能用来定义生成器函数)只要调用生成器函数就会产生一个生成器对象,它实现了 Iterator 接口,初始为暂停执行状态,可以调用 next 方法让生成器恢复执行。

next 方法的返回值类似于迭代器.{value:xxx,done:flase||true}当done为true 就意味着生成器已经执行完毕,此时再调用next方法,value是生成器函数的返回值,默认为 undefined。

function* fn() {
  yield 1;
  yield 2;
}
// 生成生成器对象,暂停状态
const Fn = fn();
// 恢复执行
console.log(Fn.next());//{value: 1, done: false}
// 恢复执行
console.log(Fn.next());//{value: 2, done: false}
// 执行完毕
console.log(Fn.next());//{value: undefined, done: true}
// 执行完毕后再调用 next 函数,会输出同样的结果
console.log(Fn.next());//{value: undefined, done: true}

yield

yield 关键字可以让生成器停止执行。生成器函数在遇到 yield 关键字后会暂停执行并保留函数作用域状态,只能通过调用生成器对象的 next 方法恢复执行。并且 yield 语句生成的值就在 next 函数返回值里,此时生成器函数时done:false的状态,通过 return 退出生成器会处于done:true状态

function* fn() {
  yield 1;
  return 2;
}
const Fn = fn();
// yield 生成的值就是 next 函数返回的对象里的 value
console.log(Fn.next());//{value: 1, done: false}
console.log(Fn.next());//{value: 2, done: true}

生成器作为可迭代对象

因为生成器对象实现了 Iterator 接口,可以使用 for of 遍历,它会自动调用 next 方法。

function* fn() {
  yield 1;
  yield 2;
  yield 3;
}
for (let item of fn()) {
  console.log(item);
}

在这里插入图片描述

next的参数

生成器对象的 next 方法是可以接收一个参数的,这个参数最终会传给 yield ,注意两点:

  • 第一次next传参是没用的,只有从第二次开始next传参才有用。
    原因:我的理解是第一次调用 next 函数执行的是第一个 yield 之前的代码,这里就是console.log('开始执行');,所以这是传参没有 yield 接收。
  • next传值时,要记住顺序是,先右边yield,后左边接收参数。
    意思就是在第二次调用 next 函数时,会先执行 yield 后边的代码,接着将参数赋值给等号右边的变量。
function* fn() {
  console.log('开始执行');
  let r1 = yield 1;
  let r2 = yield r1;
  return r2;
}
const Fn = fn();
console.log(Fn.next());//{value: 1, done: false}
console.log(Fn.next(2));//{value: 2, done: false}
console.log(Fn.next(3));//{value: 3, done: true}

提前终止生成器

return

return 会强制生成器进入关闭状态,不可逆,最后的状态值就是传给return()的参数。并且调用return以后再调用next方法都会返回{value:undefined,done:true}

function* fn() {
  yield 1;
  return 2;
  yield 3;
}
const Fn = fn();
console.log(Fn.next());//{value: 1, done: false}
console.log(Fn.next());//{value: 2, done: true}
console.log(Fn.next());//{value: undefined, done: true}
console.log(Fn.next());//{value: undefined, done: true}

throw

throw 方法会在暂停的时候将一个错误注入到生成器对象中,如果错误未被处理,生成器就会关闭

function* fn() {
  yield 1;
  yield 2;
  yield 3;
}
const Fn = fn();
console.log(Fn.next());//{value: 1, done: false}
Fn.throw('出错了···')
console.log(Fn.next());
console.log(Fn.next());

在这里插入图片描述

如果生成器函数内部处理了这个错误,只会跳过对应的yield,可再次恢复执行。

function* fn() {
  try {
    yield 1;
  } catch (error) {
    console.log('捕获到错误内容:'+ error)
  }
  yield 2;
  yield 3;
}
const Fn = fn();
console.log(Fn.next());//{value: 1, done: false}
Fn.throw('出错了···');
console.log(Fn.next());//{value: 3, done: false}
console.log(Fn.next());//{value: undefined, done: true}

在这里插入图片描述

生成器与 async/await

还记得在介绍 async/await 时说过的需求吗?请求一的结果是请求二的参数,最后拿到结果,下面利用生成器和 Promise 来模拟,注意这样的代码就类似于 async/await 是同步的逻辑

function fn(x) {
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve(x + 1);
    }, 1000);
  })
}
function* fn1() {
  console.log('开始执行');
  let r1 = yield fn(1);
  console.log('请求一完成');// 1s后打印
  let r2 = yield fn(r1);
  console.log('请求二完成');// 2s后打印
  return r2;
}
const gen = fn1();
gen.next().value.then((res) => {
  gen.next(res).value.then(res => {
      console.log('输出结果', gen.next(res));
  })
})

简单实现 async/await

上面的代码有很多缺陷,只能执行有限步···,现在我们可以稍稍封装一下

function fn(x) {
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve(x + 1);
    }, 1000);
  })
}
function* fn1() {
  console.log('开始执行');
  let r1 = yield fn(1);
  console.log('请求一完成');
  let r2 = yield fn(r1);
  console.log('请求二完成');
  return r2;
}
function myAsync (fn) {
  const Fn = fn.apply(this, arguments);//拿到生成器对象
  return new Promise((resolve,reject) => {
    function forward(key,val) {
      let res = null; 
      try{
        res = Fn[key](val);//恢复执行
      } catch(err) {
        return reject(err);
      }
      let {value,done} = res;
      if (done) {//代码执行完了,返回一个解决状态的期约
        return resolve(value);
      } else {
        return Promise.resolve(value).then(value => forward('next',value),err=>forward('throw',err));
      }
    }
    forward('next');//第一次执行
  })
}
const asyncFn = myAsync(fn1)
asyncFn.then(res => console.log(res))

这样 fn1 函数中 yield 就相当于 await ,且异步函数 myAsync 执行完就能得到一个期约,并且无论 fn1 函数中有多少 yield 都能正确执行,并得到结果。

总结

async/await 和 生成的使用细节以及具体的应用还有很多,我在这里只是稍稍总结一下,本篇博客内容也是我的学习笔记吧。我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/13791.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习6——EM算法与高斯混合模型GMM

前置内容 Jensen不等式 高斯混合模型 多元高斯模型 拉格朗日乘子法 主要内容 EM算法(Expectation-Maximization),期望-最大化。 用于保证收敛到MLE(最大似然估计)。主要用于求解包含隐变量的混合模型,主要…

R生成三线表

R生成三线表table1包测试数据生成制作三线表Tableone包加载包 探索数据类型数据整理分类构建Table函数Tableone函数细节主要基于table1 或tableone 包table1包 测试数据生成 datadata.frame(性别sample(c("男","女"), 1000,replaceT),年龄round(rnorm(10…

2021年认证杯SPSSPRO杯数学建模A题(第一阶段)医学图像的配准全过程文档及程序

2021年认证杯SPSSPRO杯数学建模 A题 医学图像的配准 原题再现: 图像的配准是图像处理领域中的一个典型问题和技术难点,其目的在于比较或融合同一对象在不同条件下获取的图像。例如为了更好地综合多种信息来辨识不同组织或病变,医生可能使用…

5年自动化测试,终于进字节跳动了,年薪30w其实也并非触不可及

一些碎碎念 什么都做了,和什么都没做其实是一样的,走出“瞎忙活”的安乐窝,才是避开弯路的最佳路径。希望我的经历能帮助到有需要的朋友。 在测试行业已经混了5个年头了,以前经常听到开发对我说,天天的点点点有意思没…

java计算机毕业设计springboot+vue+elementUI永加乡精准扶贫信息管理系统

项目介绍 系统设计的主要意义在于,一方面,对于网站来讲,系统上线后可以带来很大的便利性,精准扶贫网站管理属于非常细致的管理模式,要求数据量大,计算机管理可以提高精确性,更为便利的就是信息…

NF-κB 信号通路调节细胞因子转录

NF-κB 大家族哺乳动物 NF-κB 家族由五种成员组成:RelA/p65、c-Rel、RelB、p50 (NF-κB1) 和 p52 (NF-κB2),它们可以形成各种异源二聚体或者同源二聚体 (如常见 p50/RelA 异源二聚体),并通过与启动子的 κB 位点结合来激活大量基因。所有 N…

Mysql常用函数

Mysql常用函数 字段拼接(concat) CONCAT() 函数用于将多个字符串连接成一个字符串 格式: select CONCAT(str1,str2,…) from table_name; #查询商品表,返回一列:商品名称(价格)。 SELECT concat(prod_name,(,prod…

【论文阅读】Weakly Supervised Semantic Segmentation using Out-of-Distribution Data

一篇弱监督分割领域的论文,发表在CVPR2022上: 论文标题: Weakly Supervised Semantic Segmentation using Out-of-Distribution Data 作者信息: 代码地址: https://github.com/naver-ai/w-ood Abstract 作者认为…

专精特新小巨人的申报条件

专精特新企业分为市级专精特新、省级专精特新和国家级专精特新。 在2018年,开展了国家第一批专精特新“小巨人” 企业申报工作。为了引导中小企业积极走“专精特新”发展之路,加快新旧动能转 换步伐,提升自主创新能力、加快转型升级&#xf…

软考的网络工程师对就业有用吗?

考证只是一个结果,学会技能才是最重要的。 视工作而言,软考中级网络工程师的性价比还是非常高的,对于从事同类的技术人员,基础扎实一般可以裸考通过。 含金量嘛,计算机专业可以以考代凭,毕竟证书是人社部和…

安装谷歌服务框架2022最新版本22.45.15失败

在这里(谷歌play服务框架下载安装安卓版-谷歌服务框架2022最新版本(Google Play 服务)下载22.45.15官方手机版-蜻蜓手游网 (qt6.com)http://www.qt6.com/XiaZai/155507.html)下载了谷歌服务框架(Google Play 服务),其应用信息为: 包名:com.g…

Mutated 源代码解析 client (一)

Mutated , a C project https://github.com/scslab/mutated usage Main function in the client directory, mutated_synthetic.cc Line 14 parse the user arguments, such as “-h, -w, -c” parse_synthetic is implemented in client\opts_synthetic.cc Here, use th…

Dive into TensorFlow系列(3)- 揭开Tensor的神秘面纱

TensorFlow计算图是由op和tensor组成,那么tensor一般都用来代表什么呢?显然,像模型的输入数据、网络权重、输入数据经op处理后的输出结果都需要用张量或特殊张量进行表达。既然tensor在TensorFlow体系架构中如此重要,因此本文将带…

Redis通用命令和key的层级结构

目录 1 Redis数据结构介绍 2 Redis 通用命令 3 Redis命令-Key的层级结构 1 Redis数据结构介绍 Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样: value的数据类型共有8种,前面5中为基本数据类型&a…

5000立方米球罐设计

目 录 摘 要 I Abstract II 1 文献综述 1 1.1 课题研究的工程背景及理论、实际意义 1 1.2 球罐用钢 1 1.2.1 球罐用钢基本要求分析 1 1.2.2 国内外球罐的常用钢种 2 1.2.3 几种典型球罐用钢的优劣对比 2 1.3 球罐设计 3 1.3.1 球罐设计的执行标准及法规 3 1.3.2 球壳结构 4 1.3…

通过PLC网关如何实现三菱FX3U的远程上下载程序?

FX3U是三菱推出的高性能PLC品牌。基本性能大幅提升,晶体管输出型的基本单元内置了3轴独立最高100kHz的定位功能,并且增加了新的定位指令,从而使得定位控制功能更加强大,使用更为方便,受到企业的青睐。因此,…

PyQt5 QLabel标签

PyQt5 QLabel标签标签显示标签快捷键功能标签显示 QLabel背景色设置: palette QPalette() # 创建调色板 palette.setColor(QPalette.Window, Qt.green) # 设置调色板属性 label.setPalette(palette) # 标签设置Palette label.setAutoFillBackground(True) # 设为T…

【安全测试学习】数据库基础

以上来自学习极客时间《Web 安全攻防实战》课程内容,汇总整理思维导图。

记录多次安装mysql失败问题

首先说明一下,本人电脑已经安装过mysql,不过想从5.7版本升级到8.0 首先是卸载电脑上的mysql5.7版本,卸载过程如下: 进入控制面板,直接卸载所有mysql相关进入安装目录下删除mysql相关文件夹,通常在C:\Prog…

”互联网行业还在等金三银四或是金九银十?“,我劝你还是早做打算

对于今年的IT行业来说,可能真的根本没有所谓的“金三银四”或是“金九银十”。各大招聘网站或者软件上不管是大厂还是中小公司看似挂个招聘需求,但实际上这些公司并不非常缺人也不急着招人;我想今年程序员听的最多的一个词就是”互联网寒冬“…