回调地狱 与 Promise(JavaScript)

目录捏

  • 前言
  • 一、异步编程
  • 二、回调函数
  • 三、回调地狱
  • 四、Promise
    • 1. Promise 简介
    • 2. Promise 语法
    • 3. Promise 链式
  • 五、总结


在这里插入图片描述

前言

想要学习Promise,我们首先要了解异步编程回调函数回调地狱三方面知识:

一、异步编程

异步编程技术使你的程序可以在执行一个可能长期运行的任务的同时继续对其他事件做出反应而不必等待任务完成。
与此同时,你的程序也将在任务完成后显示结果。

在这里插入图片描述
举个栗子:

假设现在老板让你修改一个很紧急并且很重要的代码,让你下班前必须改完。并且为了督促进度,老板搬了个椅子坐在一边盯着你敲。

你心里肯定已经犯嘀咕:“你有这么闲吗?就不能去干点其他事情吗?”

老板仿佛接收到了你的心电图一样:“我就在这等着,你改完代码之前我哪也不去。”

这个例子中老板交给你任务后就一直等待什么都不做直到你改完,这个场景就是所谓的同步

第二天,老板又交给了你一项任务。

不过这次就没那么着急啦,这次老板轻描淡写“今天的这个代码不着急,你写完告诉我一声就行。”

这次老板没有盯着你写代码而是转身刷视频去了,你写完后简单的和老板报告了一声“我写完啦!”

这个例子老板交代完任务就去忙其它事情,你完成任务后简单的告诉老板任务完成,这就是所谓的异步

值得注意的是:在异步这种场景下你在改代码的同时老板在刷视频,这两件事在同时进行因此这就是异步比同步高效的本质


异步任务相对应的概念是同步任务,同步任务在主线程上排队执行,只有前一个任务执行完毕,才能执行下一个任务。异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。这里拿定时器作为异步任务举例:

// setTimeout中的内容不会先被输出,而是先输出异步任务之后的内容
    setTimeout(() => {
        console.log('我在定时器里捏!!')
    }, 2000)
    console.log('我在定时器后捏~~')

如果按照代码编写的顺序,应该先输出我在定时器里捏!!,再输出我在定时器后捏~~。但实际输出为:

在这里插入图片描述

这种不阻塞后面任务执行的任务就叫做异步任务

二、回调函数

把一个函数当作参数传递给另一个函数,但是此函数并不会立即执行,而是在将来特定的时机再去调用,这个函数就叫做回调函数。在定时器setTimeout以及Ajax的请求时都会用到回调函数。

在这里插入图片描述

再举个栗子:

你到一个商店去买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。

在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。


回调函数在我们启动一个异步任务的时候就会告诉它:等你完成了这个任务之后要干什么。这样一来主线程几乎不用关心异步任务的状态了,他自己会善始善终。

// setTimeout中第一个参数就是回调函数,只有在1秒后执行
    setTimeout(() => {
        console.log('执行回调函数!!')
    }, 1000);
    console.log('先执行我捏~~')

根据前面对异步任务的介绍,应该知道此代码会先执行定时器后的语句,再执行定时器及回调函数

在这里插入图片描述

三、回调地狱

根据前面对异步编程以及回调函数的介绍我们可以得出一个结论:存在异步任务的代码,不能保证其按照顺序执行,那如果我们非要代码顺序执行呢?

比如我要间隔不同时间输出三句话,语序必须是下面这样的:我在定时器1里捏!!我在定时器2里捏!!我在定时器3里捏!!

    setTimeout(() => {
        console.log('我在定时器1里捏!!')
    }, 3000)
    setTimeout(() => {
        console.log('我在定时器2里捏!!')
    }, 2000)
    setTimeout(() => {
        console.log('我在定时器3里捏!!')
    }, 1000)
    console.log('我在定时器后捏~~')

当使用定时器顺序调用时,则会出现输出顺序错乱的问题:

在这里插入图片描述

所以必须要这样操作,才能保证输出顺序正确:

    setTimeout(() => {
        console.log('我在定时器1里捏!!')
        setTimeout(() => {
            console.log('我在定时器2里捏!!')
            setTimeout(() => {
                console.log('我在定时器3里捏!!')
            }, 1000)
        }, 2000)
    }, 3000)
    console.log('我在定时器后捏~~')

在这里插入图片描述

可以看到,代码中的回调函数层层嵌套,并且嵌套了3层,这种回调函数中嵌套回调函数的情况就叫做回调地狱

在这里插入图片描述

所以回调地狱就是为实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护

那么该如何解决回调地狱问题呢?

四、Promise

1. Promise 简介

Promise,中文翻译过来就是承诺,意思是承诺在未来某一个时间点返回数据给你。它是JS中的一个原生对象,是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案。

首先,Promise 对象有三个状态:pending(进行中),fulfilled(已成功),rejected(已失败)

其次,Promise 构造函数接收一个函数作为参数,该函数是同步的并且会被立即执行,所以我们称之为起始函数,我们需要处理的异步任务就卸载在该函数体内,该函数的两个参数是resolvereject。异步任务执行成功时调用resolve函数并传递成功的结果,反之调用reject并传递失败的原因。

最后,Promise 构造函数返回一个 Promise 对象,该对象具有以下几个方法:

  • then:用于处理 Promise 成功状态的回调函数。
  • catch:用于处理 Promise 失败状态的回调函数(有任何异常都会直接执行)。
  • finally:无论 Promise 是成功还是失败,都会执行的回调函数。

在这里插入图片描述

2. Promise 语法

Promise 本身只是一个容器,真正异步的是它的两个回调resolvereject,分别表示 Promise 成功失败的状态。其本质不是控制异步代码的执行顺序 ,而是控制异步代码结果处理的顺序

那么如何改变 Promise 的状态:

  • resolve(value): 如果当前是 pending 就会变为 fulfilled
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('用户数据读取成功!!')
        }, 1000)
    })

    p.then(value => {
        console.log(value)
    }).catch(reason => {
        console.log(reason)
    })

	console.log(p)

在这里插入图片描述

  • reject(error): 如果当前是 pending 就会变为 rejected
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('用户数据读取失败~~')
        }, 1000)
    })

    p.then(value => {
        console.log(value)
    }).catch(reason => {
        console.log(reason)
    })

	console.log(p)

在这里插入图片描述

  • 抛出异常: 如果当前是 pending 就会变为 rejected
    const p = new Promise((resolve, reject) => {
    	throw new Error('出错啦!!')
    })
    
    console.log(p)

在这里插入图片描述

注意:一旦从进行状态变成为其他状态就永远不能更改状态了。

3. Promise 链式

Promise 链式编程可以保证代码的执行顺序,前提是每一次在than做完处理后,一定要 return 一个 Promise对象,这样才能在下一次then时接收到数据。

在对 Promise 有了一定了解之后,再尝试通过 Promise 链式调用来解决上文介绍回调地狱时所提出的问题,实现以下语句:我在定时器1里捏!!我在定时器2里捏!!我在定时器3里捏!!

    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('我在定时器1里捏!!')
        }, 3000);
    }).then(value => {
        console.log(value)
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('我在定时器2里捏!!')
            }, 2000);
        })
    }).then(value => {
        console.log(value)
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('我在定时器3里捏!!')
            }, 1000);
        })
    }).then(value =>
        console.log(value)
    ).catch(reason =>
        console.log(reason))

在这里插入图片描述

上述代码看上去很凌乱,可读性并不好,所以我们可以将它的核心部分写成一个 promise 函数

    function promise(value, time) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(value)
            }, time)
        })
    }

    promise('我在定时器1里捏!!', 3000)
        .then(data => {
            console.log(data);
            return promise('我在定时器2里捏!!', 2000);
        })
        .then(data => {
            console.log(data);
            return promise('我在定时器3里捏!!', 1000)
        })
        .then(data => {
            console.log(data);
        })
        .catch(data => {
            console.log(data);
        })

在这里插入图片描述

五、总结

Promise 虽然摆脱了回调地狱,但是then的链式调用也会带来额外的阅读负担,并且 Promise 传递中间值非常麻烦。

同时, Promise 的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个then代码块中使用调试器的步进step-over功能,调试器并不会进入后续的then代码块,因为调试器只能跟踪同步代码的每⼀步。

所以ES2017推出了新的语法 async/await 来更好的解决异步问题,下一篇文章会给大家带来async/await的相关介绍,敬请期待~

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

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

相关文章

IIS前端服务和代理

前端服务可以用nginx和IIS开启,windows自带IIS方便管理一点。其实用docker的nginx更方便管理。 记录一下IIS的安装和开启服务过程 1、打开控制面板点击程序,再点击启用或关闭windows功能。 2、 点击左侧启用或关闭Windows功能。 3、把框框中全选上之后点…

54基于matlab的包络谱分析

基于matlab的包络谱分析,目标信号→希尔伯特变换→得到解析信号→求解析信号的模→得到包络信号→傅里叶变换→得到Hilbert包络谱,包络谱分析能够有效地将这种低频冲击信号进行解调提取。程序已调通,可直接运行。 54matlab包络谱分析信号解调…

在家用Python搞副业,也能月入10000+

下班副业实现经济自由的时候,你还在床上躺着,天天摆烂吗?这样的生活真的是你想要的吗? 疫情在家接一些Python相关的小单子,既能给自己练手,还能赚是真香 从零基础开始真的一台电脑和一部手机就可以✅ 一次…

基于Qt QProcess获取linux启动的程序、QScreen 截屏、GIF动画实现

在Linux中,可以使用QProcess类来获取已启动的程序。以下是一个示例代码: #include <QCoreApplication>#include <QProcess>int main(int argc, char *argv[]){QCoreApplication a(argc, argv); // 创建一个QProcess对象 QProcess process; // 设置执行…

如何使用安卓手机数据恢复软件从安卓手机恢复数据

在 Android 上丢失数据并不是世界末日。拿起您的设备&#xff0c;利用最好的 Android 数据恢复软件来恢复手机内存或 SD 卡中的文件、通话记录、消息、联系人、照片、视频等。 在使用 Android 手机的过程中&#xff0c;您会体会到数字存储的便利性&#xff0c;它可以保存大量数…

dbeaver连接别人的数据库没有表

1.概念 非缺省的数据库&#xff1a; 通常是指在一个数据库管理系统&#xff08;DBMS&#xff09;中&#xff0c;除了系统默认创建的数据库之外的其他用户创建或自定义的数据库。许多数据库系统在安装后会创建一个默认数据库&#xff0c;例如MySQL中的mysql数据库&#xff0c;…

Unity 调用自己封装好的DLL库

因为做项目时会用到很多重复的方法&#xff0c;每次都重新写有点浪费时间&#xff0c;就可以将这些方法封装成DLL类库&#xff0c;用的时候直接引入调用就行。 首先在VS里面创建类库文件 注&#xff1a;.NET Framework要选3.5以下 然后定义好命名空间名字和类名就可以写自己要…

NAS 扩容简明指南:使用各种外设给 NAS 们扩容

说起来有趣&#xff0c;NAS 除了“不同设备共享存储”这个功能之外&#xff0c;最重要的功能就是为设备扩容&#xff0c;但是 NAS 自己的存储容量不够了&#xff0c;又该如何。 ​这篇文章分享下我目前使用外设给 NAS 扩容的思路&#xff0c;如何以相对低的成本来获取更大的容…

数据结构预算法--链表(单链表,双向链表)

1.链表 目录 1.链表 1.1链表的概念及结构 1.2 链表的分类 2.单链表的实现(不带哨兵位&#xff09; 2.1接口函数 2.2函数的实现 3.双向链表的实现&#xff08;带哨兵位&#xff09; 3.1接口函数 3.2函数的实现 1.1链表的概念及结构 概念&#xff1a;链表是一种物理存储结…

2023美团外卖商家销量

数据内容字段如下 外卖ID 外卖STR 外卖商家名称 地址 城市 省份 电话 纬度 经度 月销 起送价 评分 经营许可证 食品许可证 资源下载&#xff1a;https://download.csdn.net/download/WANJIAWEN1002/88444367?spm1001.2014.3001.5503

linux espeak语音tts;pyttsx3 ubuntu使用

整体使用espeak声音很机械不太自然 1、linux espeak语音tts 安装&#xff1a; sudo apt install espeak使用&#xff1a; #中文男声 espeak -v zh 你好 #中文女声 espeak -v zhf3 你好 #粤语男声 espeak -v zhy 你好注意&#xff1a;espeak -v zh 你好 &#xff08;Full d…

【LeetCode笔试题】27.移除元素

问题描述 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新…

有趣的 TCP 抢带宽行为

昨天发了一篇 非技术文章&#xff0c;很多人找我讨论&#xff0c;浓缩成一句话&#xff0c;就是 “死道友而不死贫道”&#xff0c;我的简历上写着这些把戏能带来什么&#xff0c;我的 blog 上写着这么做是多么无耻&#xff0c;哈哈。 看看共享链路上如何挤占带宽&#xff1a; …

ElasticSearch7.x - HTTP 操作 - 索引操作

创建索引 对比关系型数据库,创建索引就等同于创建数据库 在 Postman 中,向 ES 服务器发 PUT 请求 :http://192.168.254.101:9200/shopping 说明 {"acknowledged"【响应结果】: true, # true 操作成功"shards_acknowledged"【分片结果】: true, # 分片操…

立体库堆垛机放货动作控制程序功能

放货动作程序功能块 DB11.DBX0.0 为左出货台有货 DB11.DBX1.0 为右出货台有货 左出货台车就位 DB11.DBX0.2 右出货台车就位 DB11.DBX1.2 左出货台车就位 DB11.DBX0.2 右出货台车就位 DB11.DBX1.2 左出货台车就位 DB11.DBX0.2 右出货台车就位 DB11.DBX1.2

使用迁移学习在线校准深度学习模型

使用迁移学习在线校准深度学习模型 本文参考的是2023年发表于Engineering Applications of Artificial Intelligence, EAAI的Deep Gaussian mixture adaptive network for robust soft sensor modeling with a closed-loop calibration mechanism 1. 动机 概念漂移导致历史训…

想学好Python,一定不能错过这些项目!整整70个,附带源码课件

在程序员的求职中&#xff0c;「项目经历」往往是最重要的一环&#xff0c;它能最直观地体现你的编程能力。对于在校生来说&#xff0c;一个好的「项目经历」甚至可以等同于工作经验。可以说&#xff0c;把项目经历写好了&#xff0c;求职就通过了一半。&#xff08;文末有教程…

什么是UV贴图?

UV 是与几何图形的顶点信息相对应的二维纹理坐标。UV 至关重要&#xff0c;因为它们提供了表面网格与图像纹理如何应用于该表面之间的联系。它们基本上是控制纹理上哪些像素对应于 3D 网格上的哪个顶点的标记点。它们在雕刻中也很重要。 为什么UV映射很重要&#xff1f; 默认情…

Lightgraph.js节点图引擎【低代码开发利器】

Lightgraph.js是一个 Javascript 节点图引擎库&#xff0c;可以实现类似虚幻引擎的蓝图编程&#xff0c;包括一个编辑器来构建和测试节点图&#xff0c;支持浏览器和Node.js&#xff0c;可以轻松集成到任何现有的 Web 应用程序中&#xff0c;并且无需编辑器即可运行节点图。 在…

基于SSM的科技公司门户网站

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…
最新文章