详解JS的四种异步解决方案!

news2024/11/14 2:23:50

目录

同步&异步的概念

js中异步的应用场景

实现异步的四种方法

1、 回调函数

2、Promise

3、Generator

4、 async/await


        「异步编程」是前端工程师日常开发中经常会用到的技术,也是校招面试过程中常考的一个知识点。

        通过掌握「异步编程」的四种方式,可以让我们能够更好地处理JavaScript中的异步操作,提高代码的性能和用户体验。

        因此,「今天就想和大家来聊聊JS异步编程的四种方式!」

同步&异步的概念

在讲这四种异步方案之前,我们先来明确一下同步和异步的概念:

        所谓「同步(synchronization)」,简单来说,就是「顺序执行」,指的是同一时间只能做一件事情,只有目前正在执行的事情做完之后,才能做下一件事情。

        「同步操作的优点」在于做任何事情都是依次执行,井然有序,不会存在大家同时抢一个资源的问题。

        「同步操作的缺点」在于「会阻塞后续代码的执行」。如果当前执行的任务需要花费很长的时间,那么后面的程序就只能一直等待。

        所谓「异步(Asynchronization)」,指的是当前代码的执行不影响后面代码的执行。当程序运行到异步的代码时,会将该异步的代码作为任务放进「任务队列」,而不是推入主线程的调用栈。等主线程执行完之后,再去任务队列里执行对应的任务即可。

        因此,「异步操作的优点就是:不会阻塞后续代码的执行。」

js中异步的应用场景

开篇讲了同步和异步的概念,那么在JS中异步的应用场景有哪些呢?

  • 「定时任务」:setTimeout、setInterval

  • 「网络请求」:ajax请求、动态创建img标签的加载

  • 「事件监听器」:addEventListener

实现异步的四种方法

        对于「setTimeout、setInterval、addEventListener」这种异步场景,不需要我们手动实现异步,直接调用即可。

        但是对于「ajax请求」「node.js中操作数据库这种异步」,就需要我们自己来实现了~

1、 回调函数

在微任务队列出现之前,JS实现异步的主要方式就是通过「回调函数」

以一个简易版的Ajax请求为例,代码结构如下所示:

function ajax(obj){
 let default = {
   url: '...',
   type:'GET',
   async:true,
   contentType: 'application/json',
   success:function(){}
    };

 for (let key in obj) {
        defaultParam[key] = obj[key];
    }

    let xhr;
    if (window.XMLHttpRequest) {
        xhr = new XMLHttpRequest();
    } else {
        xhr = new ActiveXObject('Microsoft.XMLHTTP');
    }
    
    xhr.open(defaultParam.type, defaultParam.url+'?'+dataStr, defaultParam.async);
    xhr.send();
    xhr.onreadystatechange = function (){
        if (xhr.readyState === 4){
            if(xhr.status === 200){
                let result = JSON.parse(xhr.responseText);
                // 在此处调用回调函数
                defaultParam.success(result);
            }
        }
    }
}

我们在业务代码里可以这样调用「ajax请求」

ajax({
   url:'#',
   type:GET,
   success:function(e){
    // 回调函数里就是对请求结果的处理
   }
});

        「ajax请求」中的success方法就是一个回调函数,回调函数中执行的是我们请求成功之后要做的进一步操作。

        这样就初步实现了异步,但是回调函数有一个非常严重的缺点,那就是「回调地狱」的问题。

        大家可以试想一下,如果我们在回调函数里再发起一个ajax请求呢?那岂不是要在success函数里继续写一个ajax请求?那如果需要多级嵌套发起ajax请求呢?岂不是需要多级嵌套?

如果嵌套的层级很深的话,我们的代码结构可能就会变成这样:

图片

        因此,为了解决回调地狱的问题,提出了「promise」「async/await」「generator」的概念。

2、Promise

「Promise」作为典型的微任务之一,它的出现可以使JS达到异步执行的效果。

一个「Promise函数」的结构如下列代码如下:

const promise = new Promise((resolve, reject) => {
 resolve('a');
});
promise
    .then((arg) => { console.log(`执行resolve,参数是${arg}`) })
    .catch((arg) => { console.log(`执行reject,参数是${arg}`) })
    .finally(() => { console.log('结束promise') });

        如果我们需要嵌套执行异步代码,相比于回调函数来说,「Promise」的执行方式如下列代码所示:

const promise = new Promise((resolve, reject) => {
 resolve(1);
});
promise.then((value) => {
     console.log(value);
     return value * 2;
    }).then((value) => {
     console.log(value);
     return value * 2;
    }).then((value) => {
    console.log(value);
    }).catch((err) => {
  console.log(err);
    });

即通过then来实现多级嵌套(「链式调用」),这看起来是不是就比回调函数舒服多了~

每个「Promise」都会经历的生命周期是:

  • 进行中(pending) :此时代码执行尚未结束,所以也叫未处理的(unsettled)

    已处理(settled) :异步代码已执行结束 已处理的代码会进入两种状态中的一种:
    • 已拒绝(rejected):遇到错误,异步代码执行失败 ,由reject()触发

    • 已完成(fulfilled):表明异步代码执行成功,由resolve()触发

因此,「pending」「fulfilled」「rejected」就是「Promise」中的三种状态啦~

        需要注意的是,在「Promise」中,要么包含resolve() 来表示 「Promise」 的状态为fulfilled,要么包含 reject() 来表示「Promise」的状态为rejected。

        不然我们的「Promise」就会一直处于pending的状态,直至程序崩溃...

除此之外,「Promise」不仅很好的解决了链式调用的问题,它还有很多高频的操作:

  • ·Promise.all(promises) :接收一个包含多个Promise对象的数组,等待所有都完成时,返回存放它们结果的数组。如果任一被拒绝,则立即抛出错误,其他已完成的结果会被忽略

  • ·Promise.allSettled(promises) : 接收一个包含多个Promise对象的数组,等待所有都已完成或者已拒绝时,返回存放它们结果对象的数组。每个结果对象的结构为{status:'fulfilled' // 或 'rejected', value // 或reason}

  • ·Promise.race(promises) : 接收一个包含多个Promise对象的数组,等待第一个有结果(完成/拒绝)的Promise,并把其result/error作为结果返回

示例代码如下所示:

function getPromises(){
    return [
        new Promise(((resolve, reject) => setTimeout(() => resolve(1), 1000))),
        new Promise(((resolve, reject) => setTimeout(() => reject(new Error('2')), 2000))),
        new Promise(((resolve, reject) => setTimeout(() => resolve(3), 3000))),
    ];
}

Promise.all(getPromises()).then(console.log);
Promise.allSettled(getPromises()).then(console.log);
Promise.race(getPromises()).then(console.log);

打印结果为:

图片

图片

图片

3、Generator

        「generator」是ES6提出的一种异步编程的方案。因为手动创建一个iterator十分麻烦,因此ES6推出了「generator」,用于更方便的创建iterator。

        也就是说,「generator」就是一个返回值为iterator对象的函数。

在讲「generator」之前,我们先来看看iterator是什么:

iterator中文名叫「迭代器」。它为js中各种不同的数据结构(Object、Array、Set、Map)提供统一的访问机制。
任何数据结构只要部署iterator接口,就可以完成遍历操作。
因此iterator也是一种对象,不过相比于普通对象来说,它有着专为迭代而设计的接口。

我们通过一个例子来看看generator的特征:

function* createIterator() {
  yield 1;
  yield 2;
  yield 3;
}
// generators可以像正常函数一样被调用,不同的是会返回一个 iterator
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

形式上,「generator」 函数是一个普通函数,但是有两个特征:

  • ·function关键字与函数名之间有一个星号

  • ·函数体内部使用yield语句,定义不同的内部状态

        在普通函数中,我们想要一个函数最终的执行结果,一般都是return出来,或者以return作为结束函数的标准。运行函数时也不能被打断,期间也不能从外部再传入值到函数体内。

        但在「generator」中,就打破了这几点,所以「generator」和普通的函数完全不同。

        当以function*  的方式声明了一个「generator」生成器时,内部是可以有许多状态的,以yield进行断点间隔。期间我们执行调用这个生成的「generator」,他会返回一个遍历器对象,用这个对象上的方法,实现获得一个yield后面输出的结果。

function* generator() {
    yield 1
    yield 2
};
let iterator = generator();
iterator.next()  // {value: 1, done: false}
iterator.next()  // {value: 2, done: false}
iterator.next()  // {value: undefined, done: true}

4、 async/await

最后我们来讲讲「async/await」,终于讲到这儿了!!!

「async/await」是ES7提出的关于异步的终极解决方案。我看网上关于「async/await」是谁的语法糖这块有两个版本:

  • 第一个版本说「async/await」是Generator的语法糖

  • 第二个版本说「async/await」是Promise的语法糖

其实,这两种说法都没有错。

「关于async/await是Generator的语法糖:」

        所谓generator语法糖,表明的就是「aysnc/await」实现的就是generator实现的功能。但是「async/await」比generator要好用。因为generator执行yield设下的断点采用的方式就是不断的调用iterator方法,这是个手动调用的过程。

        而async配合await得到的就是断点执行后的结果。因此「async/await」比generator使用更普遍。

「关于async/await是Promise的语法糖:」

如果不使用「async/await」的话,Promise就需要通过链式调用来依次执行then之后的代码:

function counter(n){
    return new Promise((resolve, reject) => { 
        resolve(n + 1);
    });
}

function adder(a, b){
    return new Promise((resolve, reject) => { 
        resolve(a + b);
    });
}

function delay(a){
    return new Promise((resolve, reject) => { 
        setTimeout(() => resolve(a), 1000);
    });
}
// 链式调用写法
function callAll(){
    counter(1)
       .then((val) => adder(val, 3))
       .then((val) => delay(val))
       .then(console.log);
}
callAll();//5

虽然相比于回调地狱来说,链式调用确实顺眼多了。但是其呈现仍然略繁琐了一些。

「async/await的出现,就使得我们可以通过同步代码来达到异步的效果」

async function callAll(){
   const count = await counter(1);
   const sum = await adder(count, 3);
   console.log(await delay(sum));
}
callAll();// 5

由此可见,「Promise搭配async/await的使用才是正解!」

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

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

相关文章

redis 和mongodb基础操作练习

目录 redis作业 string、list、hash 数据类型 举例说明list和hash的应用场景,每个至少一个场景 mongodb作业 1. 创建一个数据库 名字grade 2. 数据库中创建一个集合名字 class 3. 集合中插入若干数据 文档格式如下 4. 查找 5. 增加、更新、删除、统计 re…

【电路原理学习笔记】第3章:欧姆定律:3.1 电压、电流与电阻的关系

第3章:欧姆定律 3.1 电压、电流与电阻的关系 欧姆定律指出:电流与电压成正比,与电阻成反比。即 I V R I\frac{V}{R} IRV​ 3.1.1 电压与电流之间的线性关系 数学上,线性指的是变量之间的关系在图形上是一条直线。线性方程所对…

数据结构与算法——希尔排序(引例、希尔增量序列、原始希尔排序、代码、时间复杂度、Hibbard增量序列、Sedgewick增量序列)

目录 引例 希尔增量序列 原始希尔排序 代码(C语言) 时间复杂度 更多增量序列 Hibbard增量序列 Sedgewick增量序列 希尔排序(by Donald Shell) 引例 给以下序列进行排序: 先以5的间隔进行插入排序&#xff1a…

自学数据结构和算法(5)

二叉树的遍历 分为先序、中序、和后序遍历。 这三种遍历都可以由递归序来得到: (1)先序遍历(也是二叉树的深度优先遍历)是第一次到某个结点才打印; (2)中序遍历是第二次到某个结…

【ACM】—蓝桥杯大一暑期集训Day3

🚀欢迎来到本文🚀 🍉个人简介:陈童学哦,目前正在学习C/C、Java、算法等方向,一个正在慢慢前行的普通人。 🏀系列专栏:陈童学的日记 💡其他专栏:CSTL&#xff…

PCL 获取点集中距离最近的两个点(二维)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 给定一个平面上n个点的点集,问题是找出点集中最近的点对。这个问题经常出现在许多应用程序中,例如,在空中交通管制中,监视靠得太近的飞机,因为这可能发生碰撞。这里实现一种方式来获取这两个点,具体计算过程如…

换零钱II:零钱面值动态变化,class方法自动兑换最少零钱(贪心算法)

银行现存零钱面值种类动态变化但数量无限,类方法change()完成指定金额的最少零钱个数兑换。 (本笔记适合学透python基本数据结构,熟悉class的基构造,对类内全局变量有一定认的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1…

C++并发编程(4):共享数据的问题、使用互斥量保护共享数据、死锁

在线程间共享数据 参考博客 线程间共享数据——使用互斥量保护共享数据 [c11]多线程编程(四)——死锁(Dead Lock) c多线程之死锁 C 死锁及解决办法 共享数据的问题 设想你有一段时间和朋友合租公寓,公寓只有一个厨房和一个浴室。除非你们的感情格外深厚&#xff0…

KBQA项目-基于豆瓣电影TOP250数据的知识图谱对话机器人

从今天开始,我们要一起来学习一个新的课程,叫做《基于豆瓣电影TOP250数据的知识图谱对话机器人》,同时,这个课程也是「知识图谱」系列的第一个项目。 项目演示 课程内容 1、爬取数据:使用reqests库,结合b…

贝莱德CEO再谈比特币ETF:客户需求让我们进军加密市场

来源:CNBC 编译:WEEX Exchang 「进军加密货币领域符合这家资产管理巨头更广泛的使命,即为投资者创造易用且低成本的产品。」贝莱德 CEO Larry Fink 周五(7 月 14 日)在 CNBC 「街谈巷议」(Squawk on the St…

Java 设计模式——装饰者模式

目录 1.概述2.结构3.案例实现3.1.抽象组件3.2.具体组件3.3.抽象装饰3.4.具体装饰3.5.测试 4.优缺点5.使用场景6.JDK 源码解析——BufferedWriter7.装饰者模式和静态代理的比较 1.概述 (1)我们先来看一个快餐店的例子:快餐店有炒面、炒饭这些…

PCB板框评估及叠层设计

PCB板框评估及叠层设计 板框评估叠层设计 板框评估 首先选中所有器件,点击下图指令 在PCB中画一个矩形框,所有器件将会排列在这个框中 快捷键 E O S 重新定义原点 选择机械1层,快捷键P L 画出大致的板框,板框画好之后&#x…

全网最详细4W字Flink入门笔记(下)

本文已收录至Github,推荐阅读 👉 Java随想录 文章目录 Flink State状态CheckPoint & SavePointCheckPoint原理SavePoint原理 StateBackend状态后端MemoryStateBackendFsStateBackendRocksDBStateBackend集群级配置StateBackend Window滚动窗口&#…

从小白到大神之路之学习运维第61天--------Ansible自动化运维工具(playbook配置文件深入了解)

第三阶段基础 时 间:2023年7月14日 参加人:全班人员 内 容: playbook配置文件 目录 playbook配置文件 一、playbook配置文件概念 修改hosts文件 建立playbook配置文件 yml脚本写法注释: 二、Playbook的核心元素 三、…

【动手学深度学习】--06.暂退法Dropout

文章目录 暂退法(Dropout)1.原理1.1动机1.2无偏差的加入噪音1.3使用丢弃法1.4推理中的丢弃法 2.从零实现Dropout2.1定义模型参数2.2定义模型2.3训练和测试 3.简洁实现 暂退法(Dropout) 学习视频:丢弃法【动手学深度学习v2】 官方笔记:暂退法&#xff0…

vue+canvas图片裁切

vuecanvas图片裁切 头像的裁切、图片的一些处理…… vue:路由router、vuex状态管理 组件效果 组件代码: 实现思路 盒子即一张画布,把选择的图片放到画布中进行裁切,按照canvas的一些语法裁切一部分内容。 有一个固定大小的画布&am…

字节有点飘了,现在阿里员工跳槽字节不受待见

上一篇:对不起,实在扛不住了。。。 字节现在厉害了,不愧为宇宙第一大厂。 阿里员工现在跳槽字节被鄙视,阿里经历竟然是减分项。 据某互联网大厂HR发文透漏:现在阿里跳字节真的不受待见,背景是负加成。 他举…

JVM内存结构——前言

前提 1. 认识JVM,什么是JVM 简单来说,就是java程序的运行环境(java二进制字节码的运行环境) 1.1 JVM (java虚拟机)的好处 : 一次编写,到处运行的机制 (因为java语言是跨…

【SQL应知应会】表分区(三)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享,与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习,有基础也有进阶,有MySQL也有Oracle 分区表 • MySQL版 前言一、分区表1.非分区表2.分区…

OSS对象存储后端实现+Vue实现图片上传【基于若依管理系统开发】

文章目录 基本介绍术语介绍图片上传方式介绍普通上传用户直传应用服务器签名后直传 OSS对象存储后端实现maven配置文件配置类ServiceController 图片上传前端图片上传组件api页面使用组件组件效果 基本介绍 术语介绍 Bucket(存储空间):用于…