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

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

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

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

  • 为什么 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");

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

list下

文章目录 注意&#xff1a;const迭代器怎么写&#xff1f;运用场合&#xff1f; inserterase析构函数赋值和拷贝构造区别&#xff1f;拷贝构造不能写那个swap,为什么&#xff1f;拷贝构造代码 面试问题什么是迭代器失效&#xff1f;vector、list的区别&#xff1f; 完整代码 注…

3、非数值型的分类变量

非数值型的分类变量 有很多非数字的数据,这里介绍如何使用它来进行机器学习。 在本教程中,您将了解什么是分类变量,以及处理此类数据的三种方法。 本课程所需数据集夸克网盘下载链接:https://pan.quark.cn/s/9b4e9a1246b2 提取码:uDzP 文章目录 1、简介2、三种方法的使用1…

idea运行卡顿优化方案

文章目录 前言一、调整配置1. idea.properties2. idea.vmoptions3.heap size4.Plugins5.Inspections 总结 前言 本人电脑16G内存&#xff0c;处理器i7 10代&#xff0c;磁盘空间也够用&#xff0c;整体配置够用&#xff0c;但运行idea会很卡&#xff0c;记录优化过程&#xff…

Linux中关于head命令详解

head的作用 head用于查看文件的开头部分的内容。 head的参数 -q隐藏文件名-v 显示文件名-c<数目>显示的字节数-n<数目>显示的行数 head的案例 # 查看yum.log前五行内容 head -5 yum.log

文件操作和IO(1)

认识文件 先来认识狭义上的文件(存储在硬盘(磁盘)上).针对硬盘这种持久化的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般. 注意:硬盘 ! 磁盘 磁盘属于…

1885_Arduino 1306 OLED屏幕的使用

全部学习汇总&#xff1a; g_arduino: Arduino hack笔记 (gitee.com) 以前我基于Arduino做过一个环境信息采集的小工具&#xff0c;采集到的信息存储到SD卡中。当时的笔记&#xff1a; 341_Arduinopython分析天气变化导致颈椎病发的原因_颈椎病相关数据分析python-CSDN博客 以前…

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

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

关于Hive架构原理,尚硅谷

最近学习hive 时候&#xff0c;在做一个实操案例&#xff0c;具体大概是这样子的&#xff1a; 我在dataGip里建了一个表&#xff0c;然后在hadoop集群创建一个文本文件里面存储了数据库表的数据信息&#xff0c;然后把他上传到hdfs后&#xff0c;dataGrip那个表也同步了我上传到…

gin渲染篇

1. 各种数据格式的响应 json、结构体、XML、YAML类似于java的properties、ProtoBuf package mainimport ("github.com/gin-gonic/gin""github.com/gin-gonic/gin/testdata/protoexample" )// 多种响应方式 func main() {// 1.创建路由// 默认使用了2个中…

java idea 中的 Scratches and Consoles

IDEA 中&#xff0c;"Scratches and Consoles" 是一个用于临时代码编辑和交互式开发的工具窗口&#xff0c;作用如下&#xff1a;Scratches&#xff08;草稿&#xff09;&#xff1a;Scratches 是一个用于临时编写和运行代码片段的工具&#xff0c;你可以在其中创建临…

四.Winform使用Webview2加载本地HTML页面并互相通信

Winform使用Webview2加载本地HTML页面并互相通信 往期目录本节目标核心代码实现HTML代码实现的窗体Demo2代码效果图 往期目录 往期相关文章目录 专栏目录 本节目标 实现刷新按钮点击 C# winform按钮可以调用C# winform代码显示到html上点击HTML按钮可以调用C# winform代码更…

2024首更---Web Service 教程

Web Services 简介 Web Services 可使您的应用程序成为 Web 应用程序。 Web Services 通过 Web 进行发布、查找和使用。 您应当具备的基础知识 在继续学习之前&#xff0c;您需要对下面的知识有基本的了解&#xff1a; HTMLXML 如果您希望首先学习这些项目&#xff0c;请在…

万户 ezOFFICE wf_process_attrelate_aiframe.jsp SQL注入漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

lv14 信号量、互斥锁、并发控制机制选择

1 信号量&#xff1a;基于阻塞的并发控制机制 a.定义信号量 struct semaphore sem; b.初始化信号量 void sema_init(struct semaphore *sem, int val); c.获得信号量P int down(struct semaphore *sem);//深度睡眠 int down_interruptible(struct semaphore *sem);//浅度…

【Linux】python版本控制

文章目录 1.查看目前python的版本2.添加软件源并更新3.选择你想要下载的版本4.警示&#xff1a;没必要设置默认版本误区千万千万不要覆盖python3软链接解决办法 5.安装pip换源 6.环境管理 网上有很多教程都是教导小白去官方下载之后编译安装。但是&#xff0c;小白连cmake是什么…

【JavaEE】CAS

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…

使用xbindkeys设置鼠标侧键

1.安装如下包 sudo apt install xbindkeys xautomation 2.生成配置文件 xbindkeys --defaults > $HOME/.xbindkeysrc 3.确定侧键键号 在终端执行下面的代码&#xff1a; xev | grep button 此时会出现如下窗口&#xff0c;将鼠标指针移动到这个窗口上&#xff1a; 单…

原生微信小程AR序实现模型动画播放只播放一次,且停留在最后一秒

1.效果展示 0868d9b9f56517a9a07dfc180cddecb2 2.微信小程序AR是2023年初发布&#xff0c;还有很多问提&#xff08;比如glb模型不能直接播放最后一帧&#xff1b;AR识别不了金属、玻璃材质的模型等…有问题解决了的小伙伴记得告诉我一声&#xff09; 微信官方文档地址 3.代码…

kotlin $ (字符串模版)的使用

$ 在kotlin 中当做字符串模版使用&#xff0c;作用就是在字符串里面识别自己定义的字符 例如打印一个字符 这个时候编译就提示我们使用字符串模版的是个 $ 的作用就是识别字符串里面的i 字数有点少了&#xff0c;在写一个demo private fun String.appendArchive(): String …

AI日报:扎克伯格瞄准AGI通用人工智能

文章目录 Meta瞄准通用人工智能领域Meta的目标Meta的产品 FAIR移动和装载H100扎克伯格对人工智能竞争对手的真实动机持怀疑态度Meta抛弃了元宇宙吗&#xff1f; Meta瞄准通用人工智能领域 Meta首席执行官马克扎克伯格&#xff08;Mark Zuckerberg&#xff09;在一份可能改变全…