【js】js 异步机制详解 Generator / Async / Promise

news2024/11/26 4:48:45

三种语法功能放在一起,是因为他们都有相似特点:

  • 维护某种状态
  • 在未来恢复状态并执行

本文重点回答以下几个问题:

  • 为什么 Generator 和 Async 函数的 代码执行流 都可以简化成树形结构?
  • async 函数为什么返回一个 promise?返回了怎样一个 promise?
  • async 函数如何优雅的转换成 promise 函数?
    Generator 用法和思考

基本生成器

generator 是生成器,从生成器的行为来看,它就是一个迭代器。

function* foo(end) {
    var idx = 0;
    while (idx < end) {
        yield idx; // 保存当前状态并返回 当前idx
        idx += 5;
    }
}


const iterator = foo(20);
// iterator.next().value 手动迭代
for (let val of iterator) { // 输出 0 到 19
    console.log(val);
}

对应到生成器函数中,他会在 yield 的时候保存当前函数的状态。那么,函数的状态保存在哪? 习惯了 C 函数的我们很难想象如何将状态保存在函数中。可能协程和 setjmp/longjmp 可以辅助我们思考生成器的实现,但还是不能直接对应到生成器函数的行为。比如:生成器需要记录如下信息

  • Idx 的大小, end 大小
  • pc 指针在 第 5 行

想在 c 语言中保存上述信息是不容易的。是因为函数在 js 是第一类值,上述的信息便可以保存在函数值本身内。对应quickjs 相当于将 pc 指针 和 参数 信息,local_buf,保存在对应的函数对象 (实际上保存在 StackFrame 中) 内,而 cur_ref 信息 quickjs 本身就已经保存了。而 c/c++ 语言的函数没有类似功能。

嵌套多层迭代器

  • 树形生成器结构。讲 yield 这种语法,是因为它跟 async 的执行流非常像,但会可以看到
  • 使用树形生成器生成 0 ~ 20 的值
    暂时无法在飞书文档外展示此内容
function* inner(index) {
  var i = 0;
  while (i < 5) {
      yield index + i;
      i++;
  }
}

// 错误的生成器用法,但是启发我们思考,这样的用法,是不是很像 await?
// function* foo(index) {
//   while (index < 20) {
//     yield inner(index);
//     index += 5;
//   }
// }

// 正确版本
function* foo1(index) {
    while (index < 20) {
        var it = inner(index);
        for (let val of it) {
            yield val;
        }
        index += 5;
    }
}

// 语法糖版本
function* foo2(index) {
   while (index < 20) {
     yield *inner(index);
     index += 5;
   }
}


const iterator = foo1(0);

for (let val of iterator) {
    console.log(val);
}

async 执行流的思考

  • Async 的代码执行流程是下面这棵树的中序遍历

  • Async 函数的代码执行流程如下

    • 先执行绿色部分代码,直到最底层的 promise,由于promise 测试未决定(pending 状态),整个函数暂停退出。
    • promise resolve或reject 后,恢复中序遍历执行淡蓝色部分代码,直到下一个 promise。这里相当于执行了 promise 的 then 回调。
      • Plus. 这里仍然有一次 暂停执行。因为 新的 promise 是 pending 状态。
    • 新的 promise resolve或reject 后,继续恢复执行 。。。。
    • Async 函数的结尾的 return 道理上相当于一个 then 回调
  • Async 函数的代码执行流程非常像 树形 generator

  • 都说 async 函数返回一个 promise,它返回的 promise 到底是什么?
    在这里插入图片描述

  • Async 函数框架

async function asyncfunc() {
    code1;
    var a1 = await asyncfunc1(); // 这里是一个 async 函数
    code2;
    var a2 = await promise1; // 这里是一个 真正的 Promise
    code3;
    var a3 = await asyncfunc2(); // 这里是一个 async 函数
    codeR;
    return x;
}

asyncfunc();
  • 可以尝试使用如下代码,单步调试来验证
async function wrapFetch(url) {
    console.log("wrapFetch code1");
    const response = await fetch(url);
    console.log("wrapFetch code2");
    return response;
}

// 异步函数示例
async function fetchAsyncData() {
    console.log("fetchAsyncData code1");
    const response = await wrapFetch('https://qqlykm.cn/api/weather/get');
    console.log("fetchAsyncData code2 ok?", response.ok);
    const data = await response.json();
    console.log(data);
}
 
// 调用异步函数
console.log("before async");
fetchAsyncData();
console.log("after async");
  • 问题:能把 async 函数串行化么?答案是不能。只要使用了 promise (叶子节点是真正的 promise),代码的执行就要遵循 promise 的执行规则,而 promise 本身就是异步的,因此无法串行化。

一点点 Promise 的思考

想要理解如何将 async 函数返回的到底是什么 promise?以及如何将 async 函数翻译成一个 promise 的形式,需要深入理解 then 函数。

Then 函数
  • Then 函数返回一个 promise,称其为 p。
  • Then 注册的回调函数中也会返回一个值。那么 返回 一个值 和 返回一个 promise 有什么不同?
  • 如果回调函数返回了 promise,那么这个promise 和 then 返回的 promise p 是 什么关系?官方文档:链式调用。
    • 结论:他俩在行为上是 同一个 promise
    • 知道这一点,就可以轻松的把一个 async 函数改写成 promise 的形式了。
const myPromise = new Promise((resolve, reject) => {
    // 进行异步操作
        const randomNumber = Math.random();
        // 如果操作成功,调用resolve并传递结果
        resolve(randomNumber);
});
 
// 使用Promise对象
myPromise.then(result => {
        // 操作成功时的处理逻辑
        console.log("操作成功,结果为:" + result);
        **return 1; **
        **return new Promise((rs, rj) => rs(2));**
    }).then( val => {
        console.log(val);
    })

将 async 改写成 promise

如何改写?下面用一个示例来展示

注:这里并没有关注异常执行流,而是只关注异步执行流。异常执行流要想完全转换,需要在promise里注册错误回调,并且 async 和 promise 都要捕获异常

// async 写法

async function asyncfunc() {
    code1;
    var a1 = await asyncfunc1();    // 这里是一个 async 函数
    code2;
    var a2 = await promise1;     // 这里是一个 真正的 Promise
    code3;
    var a3 = await asyncfunc2();    // 这里是一个 async 函数
    codeR;
    return x;
}

asyncfunc();
// 等价的promise 写法

function func() {
  return new Promise((resolve, reject) =>{
    code1;
    func1()
      .then( (val) => resolve(val) );
  }).then((a1) => {
      code2;
      return promise1;
  }).then((a2)=> {
      code3;
      // 返回一个Promise
      return func2(); 
  }).then((a3) => {
      codeR;
      return x;
  });
}
func();

结论:async 语法糖有什么好处呢?

  • 代码写起来更简洁、优雅,意味着 减少出错率
  • 词法作用域更广。写 code2 可以用 code1 部分的变量,写 promise 的时候就没办法了。

可验证代码

异步调用一个 网络 api 接口

  • async 版本
async function wrapFetch(url) {
    console.log("wrapFetch code1");
    const response = await fetch(url);
    console.log("wrapFetch code2");
    return response;
}

// 异步函数示例
async function fetchAsyncData() {
    console.log("fetchAsyncData code1");
    const response = await wrapFetch('https://qqlykm.cn/api/weather/get');
    console.log("fetchAsyncData code2 ok?", response.ok);
    const data = await response.json();
    console.log(data);
}
 
// 调用异步函数
console.log("before async");
fetchAsyncData();
console.log("after async");
  • 等价 Promise 版本
function wrapFetch(url) {
    return new Promise( function (resolve, reject) {
        console.log("wrapFetch code1");
        fetch(url).then( (response) => {
            resolve(response);
            console.log("wrapFetch code2");
        })
    });
}


function fetchAsyncData() {
    return new Promise(function (resolve, reject) {
        console.log("fetchAsyncData code1");
        wrapFetch('https://qqlykm.cn/api/weather/get')
            .then( val => resolve(val) )
    })
    .then( (response) => {
        console.log("fetchAsyncData code2 ok?", response.ok);
        return response.json()
    })
    .then((data) => {
        console.log(data);
    });
}

// 调用异步函数
console.log("before async");
fetchAsyncData();
console.log("after async");

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

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

相关文章

关于常见分布式组件高可用设计原理的理解和思考

文章目录 1. 数据存储场景和存储策略1.1 镜像模式-小规模数据1.2 分片模式-大规模数据 2. 数据一致性和高可用问题2.1 镜像模式如何保证数据一致性2.2 镜像模式如何保证数据高可用2.2.1 HA模式2.2.2 分布式选主模式 2.3 分片模式如何数据一致性和高可用 3. 大规模数据集群的架构…

Qt事件过滤

1.相关说明 监控鼠标进入组件、出组件、点击组件、双击组件的事件&#xff0c;需要重写eventFilter函数 2.相关界面 3.相关代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui-&…

【MySQL】一文总结MVCC多版本并发控制

目录 MVCC 介绍当前读和快照读当前读快照读 MVCC 原理解析隐式字段Undo Log版本链Read ViewRead View 可见性原则 RC 和 RR 下的 Read ViewRC 下的 Read ViewRR 下的 Read View小结RR 级别下能否防止幻读总结 MVCC 介绍 在当今高度并发的数据库环境中&#xff0c;有效的并发控…

【Python学习】Python学习21- 正则表达式(1)

目录 【Python学习】Python学习21- 正则表达式&#xff08;1&#xff09; 前言re.match函数实例 re.search方法re.match与re.search的区别参考 文章所属专区 Python学习 前言 本章节主要说明Python的正则表达式。 正则表达式是一个特殊的字符序列&#xff0c;它能帮助你方便的…

鸿蒙 HarmonyOS ArkTS ArkUI 动画 中心缩放、顶部缩放、纵向缩放

EntryComponentstruct Index {State widthA: number 200State heightA: number 200onPageShow():void{animateTo ( {duration: 2000,iterations: -1,curve:Curve.Linear}, () > {this.widthA 0this.heightA 0} )}build() {Column() {// 中心缩放Column(){}.width(this.wi…

Golang 搭建 WebSocket 应用(八) - 完整代码

本文应该是本系列文章最后一篇了&#xff0c;前面留下的一些坑可能后面会再补充一下&#xff0c;但不在本系列文章中了。 整体架构 再来回顾一下我们的整体架构&#xff1a; 在我们的 demo 中&#xff0c;包含了以下几种角色&#xff1a; 客户端&#xff1a;一般是浏览器&am…

MyBatis 系列:MyBatis 源码环境搭建

文章目录 一、环境准备二、下载 MyBatis 源码和 MyBatis-Parent 源码三、创建空项目、导入项目四、编译 mybatis-parent五、编译 mybatis六、测试总结 一、环境准备 jdk&#xff1a;17 maven&#xff1a;3.9.5 二、下载 MyBatis 源码和 MyBatis-Parent 源码 Mybatis&#x…

【Linux】进程间通信——system V 共享内存、消息队列、信号量

需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网&#xff0c;轻量型云服务器低至112元/年&#xff0c;优惠多多。&#xff08;联系我有折扣哦&#xff09; 文章目录 写在前面1. 共享内存1.1 共享内存的概念1.2 共享内存的原理1.3 共享内存的使用1.3.1 …

在Java中调企微机器人发送消息到群里

目录 如何使用群机器人 消息类型及数据格式 文本类型 markdown类型 图片类型 图文类型 文件类型 模版卡片类型 文本通知模版卡片 图文展示模版卡片 消息发送频率限制 文件上传接口 Java 执行语句 String url "webhook的Url"; String result HttpReque…

二叉树的直径(LeetCode 543)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路参考文献 1.问题描述 给你一棵二叉树的根节点&#xff0c;返回该树的直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的长度由它们之间边数…

Odrive 学习系列四:如何使用脚本自动初始化odrive配置

一、背景: 在学习markbase的教程后,发现odrive的初始化配置命令确实有点多。尽管odrive有自动补全: 且可以通过 ctrl + → 来快速补全: 但是对初学者而言,仍旧有比较大的工作量。 而针对于此,我们可以通过powershell脚本的方式来解决这个问题。 二、设计初始化…

继电器开关电路图大全

继电器是一种电控制器件&#xff0c;是当输入量&#xff08;激励量&#xff09;的变化达到规定要求时&#xff0c;在电气输出电路中使被控量发生预定的阶跃变化的一种电器。它具有控制系统&#xff08;又称输入回路&#xff09;和被控制系统&#xff08;又称输出回路&#xff0…

Siemens-NXUG二次开发-导入与导出(可移除参数)prt文件[Python UF][20240121]

Siemens-NXUG二次开发-导入与导出&#xff08;可移除参数&#xff09;prt文件[Python UF][20240121] 1.python uf函数1.1 NXOpen.UF.Part.Import1.2 NXOpen.UF.Part.ImportPartModes1.3 NXOpen.UF.Group.AskGroupData1.4 NXOpen.UF.Obj.AskTypeAndSubtype1.5 NXOpen.UF.Part.Ex…

Frenet坐标系下动态街道场景的最优轨迹生成

0 前言 有两个主要算法已经在实际中使用&#xff1a; &#xff08;1&#xff09;大多数研究组采用插值来解决规划问题&#xff0c;如奥迪、斯坦福最近演示中使用了回旋曲线&#xff0c;贝塞尔以及多项式曲线也被其他研究组使用。主要原因是在结构化环境中增强映射可以提供所需…

第十一站:多态练习ODU

实现动态切换 ODU.h #pragma once #include <iostream> using namespace std; #define ODU_TYPE_311_FLAG "311" #define ODU_TYPE_335_FLAG "335" enum class ODU_TYPE {ODU_TYPE_311,ODU_TYPE_335,ODU_TYPE_UNKNOW };class ODU{ public:ODU();//发…

Jan AI本地运行揭秘:首次体验,尝鲜科技前沿

&#x1f31f;&#x1f30c; 欢迎来到知识与创意的殿堂 — 远见阁小民的世界&#xff01;&#x1f680; &#x1f31f;&#x1f9ed; 在这里&#xff0c;我们一起探索技术的奥秘&#xff0c;一起在知识的海洋中遨游。 &#x1f31f;&#x1f9ed; 在这里&#xff0c;每个错误都…

带POE网络变压器与2.5G/5G/10G网络变压器产品特点介绍

Hqst华轩盛(石门盈盛)电子导读&#xff1a;一起来了解带POE网络变压器与2.5G/5G/10G网络变压器产品特点&#xff1f; 一﹑带POE网络变压器与2.5G/5G/10G网络变压器产品特点介绍 首先、POE网络变压器产品与常规不带POE产品的区别&#xff1a; 带POE网络变压器主要要求是耐电流等…

按键检测知识

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…

frida https抓包

web端导入证书、https代理即可解决大部分需求&#xff0c;但是&#xff0c;有些app需要处理ssl pinning验证。 废话不多说。frida处理ssl pin的步骤大体如下。 安装python3.x,并在python环境中安装frida&#xff1a; pip install frida pip install frida-tools下载frida-se…

C#,入门教程(22)——函数的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(21)——命名空间&#xff08;namespace&#xff09;与程序结构的基础知识https://blog.csdn.net/beijinghorn/article/details/124140653 一、函数的基本概念 一个软件的结构大体如下&#xff1a; 大厦application: a plaza { --…