JavaScript 异步函数解析

news2025/1/16 4:47:41

前言

在学习 JavaScript 的过程中,理解并灵活运用异步相关知识是一件不容易的事情,这体现在代码可读性、健壮性上,好在 ES6 出现后挽回了这一局面,我们不再需要编写可读性不高的回调嵌套,也不用为了代码的健壮性而处处小心,这得益于 Promiseasync Function,它们给我们带来了更优秀的异步方案,今天我们就来学习异步函数相关知识。

async Function

虽然 Promise 对于传统的异步编程而言已经足够优秀,但精益求精的前辈们仍觉不足,Promise 的代码执行顺序对于开发者而言依然不够直观,为次,ES6 又提出了异步函数的相关概念。

异步函数,即一个特殊函数,通过在函数声明前添加 async 关键字标识

async function () {}

// or

const asyncArrow = async () => {} 

在异步函数中,能够使用关键词 await,它期待一个 Promise 值,并等待它决议,此时会暂停当前的程序并返回,控制权交还给主线程去执行其他任务

async function asyncFun() {await Promise.resolve();console.log('end');
}

asyncFun();
console.log('start'); 

上述代码中的正确执行顺序是 startend,这意味着函数调用后异步函数内的程序被暂停了,所以首先输出 start,然后输出 end,它的行为如下

1.调用异步函数
2.等待一个立即完成的 Promise
3.给立即完成的 Promise 添加一个微任务
4.返回
5.输出 start
6.微任务执行,asyncFun 程序恢复执行

await 虽然期待一个 Promise 值,但这不是必须的,它可以等待任何值,此时浏览器会将这个值包装为一个 Promise

async function asyncFun() {await 1;// 类似于await Promise.resolve(1);
} 

await 还能够获取到 Promise 的完成值

async function asyncFun() {const result = await Promise.resolve('fulfilled');console.log(result);
}

asyncFun(); // fulfilled 

如果等待的 Promise 被拒绝,那么就会在程序停止的位置抛出异常,我们能够通过 try...catch 进行异常捕获

async function asyncFun() {try {await Promise.reject('出错了');} catch(err) {console.log(err); // 出错了}
}

asyncFun(); 

异步函数的返回值会被包装成一个 Promise,如果没有显示的返回一个值,那么 Promise 完成值就是隐式返回的 undefined

async function asyncFun() {return 'complete';
}

asyncFun().then((value) => {console.log(value); // complete}) 

看到这里,其实异步函数的一些特性就讲完了,但是你有没有发现异步函数与生成器有很多相像的地方呢?

// async
async function asyncFun() {await Promise.resolve();
}

// generator
function *generator() {yield Promise.resolve();
} 

异步函数通过 async 来标识,而生成器通过在 function 关键字后添加 * 来标识,且它们都有各自的关键字能用于暂停程序的执行,但仅仅如此吗?看一下下面的代码

function *generator() {const result = yield Promise.resolve('generator');console.log(result); // generator
}

function run(exec) {// 假设 exec 必定是一个生成器// 获取生成器对象const g = exec();// 启动生成器,获取 yield、return 出的值const p = g.next();// 假设 p.value 必定是一个 Promisep.value.then(value => g.next(value),err => g.throw(err));
}

run(generator); 

上述代码中,我们添加了一个 run 函数,用于控制生成器的执行,run 函数利用 yield 双向数据传递的特性,接受生成器返回的值,这里我们假定为 Promise 值,并给它添加一个 then 处理回调,当这个 Promise 完成时在成功回调中调用生成器对象的 next 方法并将完成值注入到上一个导致程序暂停的 yield 身上,如果失败则调用 throw 方法并注入失败原因。

现在你发现了吗?其实异步函数只是一个语法糖,基于 Promise + 生成器我们也能实现完全相同的功能,所缺的只是一个控制生成器执行流程的执行器而已。

实现执行器

我们已经了解了异步函数其实只是 Promise + 生成器的语法糖,但还缺少了一个关键的执行器函数,以下是摘抄自 《你不知道的JavaScript》 的实现

function run(exec, ...args) {// 调用生成器函数并传递参数const it = exec.apply(this, args);return Promise.resolve().then(function handleNext(value) { // 不断处理 next 调用const next = it.next(value); // 获取 next 返回值// 立即执行函数,传递 next 返回值return (function handleResult(next) {if (next.done) { // 执行完毕直接返回return next.value;} else {// 未执行完毕则给 yield 出的 Promise 添加then处理回调// 同时避免 yield 非 Promise 值,需要进行一层 resolve 包装return Promise.resolve(next.value).then(handleNext, // 成功回调,继续调用 handleNext 获取下一个值function handleErr(err) { // 失败回调return Promise.resolve(it.throw(err) // 注入异常,如果异常在生成器内部被处理则继续调用 handleResult).then(handleResult);})}})(next);})
} 

拥有了这个执行器函数,我们就能够基于 Promise + 生成器来模拟异步函数的行为了

function *generator() {try {const result = yield Promise.reject('出错啦');} catch(err) {console.log(err); // 出错啦}const data = yield Promise.resolve('data');console.log(data);return 'complete';
}

run(generator).then(value => {console.log(value); // complete})

// 出错啦
// data
// complete 

结语

本文讲述了异步函数的相关概念,并了解了异步函数其实只是语法糖,我们完全能够自己实现相同的功能,可以看到,生成器在这中间充当了非常重要的角色,以后可能还会基于生成器出现更多强大的模式。

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

spring boot使用TaskScheduler实现动态增删启停定时任务

TaskScheduler 概述 TaskScheduler是spring 3.0版本后,自带了一个定时任务工具,不用配置文件,可以动态改变执行状态。也可以使用cron表达式设置定时任务。 被执行的类要实现Runnable接口 TaskScheduler是一个接口,它定义了6个…

具有梯度流的一类系统的扩散图卡尔曼滤波(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

FEW-SHOT TEXT CLASSIFICATION WITH DISTRIBUTIONAL SIGNATURES

摘要 在这篇文章中,我们利用元学习进行文本分类,元学习在计算机视觉中有相当好的效果,在这里,低层次的模式可以跨学习任务迁移的,,然而,直接将这个方法应用到文本中是具有挑战性的,…

【自学C++】C++输入cin

C输入cin C输入cin教程 在 C 语言 中我们需要捕获用户的键盘输入,可以使用 scanf 函数。scanf 函数在输入时,我们必须要指定输入的数据类型对应的格式化符,挺不方便。 在 C 中,我们要捕捉用户的输入,直接使用 std 命…

【目标检测】C-GhostNet

1、论文 论文题名:《GhostNet: More Features from Cheap Operations》 arxiv:https://arxiv.org/abs/1911.11907 github:https://github.com/huawei-noah/ghostnet 作者翻译:https://zhuanlan.zhihu.com/p/109325275 2、摘要 本篇论文是华…

2023/1/6 Vue学习笔记-2

1 收集表单数据 收集表单数据&#xff1a; 若&#xff1a;<input type"text">&#xff0c;则v-model收集的是value的值&#xff0c;用户输入的就是value值。 若&#xff1a;<input type"radio">&#xff0c;则v-modle收集的是value的值&…

String、StringBuffer和StringBuilder

String 类是不可变类&#xff0c;即一旦一个 String 对象被创建以后&#xff0c;包含在这个对象中的字符序列是不可改变的&#xff0c;直至这个对象被销毁。 Java 提供了两个可变字符串类 StringBuffer 和 StringBuilder&#xff0c;中文翻译为“字符串缓冲区”。 StringBuil…

Python和MySQL对比(3):用Pandas 实现MySQL的子查询、like_regexp、case when_if语法效果

文章目录一、前言二、语法对比数据表子查询like/regexpcase when/ifin三、小结一、前言 环境&#xff1a; windows11 64位 Python3.9 MySQL8 pandas1.4.2 本文主要介绍 MySQL 中的子查询、like/regexp、case when/if 如何使用pandas实现&#xff0c;同时二者又有什么区别。 注…

FPGA中二进制数的运算

一、B比特的二进制数可以表示的范围 无符号整数&#xff1a;0~2B-1 有符号整数&#xff1a;-2B-1~2B-1-1 二、有符号数&#xff08;补码&#xff09;与十进制数之间的转换 D&#xff1a;十进制数 B&#xff1a;二进制数aB-1aB-2a1a0的位数 aB-1:符号位 三、符号位拓展 在使…

Android 12 蓝牙适配 Java版

Android 12.0蓝牙适配前言正文一、Android版本中蓝牙简介二、新建项目① 配置build.gradle② 配置AndroidManifest.xml三、打开蓝牙① 打开蓝牙意图② 请求BLUETOOTH_CONNECT权限意图四、蓝牙扫描① 扫描者② 扫描回调③ 扫描方法④ 执行扫描⑤ 应用不推导物理位置五、页面显示…

基于Java+Springboot+vue体育用品销售商城平台设计和实现

基于JavaSpringbootvue体育用品销售商城平台设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取…

Docker部署JAVA项目

一、 Docker安装 1、yum 包更新到最新 yum update 2、安装需要的软件包&#xff0c; yum-util 提供yum-config-manager功能&#xff0c;另外两个是devicemapper驱动依赖的 yum install yum-utils device-mapper-persistent-data lvm2 -y 3、 设置yum源 yum-config-manage…

等级保护----1、网络安全等级保护一级安全测评要求

文章目录一、安全测评通用要求1、安全物理环境1.1 物理访问控制1.2 防盗窃和防破坏1.3 防雷击1.4 防火1.5 防水和防潮1.6 温湿度控制1.7 电力供应2、安全通信网络2.1 通信传输2.2 可信验证3、安全区域边界3.1 边界防护3.2 访问控制3.3 可信验证4、安全计算环境4.1 身份鉴别4.2 …

使用关键字like进行模糊查询

【模糊查询】&#xff1a;使用关键字like [支持%或者下划线匹配&#xff0c;%匹配任意多个字符&#xff0c;一个下划线只匹配任意一个字符。] 实例&#xff1a; 查询名字中带有字母o的员工&#xff1a; select * from emp where ename like %o%; 找出名字以T结…

2000-2021.3月土地交易高频数据库(含爬取代码和数据)

2000-2021.3月土地交易高频数据库&#xff08;含爬取代码和数据&#xff09; 1、时间跨度&#xff1a;2000-2021年3月1日 2、指标&#xff1a;年份、电子监管号、所在省份、所在城市、所在区县、经度、纬度、项目名称、项目位置、面积、土地来源、土地用途、供地方式、土地使…

Python CV 实现风格化图片转换

前几天遇到一个风格化图片转换的需求&#xff0c;效果像这样&#xff1a; 像这样&#xff0c;需要用纯色圆形填充图像&#xff0c;形成风格化的图片样式。 实现原理 整体原理还是比较简单的&#xff0c;有点类似与马赛克的处理方式。 假设图片宽 w 像素&#xff0c;高 h 像素…

FLStudio21中文版下载及水果软件2023功能介绍

如今&#xff0c;越来越多的音乐人选择使用音乐制作软件来进行音乐的创作&#xff0c;一台电脑、一款软件以及一个外接MIDI就是一个小型的音乐工作站&#xff0c;而如今非常流行的FL Studio&#xff08;在国内被称为“水果”&#xff09;成了音乐界无论萌新还是大佬们的首选&am…

二阶微分算子与反锐化屏蔽

二阶微分算子 任意二阶微分必须满足&#xff1a;灰度不变的区域微分值为0&#xff1b;灰度台阶或斜坡的起点处微分值非0&#xff1b;沿着斜坡的微分值为0。由于处理离散值&#xff0c;因此微分用差分近似&#xff1a; 二维图像f(x,y),沿着两个空间坐标轴求解二阶微分&#xff1…

逆向-还原代码之little-or-big (Arm 64)

// 源代码 #include <stdio.h> /* * 2016/9/29 yu liang. */ int test_one(void) { int i1; char *p(char *)&i; if(*p1) printf("Little_endian\n"); // Little_endian else printf("B…

一文弄清楚Vue中的computed与watch的区别

1.实现业务当我们点击按钮&#xff0c;就会切换h1标签的语句内容&#xff0c;再次点击按钮&#xff0c;h1标签的语句内容就会恢复&#xff0c;每次点击按钮&#xff0c;浏览器都会输出一个语句来表达监视到了h1的内容改变2.Vue中的computed计算属性2.1利用Vue中的computed计算属…