深入理解 JS 异步编程 03(Generator 生成器与 async/await)

2024-08-29 13:20

本文主要是介绍深入理解 JS 异步编程 03(Generator 生成器与 async/await),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Generator

Promise 在解决异步编程回调地狱的问题时,采用了链式的调用方式,但在代码比较复杂的业务逻辑中还是有可能出现嵌套问题的 → 如 ↓

生成器(Generator)是一种特殊的函数,它可以让我们更加灵活的控制函数再什么时候继续执行、暂停执行等

基本使用

  • 1. 创建生成器函数: 生成器函数通过 "function*" 进行创建 → 在声明函数时,添加一个星号 * 就代表该函数为一个生成器函数

  • 2. yield: 生成器函数通过 yield 关键字来控制函数的执行流程(函数在执行时碰到 yeild 关键字就会停止执行,如果像继续向后执行,需要通过对应生成器函数返回的对象中的 next 方法来继续执行后续的代码)

  • 3. 返回值: 生成器函数返回一个 Generator 对象(生成器事实上是一种特殊的迭代器)

  • function* gen() { // -- 1. 创建生成器函数console.log("generator function executed")yield "kong" // -- 2. 每一个 yield 关键字都会中断函数的执行console.log("kong ~")yield "deng"console.log("deng ~")yield "wang"console.log("wang ~")return "END" // -- 返回值处也会进行中断(即可以理解为也是一个特殊 yield 中断语句)
    }const generator = gen() // -- 3. 调用生成器函数 → 会根据 yeild 关键字,返回一个对应的 generator 对象(特殊的迭代器对象) → log: generator function executed// -- 4. 通过 next 方法来继续执行后续的代码
    console.log(generator.next()) // log: { value: 'kong', done: false } → kong ~
    console.log(generator.next()) // log: { value: 'deng', done: false } → deng ~
    console.log(generator.next()) // log: { value: 'wang', done: false } → wang ~
    console.log(generator.next()) // log: { value: 'END', done: true }
    console.log(generator.next()) // log: { value: 'undefined', done: true }
    

next 函数传参

  • 我们可以在调用 next 函数时,给其传递参数,该参数会作为上一个 yield 语句的返回值

    • 即: 我们可以通过该方式来使其异步请求,当请求完成时,我们在执行后续的代码,并将对应异步的结果返回到对应的位置上(生成器函数异步转同步)
    • 如下示例
  • function* gen(prefix) {const value1 = yield prefix + "_aaa"const value2 = yield value1 + "_bbb"const vlaue3 = yield value2 + "_ccc"return vlaue3
    }const generator = gen("kong")const res1 = generator.next()
    const res2 = generator.next(res1.value)
    const res3 = generator.next(res2.value)
    const result = generator.next(res3.value)
    console.log(result) // { value: 'kong_aaa_bbb_ccc', done: true }
    console.log(generator.next()) // { value: undefined, done: true }
    

generator 对象中的 throw 方法

  • 我们可以通过 generator 对象上的 throw 来抛出错误,可以在生成器中通过 try ... catch 来进行捕获

  • function* gen(prefix) {console.log("函数执行~")try {yield "kong"} catch (e) {console.log("生成器函数内部捕获异常:", e) // }console.log("函数执行接收~")
    }const generator = gen("kong")
    const res1 = generator.next()
    generator.throw("error message")/** ↑ log: ↓函数执行~生成器函数内部捕获异常: error message函数执行接收~*/
    

因为生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器(如前面可迭代对象中的 “类数组中的示例”)

  • const likeArray = {0: 1,1: 2,2: 3,length: 3, // -- 数据内部实现了迭代的方法,我们可以自己进行元编程来实现相应的方法// [Symbol.iterator]() {//     let idx = 0//     return {//         next: () => {//             return { value: this[idx], done: idx++ === this.length }//         }//     }// }// -- 因为生成器函数返回的是一个迭代器对象,所以上面的方式我们也可以通过生成器函数来进行实现*[Symbol.iterator]() {let [idx, len] = [0, this.length]while (idx !== len) {yield this[idx++]}}
    }
    console.log([...likeArray])
    

yield 语法糖:*

  • 该语法糖可以可以依次迭代一个可迭代对象,每次迭代其中的一个值(如 ↓)

  • function* createArrayIterator(arr) {yield* arr
    }// -- 等价于function* createArrayIterator(arr) {for (const item of arr) {yield item}
    }
    

异步处理方案🔺

我们这里通过一个案例来一步一步的查看异步处理方案的过渡

例子: 我们模拟向服务器发送 3 次请求,且除第一个请求外的每一个请求都依赖上一个请求的结果 ↓

  • function requestData(arg) { // -- 模拟请求: 请求方法return new Promise((resolve, reject) => {setTimeout(() => {resolve(arg)}, 1000)})
    }
    
  • 回调地狱

    • function getData() { // -- 模拟请求: 请求处理requestData("kong").then(res1 => {requestData(res1 + "_deng").then(res2 => {requestData(res2 + "_wang").then(res3 => {console.log(res3)})})})
      }
      
  • 可以通过 Promise 中 then 的链式调用来解决该回调地狱问题

    • function getData() {requestData("kong").then(res1 => requestData(res1 + "_deng")).then(res2 => requestData(res2 + "_wang")).then(res3 => {console.log(res3)})
      }
      
    • 虽然这种链式调用已经帮我们解决了回调地狱的问题,但上面的代码阅读性还是比较差的

    • 我们可以通过 Generator 生成器还进行优化,通过生成器函数我们可以灵活的控制代码的执行与暂停(异步转同步)

  • Generator 生成器

    • function* getData() {const res1 = yield requestData("kong")const res2 = yield requestData(res1 + "_deng")const res3 = yield requestData(res2 + "_wang")console.log(res3)
      }+ 可以看出如果通过生成器函数来处理异步代码,在生成器函数中可以像同步的一样来进行处理,代码就更加简洁、阅读性高了
      + 不过下面 generator 对象处理部分就会显得特别不好阅读(不过它们都是有规律的,我们可以统一封装成一个工具函数来对该生成器进行处理,或借助一个 co 库)// -- 不过生成器函数返回的是一个 generator 对象,我们还需要通过该 generator 来控制代码的执行与暂停操作
      const generator = getData()const { value, done } = generator.next()
      if (!done) {value.then(res1 => {const { value, done } = generator.next(res1)if (!done) {value.then(res2 => {const { value, done } = generator.next(res2)if (!done) {value.then(res3 => {const { value, done } = generator.next(res3)})}})}})
      }
      
  • Generator + co : 自动处理生成器函数

    • co: 该库就是早期用于处理生成器函数的一个工具函数,如下示例

    • function* getData() {const res1 = yield requestData("kong")const res2 = yield requestData(res1 + "_deng")const res3 = yield requestData(res2 + "_wang")console.log(res3) // kong_deng_wangreturn res3
      }
      
      + npm i co
      const co = require("co")+ 该工具函数可以自动帮我们来处理生成器函数
      co(getData).then(res => { // -- getData return 的结果console.log(res) // kong_deng_wang
      })
      
    • 🔺我们也可以根据处理生成器函数代码的规律来自己写一个 co 处理函数

      • function co(generatorFn) {return new Promise((resolve, reject) => { // -- 该工具函数最终返回一个 Promise 对象const generator = generatorFn()function next(nextArg) { // -- 定义一个递归方法const { value, done } = generator.next(nextArg)if (!done) { // -- 当生成器还未迭代结束时,继续调用 next 进行迭代(如果存在异步代码,先等待异步代码的执行)// -- 通过 Promise.resolve 来避免,当 value 不是 promise 时,无法通过 then 来进行判断... → 即: 当 vlaue 不是 promise 时,先将其转换成 promise 先Promise.resolve(value).then(res => {next(res) // -- 递归迭代,并将 data 传入上一次的 yeild 返回值中}, (e) => generator.throw(e)) // -- 错误处理(可在生成器函数中通过 try ... catch 进行捕获)}else resolve(value) // -- 如果 done 为 true: 即已完成 → 兑现最终的结果}next(undefined)})
        }+ TEST
        co(getData).then(res => {console.log(res) // kong_deng_wang
        })
        

Async / Await

上面虽然说通过 Generator + co 来处理异步代码时,可以有比较好的处理,但是每次都需要引入一下 co 库与一些使用,还是稍微有那么一点麻烦

在 ECMAScript 2017(ES8)中就为我们提供了 async/await 关键字(语法糖),可以使我们能更好的处理异步代码(也可以理解为: async/await === Generator + co)

async/await 就相当于是 generator + co 的语法糖

它们允许我们以更接近于同步代码的方式来编写异步代码,从而提高代码的可读性和可维护性

基本使用

  • 1. async 关键字用于声明一个异步函数,这意味者函数内部可以使用 await 关键字

  • 2. await 关键字

    • await 关键字后面需要跟上一个表达式,这个表达式会返回一个 Promise 对象
    • awiat 关键字使代码暂停执行,会等待所返回的 Promise 对象变成 fulfilled 状态,才会继续执行后续的代码
    • 🔺await 关键字后面所跟的值,同样分为前面 Promise 中的 resolve 参数的三种情况
      • 如果 await 后面是一个普通的值,那么会直接返回这个值
      • 如果 await 后面是一个普通的值,那么会直接返回这个值
      • 如果 await 后面的表达式,返回的 Promise 是 reject 的状态,那么会将这个 reject 结果直接作为函数的 Promise 的 reject 值
  • 3. 异步函数的返回值

    • 异步函数的返回值相当于被包裹 到Promise.resolve 中 → 该返回值与生成器函数中的返回值同理(也分为前面的三种情况)
      • 如果返回的是一个普通的值,那么会直接返回这个值
      • 如果我们的异步函数的返回值是 Promise,状态由会由 Promise 决定
      • 如果我们的异步函数的返回值是一个对象并且实现了 thenable,那么会由对象的 then 方法来决定
  • 4. 使用例子(我们以上面 Generator + co 的例子通过 async/awiat 的方式做一个示例)

    • + Generator + co 方式
      const co = require("co")function* getData() {const res1 = yield requestData("kong")const res2 = yield requestData(res1 + "_deng")const res3 = yield requestData(res2 + "_wang")console.log(res3)return res3
      }co(getData).then(res => {console.log(res) // kong_deng_wang
      })
      
    • + async/await 示例
      async function getData() { // -- 通过 async 关键字,声明该函数是一个异步函数const res1 = await requestData("kong") // -- 通过 await 关键字,使其异步函数转成同步执行const res2 = await requestData(res1 + "_deng")const res3 = await requestData(res2 + "_wang")console.log(res3)return res3
      }getData().then(res => {console.log(res) // kong_deng_wang
      })
      
    • 该两种方式可以理解为几乎是等价的(async/await 就相当于是 Generator + co 的一种语法糖)

总结

1. Promise 出现是为了解决异步回调的问题(通过链式调用)

2. Iterator 与 Generator 的出现是为了解决 Promise 过多的链式调用代码阅读性不友好的问题,但使用通常需要借助一个工具函数来对生成器函数进行处理(通过代码的暂停执行与继续执行 - 异步转同步)

3. async/await 是异步编程的一个语法糖,相当于 Generator + co 工具函数的一个结合,使其可以在使用时,不需要每次都引入相应的工具函数,与使用起来更见的方便(Generator + co 的语法糖)

4. 基本过渡流程: 回调地狱 → Promise → Generator → async/await

这篇关于深入理解 JS 异步编程 03(Generator 生成器与 async/await)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1117979

相关文章

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte