JavaScript重难点突破:期约与异步函数

news2025/7/10 10:47:45

同步和异步

  1. 同步(Synchronous)​
  • 定义:任务按顺序依次执行,前一个任务完成前,后续任务必须等待。

  • 特点:阻塞性执行,程序逻辑直观,但效率较低

  1. 异步(Asynchronous)​
  • 定义:任务发起后无需等待结果,程序继续执行其他操作,待任务完成后通过回调或事件通知处理结果。

  • 特点:非阻塞性执行,支持并发,资源利用率高

简单来说,同步就是刷牙然后煮面,异步就是让面在一边煮着一边跑去刷牙。

期约

期约是对尚不存在结果的一个替身。

在ES6中,期约是一种引用类型(Promise),使用new操作符实例化。

期约状态机

期约对象有三种状态,这些状态是期约对象内置的,除了调用相应的API,否则不能对其进行更改。

  • pending(待定)

  • fulfilled(兑现)

  • rejected(拒绝)

新建一个期约对象,并且期约对象还没进行任何操作时,期约对象的状态为pending,当期约对象已经被成功解决后,则转为fulfilled,而解决失败则转为rejected,具体让期约状态转换的函数后面介绍。

待定(pending)是期约的最初始状态。在待定状态下,期约可以落定(settled)为代表成功的兑现(fulfilled)状态,或者代表失败的拒绝(rejected)状态。
无论落定为哪种状态都是不可逆的。只要从待定转换为兑现或拒绝,期约的状态就不再改变。而且,也不能保证期约必然会脱离待定状态。因此,组织合理的代码无论期约解决(resolve)
还是拒绝(reject),甚至永远处于待定(pending)状态,都应该具有恰当的行为。
重要的是,期约的状态是私有的,不能直接通过JavaScript检测到。这主要是为了避免根据读取到的期约状态,以同步方式处理期约对象。
另外,期约的状态也不能被外部JavaScript代码修改。这与不能读取该状态的原因一样:【期约故意将异步行为封装起来,从而隔离外部的同步代码】

《JavaScript高级程序设计第四版》

如何理解期约对象

正如前面介绍的,期约对象是专门为异步编程而设计的,期约对象是对尚不存在结果的一个替身。

举个例子,你参加了一场考试,试卷由他人进行批改(异步操作),而你在试卷批改的过程中是自由的,你可以吃饭睡觉,你也可以尝试查询试卷批改的状态。

如果试卷还在批改当中,则返回pending,如果已经批改结束,并且你已经通过了考试,返回fulfilled,如果未通过考试,则返回rejected

理解了上述的场景,我们就能够理解期约对象和期约对象的状态机了。

  • 期约对象代表了某一个异步操作。

  • 期约状态机代表了异步操作的完成状态。

如何控制期约对象的状态机

期约对象的状态是私有的,只能通过期约对象内部的API进行操作。

向期约对象中传入一个执行器函数(回调函数),期约对象会向执行器函数传入两个参数resolvereject,调用resolve()使期约状态变为fulfilled,调用reject()使期约状态变为rejected

    const waiter1 = new Promise((resolve, reject) => { });
    const waiter2 = new Promise((resolve, reject) => resolve());
    const waiter3 = new Promise((resolve, reject) => reject());

    // 其中undefined表示期约对象在完成后期待一个返回值,但这里没有给返回值
    console.log('waiter1:', waiter1); // waiter1: Promise {<pending>}
    console.log('waiter2:', waiter2); // waiter2: Promise {<fulfilled>:undefined}
    console.log('waiter3:', waiter3); // waiter1: Promise {<rejected>:undefined}

请添加图片描述

期约对象的静态方法

  • Promise.resolve()

    这个方法可以直接创建一个fulfilled状态的期约对象,这个期约对象的值由传入的参数指定。

        const settled1 = Promise.resolve(3)
        const settled2 = Promise.resolve('我是字符串')
        const settled3 = Promise.resolve(new Promise(() => {}))
    
        console.log(settled1) // Promise {<fulfilled>: 3}
        console.log(settled2) // Promise {<fulfilled>: '我是字符串'}
        console.log(settled3) // Promise {<pending>}
    

    可以看到,我们传入什么值,期约对象就会返回什么值。

    但是如果我们传入的是另一个期约对象,则会直接返回传入的期约对象。

  • Promise.reject()

    这个方法可以直接创建一个rejected状态的期约对象,这个期约对象的值由传入的参数指定。

        const settled1 = Promise.reject(3)
        const settled2 = Promise.reject('我是字符串')
        const settled3 = Promise.reject(new Promise(() => {}))
    
        console.log(settled1) // Promise {<rejected>: 3}
        console.log(settled2) // Promise {<rejected>: '我是字符串'}
        console.log(settled3) // Promise {<rejected>: Promise {<pending>}}
    

    这个方法和Promise.resolve()类似,也会将传入的值作为期约对象的值返回。

    但不同的是,如果传入一个期约对象,那么这个期约对象也会作为期约对象的值返回。

    调用reject()或者Promise.reject()都会抛出一个异步错误。同步代码块中的trycatch结构无法捕获到异步错误,只有异步结构中才能捕获异步错误

    期约的实例方法

  • 实现Thenable方法

    在ECMAScript暴露的异步结构中,任何对象都有一个then()方法。

  • Promise.prototype.then()

    then()方法挂载在Promise的原型上,所以被所有Promise实例对象共享。

    then()方法接收两个回调函数参数,第一个参数在Promise对象的状态落定为fulfilled时执行,第二个参数在Promise对象的状态落定为rejected时执行。

    如何理解then()方法

    在平时写js方法时,我们都是使用的同步代码块,也就是说,写在后面的代码一定后执行,比如我们在前面一行计算let sum = 10 + 1,那么我们就可以在这一行后面的任意位置输出sum,因为sum的计算是写在前面的,在同步代码块中,他已经被计算完毕了。

    现在我们使用了异步编程,我们已经知道Promise对象内置了一个状态机,它用于通知自己是否执行完毕。

    由于Promise是异步执行的,假如我们在同步代码块中读取Promise对象,我们有可能获取3种结果。如果我们需要打印Promise的返回值,我们不可能在同步代码块中不停的检测Promise的状态,这样会导致后面的代码无法执行,这就违背了异步编程的初衷。

    所以我们使用then()方法,then()方法可以想象为一个触发器,设置好then()方法后,只要Promise对象settled到了任意一种状态,就会触发then()方法中设定好的函数,这样我们就可以异步的处理Promise的返回值而无需再在同步代码块中处理Promise的返回值了。

        const p1 = new Promise((resolve, reject) => {
          // 1秒后返回10 + 1的计算结果
          setTimeout(() => resolve(10 + 1), 1000);
        });
    
        p1.then(() => {
          console.log('我计算完成,被触发了');
          console.log('我是then中的p1:',p1)
        }, () => { console.log('我计算失败,被触发了'); });
    
        console.log('我是同步代码块中的p1:',p1)
    

    请添加图片描述

    可以看到,同步代码块由于执行的比较快,已经运行到输出Promise对象的值了,但是此时Promise对象还没执行完,状态为pending,而then中却可以正常输出Promise的返回值11,这是因为then()中的第一个参数只有在Promise对象状态为fulfilled时才被调用。

    我们将then()方法的第一个参数称为onResolved处理程序,第二个参数称为onRejected处理程序。

  • .then() 返回的 Promise 状态如何确定?

    回调返回值类型决定状态

    • 返回普通值​(非 Promise 对象):新 Promise 会被 Promise.resolve() 包装为 ​fulfilled 状态

      p.then(() => 42); // 新 Promise 状态:fulfilled,值:42
      
    • 抛出异常:新 Promise 变为 ​rejected 状态,异常对象作为拒绝原因

      p.then(() => { throw new Error("fail"); }); // 状态:rejected,原因:Error对象
      
    • 返回 Promise 对象:新 Promise 将 ​继承该 Promise 的状态和值

      p.then(() => Promise.reject("error")); // 新 Promise 状态:rejected,原因:"error"
      

    因此通过.then()方法返回的也是Promise对象,所以也有.then()方法,.then()方法可以进行链式调用

        const p1 = new Promise((resolve, reject) => {
          // 3秒后返回10 + 1的计算结果
          setTimeout(() => resolve(double(1)), 1000);
        });
    
        p1.then(value => {
          return value
        })
        .then(value => {
          return double(value)
        })
        .then(value => {
          return double(value)
        })
        .then(value => console.log(value)) // 8
    
  • Promise.prototype.catch()

    等于then(null,() => {}),也就是onRejected处理程序。

  • Promise.prototype.finally()

    传入finally()的回调函数能保证一定被执行,和try-catch-finally中的finally用法一致。

  • Promise.all()和Promise.race()

    这两个方法都可以传入一个包含多个期约的可迭代对象,常见方法是传入一个包含多个期约的数组。

    • Promise.all():会等待传入的期约全部兑现后才兑现,如果有一个期约待定或者拒绝,则返回待定或者拒绝

    • Promise.race():会返回根据第一个兑现或者拒绝的期约决定状态的新期约对象。

异步函数

通过刚刚期约对象的学习我们了解到,期约对象是异步执行的,因此如果想操作期约对象的流程必须要使用then()方法。

但是这样同样导致了一个问题,同步代码块和异步代码块被完全的区分开了,从使用了期约对象开始,所有和这个期约对象有关的流程都要在then中实现,这会使得then()方法的函数体变得很大,并不好维护。

于是,从ES8开始引入了一组新的关键字async/await用于解决这个问题。

基本概念

  • async 函数

    • 声明方式:在函数前添加 async 关键字,如 async function fetchData() {}

    • 返回值:始终返回一个 Promise 对象。若函数返回非 Promise 值(如字符串、数值),该值会被自动包装为 resolve 状态的 Promise

      async function example() { return "Hello"; }
      example().then(console.log); // 输出 "Hello"
      
  • await 关键字

    • 使用范围:仅能在 async 函数内部使用。

    • 功能:暂停当前 async 函数的执行,等待右侧的 Promise 完成(resolvereject),并返回解析后的值

           async function fetchUser() {
       const response = await fetch('/api/user'); // 等待请求完成
       return response.json();
      }
      

    简单来说,关键字async声明了这个函数应该被异步执行,关键字await表明被async声明的函数应该被停止执行,等到await右侧的表达式返回值后才被继续执行。

async

async关键字标记的函数会返回一个Promise对象,如果返回的值不是Promise对象,则会使用Promise.resolve()对返回的值进行包装。

async 函数的执行流程

  1. 同步代码的立即执行
  • 未遇到 awaitasync 函数内部的代码会按照同步顺序立即执行,与普通函数的行为完全一致。例如:

    async function demo() {
     console.log("A");  // 同步执行
     console.log("B");  // 同步执行
    }
    demo();
    console.log("C");
    

    输出顺序为:A → B → C

  • 本质async 函数被调用时,其函数体内的同步代码会直接进入主线程的同步任务队列,立即执行。

  1. ​**await 对执行流程的干预**
  • 遇到 await:函数会暂停当前执行,将 await 后的表达式(通常是 Promise)放入微任务队列,并交出主线程控制权。此时,外部同步代码会继续执行。例如:

    async function demo() {
     console.log("A");
     await new Promise(resolve => setTimeout(resolve, 1000)); // 暂停
     console.log("B");  // 异步执行(微任务)
    }
    demo();
    console.log("C");
    

    输出顺序为:A → C → (1秒后) B

  • 关键机制await 后的代码会被封装为微任务,等待当前同步代码执行完毕后才会继续执行。

    async function heavyTask () {
      console.log("开始耗时操作");
      // 没有await
      for (let i = 0; i < 1e9; i++);  
      console.log("耗时操作完成");
    }
    heavyTask();
    console.log("外部代码");

请添加图片描述

    async function heavyTask () {
      console.log("开始耗时操作");
      // 有await
      await '123'
      for (let i = 0; i < 1e9; i++);  
      console.log("耗时操作完成");
    }
    heavyTask();
    console.log("外部代码");

请添加图片描述

可以看到,在await后的代码才会作为异步代码执行,否则async修饰的代码会像普通函数一样同步执行。

await

await关键字期待右侧是一个实现了Thenable接口的对象。

但如果不是,则await不会等待,而是将右侧视为一个已经fulfilled的期约对象,直接返回。不会将值包装为Promise对象

    const func = async () => {
      console.log(await '123') 
    }
    // 注意,123没有被包装为Promise对象
    func() // '123'

如果await右侧是一个Promise对象,并且尚未settled,那么异步程序会在await处阻塞,停止运行直到右侧的Promise对象已经fulfilled或者rejected

异步函数的特质不会扩展到嵌套函数,await只能在async标记的函数中使用

如果需要进行并行优化,不要每调用一次async函数就等待await返回值,而是一次性将async函数全部调用后,再按照需要的顺序等待await的返回值。

    const asyncFunc1 = async () => { console.log(1); return 'a' }
    const asyncFunc2 = async () => { console.log(2); return 'b' }
    const asyncFunc3 = async () => { console.log(3); return 'c' }
    const asyncFunc4 = async () => { console.log(4); return 'd' }

    // 错误示范
    const run1 = async () => {
      console.log(await asyncFunc1()) 
      console.log(await asyncFunc2()) 
      console.log(await asyncFunc3()) 
      console.log(await asyncFunc4()) 
    }

    // 正确示范
    const run2 = async () => {
      const res1 = asyncFunc1()
      const res2 = asyncFunc2()
      const res3 = asyncFunc3()
      const res4 = asyncFunc4()

      console.log(await res1) 
      console.log(await res2) 
      console.log(await res3) 
      console.log(await res4) 
    }

在错误示范中,每次调用async函数都等到async函数返回后才执行下一个async函数。
而正确示范中,将所有async函数全部执行后,再等待async函数的返回值。

正确示范也可以通过Promise.all()来实现。

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

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

相关文章

蓝桥杯高频考点——高精度(含C++源码)

高精度 前言高精度加法例题思路及代码solution 1&#xff08;初阶版 40分&#xff09;solution 2&#xff08;完全体 AC&#xff09; 高精度乘法例题思路及代码solution 1&#xff08;TLE 但是代码很清晰&#xff09;solution 1的问题solution 2&#xff08;优化 AC&#xff09…

【机器人】复现 GraspNet 端到端抓取点估计 | PyTorch2.3 | CUDA12.1

GraspNet是通用物体抓取的大规模基准的基线模型&#xff0c;值得学习和复现。 本文分享使用较新版本的PyTorch和CUDA&#xff0c;来搭建开发环境。 论文地址&#xff1a;GraspNet-1Billion: A Large-Scale Benchmark for General Object Grasping 开源地址&#xff1a;https:…

视频联网平台智慧运维系统:智能时代的城市视觉中枢

引言&#xff1a;破解视频运维的"帕累托困境" 在智慧城市与数字化转型浪潮中&#xff0c;全球视频监控设备保有量已突破10亿台&#xff0c;日均产生的视频数据量超过10万PB。然而&#xff0c;传统运维模式正面临三重困境&#xff1a; 海量设备管理失序&#xff1a;…

《网络管理》实践环节03:snmp服务器上对网络设备和服务器进行初步监控

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 应用拓扑图 3.0准备工作 所有Linux服务器上&#xff08;服务器和Agent端&#xff09;安装下列工具 yum -y install net-snmp net-snmp-utils 保证所有的HCL网络设备和服务器相互间能…

ubuntu中使用安卓模拟器

本文这里介绍 使用 android studio Emulator &#xff0c; 当然也有 Anbox (Lightweight)&#xff0c; Waydroid (Best for Full Android Experience), 首先确保自己安装了 android studio &#xff1b; sudo apt update sudo apt install openjdk-11-jdk sudo snap install…

py数据结构day3

思维导图&#xff1a; 代码1&#xff08;完成双向循环链表的判空、尾插、遍历、尾删&#xff09;&#xff1a; class Node:def __init__(self, data):self.data dataself.next Noneself.prev Noneclass DoubleCycleLink:def __init__(self):self.head Noneself.tail None…

STM32单片机入门学习——第8节: [3-4] 按键控制LED光敏传感器控制蜂鸣器

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.02 STM32开发板学习——第8节: [3-4] 按键控制LED&光敏传感器控制蜂鸣器 前言开…

【JavaScript】十三、事件监听与事件类型

文章目录 1、事件监听1.1 案例&#xff1a;击关闭顶部广告1.2 案例&#xff1a;随机点名1.3 事件监听的版本 2、事件类型2.1 鼠标事件2.1.1 语法2.1.2 案例&#xff1a;轮播图主动切换 2.2 焦点事件2.2.1 语法2.2.2 案例&#xff1a;模拟小米搜索框 2.3 键盘事件2.3.1 语法2.3.…

通过ansible+docker-compose快速安装一主两从redis+三sentinel

目录 示例主机列表 架构参考 文件内容 安装脚本 ansible变量&#xff0c;需修改 ansible配置文件和主机清单&#xff0c;需修改 运行方式 验证故障转移master 涉及redis镜像和完整的脚本文件 示例主机列表 架构参考 文件内容 安装脚本 #!/bin/bashset -e export pa…

mysql docker容器启动遇到的问题整理

好几个月没折腾mysql的部署&#xff0c;弄了下&#xff0c;又遇到不少问题 问题一&#xff1a;Access denied for user ‘root‘‘172.18.0.1‘ docker容器启动后&#xff0c;本地navicat 连接报这个错误 查到两个方案&#xff0c;一个貌似是要让root用户能在任意ip地址&…

长短期记忆神经网络(LSTM)基础学习与实例:预测序列的未来

目录 1. 前言 2. LSTM的基本原理 2.1 LSTM基本结构 2.2 LSTM的计算过程 3. LSTM实例&#xff1a;预测序列的未来 3.1 数据准备 3.2 模型构建 3.3 模型训练 3.4 模型预测 3.5 完整程序预测序列的未来 4. 总结 1. 前言 在深度学习领域&#xff0c;循环神经网络&…

C++多继承

可以用多个基类来派生一个类。 格式为&#xff1a; class 类名:类名1,…, 类名n { private: … &#xff1b; //私有成员说明; public: … &#xff1b; //公有成员说明; protected: … &#xff1b; //保护的成员说明; }; class D: public A, protected B, private C { …//派…

【深度学习新浪潮】DeepSeek近期的技术进展及未来动向

一、近期技术进展 模型迭代与性能提升 DeepSeek-V3-0324版本更新:2025年3月24日发布,作为V3的小版本升级,参数规模达6850亿,采用混合专家(MoE)架构,激活参数370亿。其代码能力接近Claude 3.7,数学推理能力显著提升,且在开源社区(如Hugging Face)上线。DeepSeek-R1模…

工业4.0时代下的人工智能新发展

摘要&#xff1a;随着德国工业4.0时代以及中国制造2025的提出&#xff0c;工业智能化的改革的时代正逐渐到来&#xff0c;然而我国整体工业水平仍然处于工业2.0水平。围绕工业4.0中智能工厂、智能生产、智能物流这三大主题&#xff0c;结合国内外研究现状&#xff0c;对人工智能…

监控易一体化运维:高性能与易扩展,赋能运维新高度

在当今数字化时代&#xff0c;云技术、大数据、智慧城市等前沿科技蓬勃发展&#xff0c;企业和城市对 IT 基础设施的依赖程度与日俱增。在这样的大环境下&#xff0c;运维系统的高性能与易扩展性对于保障业务稳定运行和推动发展的关键意义。今天&#xff0c;为大家深入剖析监控…

机器学习stats_linregress

import numpy as np from scipy import stats# r stats.linregress(xs, ys) 是一个用于执行简单线性回归的函数&#xff0c;通常来自 scipy.stats 库。# 具体含义如下&#xff1a;# stats.linregress&#xff1a;执行线性回归分析&#xff0c;拟合一条最佳直线来描述两个变量 …

Linux系统01---指令

目录 学习的方法 Linux 系统介绍 2.1 Unix 操作系统&#xff08;了解&#xff09; 2.2 Linux 操作系统&#xff08;了解&#xff09; 2.3 Linux 操作系统的主要特性&#xff08;重点&#xff09; 2.4 Linux 与 Unix 的区别与联系 2.5 GUN 与 GPL&#xff08;了解&#…

【蓝桥杯14天冲刺课题单】Day 8

1.题目链接&#xff1a;19714 数字诗意 这道题是一道数学题。 先考虑奇数&#xff0c;已知奇数都可以表示为两个相邻的数字之和&#xff0c;2k1k(k1) &#xff0c;那么所有的奇数都不会被计入。 那么就需要考虑偶数什么情况需要被统计。根据打表&#xff0c;其实可以发现除了…

DeepSeek 开源的 3FS 如何?

DeepSeek 3FS&#xff08;Fire-Flyer File System&#xff09;是一款由深度求索&#xff08;DeepSeek&#xff09;于2025年2月28日开源的高性能并行文件系统&#xff0c;专为人工智能训练和推理任务设计。以下从多个维度详细解析其核心特性、技术架构、应用场景及行业影响&…

通过 Docker Swarm 集群探究 Overlay 网络跨主机通信原理

什么是Overlay网络, 用于解决什么问题 ? Overlay网络通过在现有网络之上创建一个虚拟网络层, 解决不同主机的容器之间相互通信的问题 如果没有Overlay网络&#xff0c;实现跨主机的容器通信通常需要以下方法&#xff1a; 端口映射使用宿主机网络模式 这些方法牺牲了容器网络…