重学JavaScript高级(十二):async/await-事件循环-面试高频

async/await-事件循环

前面我们学习了生成器和迭代器,那么在本篇文章中,我们主要讲解生成器与Promise的结合使用,从而引出async/await语法,同时会涉及面试中频次最高的一个知识点:事件循环

生成器与异步处理

  • 首先需要了解回调地狱
    • 在Promise出来之前,我们多次请求网络接口,有可能产生回调地狱
//伪代码
function request(url) {//请求的逻辑代码//返回一个结果return res;
}
//这样一层嵌套着一层,就是回调地狱
request("第一次").then((res1) => {request("第二次" + res1).then((res2) => {request("第三次" + res2);});
});
  • 而Promise的出现就解决了回调地狱的问题
    • 不明白return的返回值的,可以看我先前的文章,Promise–详解
function request(url) {return new Promise((resolve, reject) => {resolve(url);});
}//这样就解决了回调地狱的问题,将代码写成了链式调用
request("第一次").then((res1) => {console.log(res1);return request("第二次" + res1);}).then((res2) => {console.log(res2);return request("第三次" + res2);}).then((res3) => {console.log(res3);return request("结束");});
  • 以上代码虽然解决了回调地狱的问题,但是看上去不是很优雅
  • 因此可以和生成器相结合,优化出以下的代码
/*理想代码
function getData(){let res = yield request(url);let res1 = yield request(res);let res2 = request(res1);
}
*/
function request(url) {return new Promise((resolve, reject) => {resolve(url);});
}//在获取上一次结果之后,再进行下一次请求
//因此我们就可以应用到yield
function* getdata(url) {//yield空格后面的函数会直接执行let res = yield request(url);let res1 = yield request(res);let res2 = yield request(res1);console.log("请求结束");
}//执行生成器函数
let generator = getdata("test");
//这时候执行next,其返回值{value:Promise,done{false}}
//因此我们获取value调用then方法,就可以获取resolve的值
// console.log(generator.next());generator.next().value.then((res) => {console.log(res);//第一次执行完之后,就会去进行下一次请求generator.next(res + "test").value.then((res1) => {console.log(res1);//第二次执行完之后,就会进行第三次generator.next(res1 + "test").value.then((res2) => {console.log(res2);//此时运行最后一行console代码generator.next();});});
});
  • 而我们只希望有 getdata这种函数,所以引出了 async/await的语法
  • 实际上是Promise与生成器的语法糖
function request(url) {return new Promise((resolve, reject) => {setTimeout(() => {resolve(url);}, 2000);});
}//在获取上一次结果之后,再进行下一次请求
//因此我们就可以应用到yield
async function getdata(url) {//yield空格后面的函数会直接执行let res = await request(url + 1);console.log(res);let res1 = await request(res + 2);console.log(res1);let res2 = await request(res1 + 3);console.log(res2);console.log("请求结束");
}
getdata("test");

异步函数async/await

异步函数结构

函数分为:普通函数、生成器函数、异步函数,本次就开始学习异步函数

  • 在函数前面加 async即可,并当成普通函数执行
async function foo(){}const bar = async ()=>{}

异步函数的返回值

异步函数内部代码执行过程和普通函数是一致的

  • 普通函数没有指定返回值的时候,返回的是undefined;而异步函数返回值相当于被Promise.resolve()包裹
  • 如果返回的是一个Promise,则会由这个Promise决定
  • 如果返回的是一个对象,且对象中有then方法,则会由这个then方法决定
//其他情况之前的文章有讲过,不再演示
async function foo() {return 123;
}foo().then((res) => {console.log(res);//123
});

异步函数的异常

异步函数返回的是一个Promise,Promise有三种状态,正常执行是pending,return是fulfilled,那么何时抛出异常

  • 一些api使用错误时
  • 主动通过throw抛出
async function foo() {//在执行的时候,不会立即报错,会将错误信息用reject包裹"123".filter();//或者使用throw进行抛出异常throw new Error("主动抛出异常")return 123;
}foo().then((res) => {console.log(res); //123}).catch((err) => {console.log(err);//不会影响下面的代码执行console.log(123456);});

await关键字的使用

  • 必须在异步函数中使用
  • 一般后面跟着一个表达式,且这个表达式返回的是一个Promise(await后面可以直接跟一个普通函数或者数据,但是没有任何意义)
  • await会等到 Promise状态为fulfilled的时候,继续执行下面的代码
  • 若Promise返回的是rejected,就需要进行捕获
function request(count) {return new Promise((resolve, reject) => {setTimeout(() => {resolve(count);}, 1000);});
}async function getData() {let res = await request(1);console.log(res);let res1 = await request(res + 1);console.log(res1);let res2 = await request(res1 + 1);console.log(res2);
}getData();
  • 捕获异常的方式
function request(count) {return new Promise((resolve, reject) => {setTimeout(() => {reject(count);}, 1000);});
}
//方式一
async function getData() {let res = await request(1);console.log(res);let res1 = await request(res + 1);console.log(res1);let res2 = await request(res1 + 1);console.log(res2);
}
//因为异步函数的返回值是Promise
getData().catch((err)=>{console.log(err)
})//方式二,使用try catch
async function getData() {try {let res = await request(1);console.log(res);let res1 = await request(res + 1);console.log(res1);let res2 = await request(res1 + 1);console.log(res2);} catch (error) {console.log(error);}
}

await/async关键字的结合使用

  • 多个异步函数之间可以使用await
async function first(){}
async function second(){}
async function third(){}async function foo(){//第一个函数执行完成,且返回的是fulfilled状态,才会执行下面的代码await first()await second()await third()
}foo()

进程和线程

  • 进程和线程是操作系统的两个概念
    • 进程:计算机已经运行的程序(打开应用程序,就会开启至少一个进程),是操作系统管理程序的一种方式
    • 线程:操作系统能够运行 运算的调度的最小单位,通常情况下它被包含在进程中
  • 以下是形象的解释
    • 进程:可以认为,打开一个应用程序,就会默认启动一个进程(可以是多进程)
    • 线程,每一个进程中,都会至少启动一个线程来执行其中的代码(也有可能是多线程)
    • 所以可以说,进程是线程的容器

浏览器中的JS线程

  • JS是单线程(可以开启workers),但是JS的线程有自己的 容器:浏览器和Node

  • 浏览器是多进程的,当我们打开 一个新的tab页面的时候,就会开启一个新的进程,这是为了防止一个页面卡死,而造成所有页面无法响应

    • 每个进程会有多个线程,其中有一个线程是专门分给JS代码运行的

    • 即,JS代码一次只能做一次事情,如果这个线程十分耗时,当前线程 就会阻塞

  • 所以真正耗时的操作,并不是由JS线程在执行的

    • 浏览器每个进程都是多线程的,那么其他线程可以来完成这个耗时的操作
    • 比如网络请求、定时器,我们只需要在特定的时候执行应该有的回调即可

浏览器的事件循环

  • 首先了解一下事件队列
    • 浏览器在处理DOM监听,ajax请求,定时器的时候,会将相关函数里面的回调函数,放入事件队列中
    • 事件队列是一种数据结构,遵循先进先出的原则
  • 那么在JS执行的过程中,遇见了定时器等相关事件,又会怎么操作
    • 首先会执行函数,但是不会执行里面的回调函数
    • 将里面的回调函数交由浏览器其他线程
    • 其他线程在处理好之后,将回调函数放入事件队列中
    • 待执行栈中空了,就会从事件队列中取出待执行的函数

image.png

微任务和宏任务

事件循环中并非只维护者一个队列,事实上有两个队列:宏任务队列和微任务队列

  • 宏任务队列:ajax、setTimeout、setInterval、DOM监听、Rendering等

  • **微任务队列:**Promise的then回调(Promise中的代码会立即执行),Mutation Observer API queueMicrotask()等

  • 那么这两个队列的优先级是什么样的

    • main script中的代码优先执行
    • 再执行 任何一个宏任务之前(不是队列,是单个的宏任务),都会先去检查微任务队列中,是否有任务需要执行
      • 也就是说 每一个宏任务执行之前,必须保证微任务的队列是空的
      • 如果不为空,那么就优先执行微任务队列中的任务
      • 若微任务中的代码不断往微任务队列中增加任务,则宏任务中的代码则会无线延期

抛出异常-throw

遇到throw,代码就会停止执行,且后面的代码不会执行

  • throw后面可以跟String、number、boolean、对象
throw {message:"错误",code:-1001}
  • 后面可以接表达式(new Error)
throw new Error("这是错误")
//会把错误地方的调用栈,错误信息都会打印出来

捕获异常的方式

当代码抛出异常之后,不去捕获的话,就会很危险

try catch捕获异常

  • 如果我们在调用一个函数的时候,这个函数抛出了异常,但是并没有对 这个异常进行处理,那么这个 异常会继续传递到上一个函数调用中(会一层一层向上传递)
  • 如果到了全局,依旧没有对异常进行处理,浏览器就会终止程序
function bar(){throw "我是错误"
}
function foo(){try{foo()}catcha(error){console.log(error)}
}
foo()

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

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

相关文章

【分布式技术专题】「Zookeeper中间件」Paxos协议的原理和实际运行中的应用流程分析

Paxo算法介绍 Paxos算法是莱斯利兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的一致性算法。 Paxos产生背景 Paxos算法是基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一,其解决的问题就是在分…

SQL拆分字段内容(含分隔符)

问题描述: 在做数据迁移的过程中,我们希望对表中的某个字段根据分隔符进行拆分,得到多条数据,原代码有点意思,因此记录一下。 我们假设某条数据如下: IDSTRS1公司名称不能小于四个字,行业类别…

SSM框架,Spring-ioc的学习(上)

知识点引入 关于框架 框架( Framework )是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统,它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目…

python-pandas查漏补缺

1. create labels for Series 2. 3. 4. 用平均数等去填empty的格子 5. 6. 7.

SPSS双变量相关分析

双变量相关分析通过计算皮尔逊简单相关系数、斯皮尔曼等级相关系数、肯德尔等级相关系数及其显著性水平展开。其中皮尔逊简单相关系数是一种线性关联度量,适用于变量为定量连续变量且服从正态分布、相关关系为线性时的情形。如果变量不是正态分布的,或具…

基于springboot超市进销存系统源码和论文

随着信息化时代的到来,管理系统都趋向于智能化、系统化,超市进销存系统也不例外,但目前国内仍都使用人工管理,市场规模越来越大,同时信息量也越来越庞大,人工管理显然已无法应对时代的变化,而超…

小游戏和GUI编程(3) | 基于 SFML 的字符阵

小游戏和GUI编程(3) | 基于 SFML 的字符阵 1. 简介 使用 EasyX 图形库时, 官方第一个例子是字符阵。 EasyX 不开源, 也不能跨平台, API 陈旧, API 是 C 而不是 C。 现在使用 SFML 来实现字符阵, 克服 EasyX 的这些问…

Java并发基础:LinkedTransferQueue全面解析!

内容概要 LinkedTransferQueue类实现了高效的线程间数据传递,支持等待匹配的生产者-消费者模式,基于链表的无界设计使其在高并发场景下表现卓越,且无需担心队列溢出,丰富的方法和良好的可扩展性满足了各种复杂应用场景的需求。 …

2024牛客寒假算法基础集训营3部分题解

智乃与瞩目狸猫、幸运水母、月宫龙虾 链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 Ubuntu是一个以桌面应用为主的Linux发行版操作系统,其名称来自非洲南部祖鲁语或豪萨语的"ubuntu"一词,意思是"人性…

无心剑汉英双语诗《龙年大吉》

七绝龙年大吉 Great Luck in the Dragon Year 龙腾五岳九州圆 年吼佳音万里传 大漠苍鹰华夏梦 吉人天相铸奇缘 Dragon flies over five peaks watching the divine land so great and round, New Year’s call sends joyous tidal waves far across the world’s bound. The…

[office] 怎么在Excel2003菜单栏自定义一个选项卡 #其他#微信#知识分享

怎么在Excel2003菜单栏自定义一个选项卡 怎么在Excel2003菜单栏自定义一个选项卡 ①启动Excel2003,单击菜单栏--工具--自定义。 ②在自定义界面,我们单击命令标签,在类别中选择新菜单,鼠标左键按住新菜单,拖放到菜单栏…

SpringCloud-高级篇(十九)

我们已经学过使用 SpringAMQP去收和发消息,但是发和收消息是只是MQ最基本的功能了,在收发消息的过程中,会有很多的问题需要去解决,下面需要学习rabbitMQ的高级特性去解决 死信交换机:这个可以帮助我们实现消息的延迟的…

Git远程仓库的使用(Gitee)及相关指令

目录 1 远程仓库的创建和配置 1.1 创建远程仓库 1.2 设置SSH公钥 2 指令 2.1 git remote add 远端名称(一般为origin) 仓库路径 2.2 git remote 2.3 git push [-f] [--set-upstream] [远端名称 [本地分支名][:远端分支名]] 2.3 git clone url 2.4 git fetch 2.5 git p…

HCIA--NAT实验

1. 划分网段,配置接口IP地址,内网启用OSPF协议,并配置一对一的NAT: AR1配置: [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 10.1.1.1 24 [Huawei-GigabitEthernet0/0/0]int g0/0/1 [Huawei-GigabitEther…

【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏16(附项目源码)

本节最终效果演示 【独游开发记录】一个人开发的,类森林,七日杀生存游戏 文章目录 本节最终效果演示系列目录前言泛型单例添加声音脚步声鸭子动物音效人物各种操作音效砍树音效 效果源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#x…

[经验] 喉咙沙哑的原因及应对方法是什么 #学习方法#其他#媒体

喉咙沙哑的原因及应对方法是什么 生活中,喉咙不舒服是很常见的情况,尤其是喉咙沙哑,让人感到特别难受,影响睡眠和生活质量。那么喉咙沙哑怎么办呢?接下来我会分享一些简单易行的方法,帮助你缓解这种不适感…

政安晨:示例演绎机器学习中(深度学习)神经网络的数学基础——快速理解核心概念(一){两篇文章讲清楚}

进入人工智能领域免不了与算法打交道,算法依托数学基础,很多小伙伴可能新生畏惧,不用怕,算法没那么难,也没那么玄乎,未来人工智能时代说不得人人都要了解算法、应用算法。 本文试图以一篇文章,…

《CSS 简易速速上手小册》第2章:CSS 布局与定位(2024 最新版)

文章目录 2.1 Flexbox:灵活的布局解决方案2.1.1 基础知识2.1.2 重点案例:创建一个响应式导航菜单2.1.3 拓展案例 1:卡片布局2.1.4 拓展案例 2:中心对齐的登录表单 2.2 Grid 布局:网格系统的魔力2.2.1 基础知识2.2.2 重…

数字孪生:构建未来智慧社区的关键技术

随着科技的快速发展,数字孪生技术作为构建未来智慧社区的关键技术,正逐渐受到广泛关注。数字孪生技术能够实现物理世界与数字世界的交互映射,为智慧社区的建设提供强有力的支持。本文将探讨数字孪生技术在构建未来智慧社区中的作用和意义&…

枚举(Java)

一、概念 枚举是一种特殊的类。 格式: 修饰符 enum 枚举类名{ 对象名称1,对象名称2,....; 其他成员... } 二、枚举类的特点 1.枚举类的第一行只能罗列一些名称,并且这些名称都是常量,每个常量记住一个枚举类对象…