异步的终极方案Async-Await 和Generator

news2024/9/20 5:43:24

Async-Await

基本介绍

之前解决异步我们一直使用Promise.then()方案,虽然解决了回调地狱的情况,但使用链式写法也并不特别优雅。比如看下面的代码。
在这里插入图片描述

所以就出现了一种号称异步的终极方案AsyncAwait。我们看他的定义

async 函数是使用async关键字声明的函数。async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用
await 关键字。async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用
promise。

我们可以把代码改写成下面这样:
在这里插入图片描述
通过上面的改写,我们可以使用同步的写法来实现异步编程。
需要注意的是,如果Promise是被reject的,需要通过try catch来捕获,代码如下所示:
在这里插入图片描述

注意事项

同一个async函数中的await前面的await先执行完成之后再执行后面的await,但不同async函数中的await是不会互相阻塞的。比如常见的就是forEach,我们看下面代码:

  const promise1 = () =>  new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const promise2 = () =>  new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 1000)
  })

  const promise3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 1000)
  })

  const promiseList = [promise1, promise2, promise3];

  // 打印结果不符合预期,三个值在等待1s后同时打印出来了并没有依次执行
  promiseList.forEach(async promiseItem => {
    const val = await promiseItem()
    console.log(val);
  })

执行效果如下所示:
在这里插入图片描述
执行效果不符合预期的原因就是forEach中的三个await在三个不同的async函数中,是相互独立的,并不会await
我们将代码改成如下方式:

const promise1 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const promise2 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 1000)
  })

  const promise3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 1000)
  })

  const promiseList = [promise1, promise2, promise3];


  // 输出符合预期
  // 先打印promise1 val等1s后打印promise2 val再等1s后打印promise3 val
  const fn = async () => {
    for (let i = 0; i < promiseList.length; i++) {
      // for循环的三个await 都在同一个async函数中
      // 所以第二个await 会等第一个await,第三个await会等第二个await依次执行
      const val = await promiseList[i]()
      console.log(val);
    }
  }
  fn()

执行效果如下所示:
在这里插入图片描述
打印效果符合预期,因为在这里三个await是在同一个async函数中的,所以会依次执行每一个await

Async的实现原理 - Generator

基本介绍

上面提到async 函数是 AsyncFunction 构造函数的实例async function* 声明定义了一个异步生成器函数,其返回一个 AsyncGenerator 对象。
比如说我们一般在代码中有三个方法分别获取一些后台的数据,如果我们使用async函数的话,一般这么写:

  const getAjaxData1 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const getAjaxData2 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 2000)
  })

  const getAjaxData3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 3000)
  })
 const promiseFn = async () => {
    const val1 = await getAjaxData1()
    console.log('接受到的值1', val1)
    const val2 = await getAjaxData2()
    console.log('接受到的值2', val2)
    const val3 = await getAjaxData3()
    console.log('接受到的值3', val3)
 }
promiseFn()

效果如下所示:
在这里插入图片描述
其实async是会使用function * 生成一个函数异步生成器。如下如所示:而每一个yield后面的逻辑,都需要生成器函数调用next()方法执行,其返回值有两部分组成

  1. done:表当前函数生成器是否执行完,false表未执行完,true表执行完
  2. value:每个yield后面的返回值(在这里就是返回一个promise对象)
    在这里插入图片描述
    实际上通过function *获取promise的值完整逻辑是这样:
const getAjaxData1 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const getAjaxData2 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 2000)
  })

  const getAjaxData3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 3000)
  })

  // generate执行每一个yield获取数据
  function* getAjaxDataStep() {
    yield getAjaxData1()
    yield getAjaxData2()
    yield getAjaxData3()
  }

  // 先执行 function* 一次,得到迭代器
  // promise实例通过.then方法获取promise的结果
  const myGenerate = getAjaxDataStep()
  myGenerate.next().value.then(val => {
    console.log('接受到的值1', val)
    return myGenerate.next().value
  }).then(val => {
    console.log('接受到的值2', val)
    return myGenerate.next().value
  }).then(val => {
    console.log('接受到的值3', val)
    return myGenerate.next().value
  }).then(val => {
    console.log('接受到的值4', val)
    return myGenerate.next().value
  })

其执行效果如下:
在这里插入图片描述
可以看到和上面使用Promise执行得到的效果一样。

实现一个自动执行的Generator

我们如果不使用上面async await或者是Promise 对象.then()的方式,我们也可以封装一个函数,自动执行function *中的每一个任务,来获取每个值

const getAjaxData1 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const getAjaxData2 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 2000)
  })

  const getAjaxData3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 3000)
  })

  // generate执行每一个yield获取数据
  function* getAjaxDataStep() {
    yield getAjaxData1()
    yield getAjaxData2()
    yield getAjaxData3()
  }

  // 一个高阶函数,接受function * 为入参
  const autoRunGenerateFn = (genFn) => {
    // 得先执行一次,得到迭代函数
    const myGen = genFn()
    let count = 1;
    // 递归执行该函数
    // 如果done: false就已知执行autoRun
    // 递归出口就是done: true
    const autoRun = () => {
      const result = myGen.next()
      const done = result.done
      const value = result.value
      if (done) {
        // 是否已近完成:完成递归出口
        console.log('已完成:', value);
      } else {
        // 是否已近完成:未完成 
        if (value instanceof Promise) {
          // promise的值
          value.then(v => {
            // promise的值通过.then获取
            console.log(`接受到的值${count}`, v)
            count++
            // 未全部完成,在promise.then内再次autoRun
            autoRun()
          })
        } else {
          // 非promise其值可直接获取
          console.log(`接受到的值${count}`, v)
          count++
          // 未全部完成,再次autoRun
          autoRun()
        }
      }
    }
    autoRun()
  }

  autoRunGenerateFn(getAjaxDataStep)

实现效果如下所示:
在这里插入图片描述

参考资料

Async
function *

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

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

相关文章

手枪钻改台钻(3D打印)

所需工具&#xff1a; 1、3D模型打印&#xff1b; 2、手枪钻&#xff1b; 首先在SOLIDWORKS里面新建一个电钻夹的模型&#xff1a; 第二步导出“STL”文件&#xff1a; 第三步将"STL"文件导入到3D切片工具 Cura_15.02.1 里面进行切片&#xff1a; 第四步导出“g-…

显示器方案

概述&#xff1a; USB HUB连接Host与Device之间&#xff0c;可以扩展出多个USB设备接口&#xff0c;使得一个Host能同时与多个Device进行数据连接。 注&#xff1a;普遍而言&#xff0c;一颗HUB扩展芯片可扩展4个USB下行接口&#xff0c;市面上1 to 7的HUB&#xff0c;一般使用…

【Python实训项目】pygame制作【飞机大战】

目录 一、课程设计目的及应用背景 二、课程设计内容 三、课程设计代码实现 1. 创建子弹类 2.创建玩家飞机类 3. 创建敌机类 4.检查键盘输入 5.创建主模块 四、测试结果 五、思考、心得和改进以及不明白的问题 附录 所有代码如下&#xff1a; 一、课程设计目的及应用背景 根据课…

网联V2X视频事件检测相机使用说明书

1 产品概览 网联 V2X视频事件检测相机 视频事件检测相机 &#xff0c;内置 1/1.8″逐行扫描 800万像素传感器&#xff1b;视 万像素传感器&#xff1b;视 频编码协议支持 H.265、H.264、MJPEG&#xff1b;具有 1个 10M/100M/1000M自适应以 太网 RJ45接口、 1路 RS485接口&#…

Bash中的eval命令

主要参考&#xff1a;What is the “eval” command in bash 简而言之&#xff0c;它让一个输入行被解析两次。 它是如何做到这一点的&#xff1f; shell有一系列步骤来解析一行命令。 shell读取它的输入shell将输入放入token:分为运算符和单词The shell parses the input i…

Boundary Smoothing for Named Entity Recognition

原文链接&#xff1a;https://aclanthology.org/2022.acl-long.490.pdf ACL 2022 介绍 问题 作者认为在命名实体任务中&#xff0c;由于实体的边界标注存在模糊、不一致的情况&#xff0c;比如一些实体中的冠词和修饰词。如下图所示中蓝色框和红色框中的内容都可以被认为是同一…

静态断言 static_assert

文章链接: https://subingwen.cn/cpp/static_assert/ 1. 断言 断言&#xff08;assertion&#xff09;是一种编程中常用的手段。在通常情况下&#xff0c;断言就是将一个返回值总是需要为真的判断表达式放在语句中&#xff0c;用于排除在设计的逻辑上不应该产生的情况。 比如…

NeurIPS 2022 | UniAD,一个模型解决所有类别的异常检测!代码已开源

paper&#xff1a;A Unified Model for Multi-class Anomaly Detection&#xff0c;Accepted by NeurIPS 2022. GitHub - zhiyuanyou/UniAD: [NeurIPS 2022] A Unified Model for Multi-class Anomaly Detection Introduction 异常检测已经取得了非常突出的进展。考虑到异常…

测试流程实战

目录&#xff1a; 测试流程梳理业务架构分析实战测试用例管理实战Bug 录入与管理实战如何写 Bug 报告编写 Bug 报告 1.测试流程梳理 2.业务架构分析实战 使用 plantuml 完成雪球 app 登录流程时序图plantuml 官网&#xff1a;使用简单的文字描述画UML图的开源工具。plantuml…

智云通CRM:电子元器件企业优化客户管理的智慧选择

在电子元器件行业&#xff0c;客户管理一直是企业发展的关键所在。如何高效地管理客户&#xff0c;提升销售业绩成为电子元器件企业亟需解决的问题。而智云通CRM作为一款专为企业客户管理和销售管理而生的软件&#xff0c;正成为电子元器件企业优化客户管理的智慧选择。 首先…

vue基础语法

目录 1&#xff1a;vue基础语法 2&#xff1a;内容渲染指令&#xff08;操作标签体中的内容&#xff09; 2.1&#xff1a;v-text指令 2.2&#xff1a;插值语法{{}}语法&#xff1a; 2.3&#xff1a;v-html指令 3&#xff1a;属性绑定指令 4&#xff1a;事件绑定&#xf…

我的大模型观:我眼中的LLM和AIGC

今年&#xff0c;大模型火的一塌糊涂。最近几个月paper with code上&#xff0c;前几名的论文几乎都是生成模型和LLM。参加AI相关的会议&#xff0c;也是千篇一律的LLM。国内的大厂争先恐后的发布自己的大模型&#xff0c;比如百度的文心、360的智脑、讯飞的星火等等&#xff0…

代码随想录算法训练营第三天 | 链表基础系列1-- 链表理论基础-移除链表元素-设计链表-反转链表(203、707、206)

链表基础系列1 链表基础移除链表元素203 移除链表元素代码随想录的代码 707 设计链表我的代码(错误太多&#xff0c;一致debug&#xff0c;没有用虚拟头&#xff0c;不想写了&#xff0c;是未通过的代码)代码随想录的代码小记&#xff1a;双链表好复杂&#xff0c;要仔细看。 2…

【狂神】MySQL - Delete 和 Truncate 的区别

1. DELETE 命令 语法 &#xff1a; delete from 表名 [where 条件] -- 删除数据 (避免这样写, 会全部删除) DELETE FROM student;-- 删除指定数据 DELETE FROM student WHERE id 1; 2. TRUNCATE 命令 作用 : 完全清空一个数据库表, 表的结构和索引约束不会变. -- 清空 stu…

【Linux】LVS+Keepalived高可用负载均衡群集

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 LVSKeepalived高可用负载均衡群集 一、Keepalived实现原理1.Keepalived案例分析2.Keepalived工具介绍3&#xff0c;Keepalived实现原理剖析4.Keepalived案例讲解5.Keepalived…

自媒体新手如何从零开始做自媒体?有哪些步骤流程?

自媒体已经成为了一种非常流行的个人创业方式&#xff0c;相比于传统的创业方式&#xff0c;自媒体的投入成本较低&#xff0c;且门槛较低。许多人都有一个梦想&#xff0c;希望成为一个自媒体人&#xff0c;成为自己的老板。但是&#xff0c;对于很多自媒体小白来说&#xff0…

基于Springboot+mybatis+mysql+vue实现企业注册模块功能

基于Springbootmybatismysqlvue实现企业注册模块功能 一、系统介绍二、功能展示1.主页面2.注册成功 三、数据库四、代码展示四、其他系统实现五、获取源码 一、系统介绍 该系统实现简单的企业信息注册&#xff0c;保存后&#xff0c;提示注册成功。 运行环境&#xff1a;idea…

IP 协议(网络层协议)

IP协议 IP 协议作用地址管理动态分配 IP 地址NAT 机制IPv6IP 地址的组成 路由选择 IP 协议作用 主要有两点 : 地址管理 为每个上网的设备分配一个唯一地址. 路由选择 两台主机间的信息交互, 具体走哪条线路. 地址管理 先来看看 IP协议 报文格式 : IP 协议最主要就是 32 位的…

2.9寸NFC卡片

应用广泛 无需电池 可挂、可横向/纵向摆放&#xff0c;适合多种场所 使用2.9寸电子纸墨水屏&#xff0c;持续显示不耗电 本产品无电池&#xff0c;节能环保&#xff0c;助力实现碳中和 ​ 基于电子纸墨水屏作为显示屏&#xff0c;符合当下节能环保、护眼的需求。质地轻薄、大…

Flameshot (火焰截图)截图无法插入汉字

前不久&#xff0c;Debian11升级至Debian12后&#xff0c;发现fcitx5无法用了&#xff0c;好似包也被删除了。于是重新安装了fcitx5,但发现了一个问题&#xff0c;利用Flameshot&#xff08;火焰截图&#xff09;截取图片时&#xff0c;无法对图片进行文字注释。如下图所示&…