JavaScript中Promise的简单使用及其原理
Promise是ES6最重要的特性之一,今天来系统且细致的研究一下Promise的用法以及原理。
按照我往常的理解,Promise是一个构造函数,有all、resolve、reject、then、catch等几个方法,一般情况下,在涉及到异步操作时才会用到Promise。
所以我接下来先new一个Promise对象,并在其中进行一些异步操作:
// 使用Promise的时候一般会把它包裹在一个函数中,并在函数的最后返回这个Promise对象
function runPro()(var a = new Promise((resolve, reject) => {setTimeout(() => {console.log('work done!');resolve('success');}, 1000);});return a;
)
runPro()
在上面的代码中,Promise的构造函数接收一个箭头函数作为参数,这个箭头函数又有两个参数,分别是resolve和reject,我在这个箭头函数中使用setTimeout进行了一些异步操作,异步操作中执行了resolve方法,并给resolve方法传了一个字符串‘success’作为参数。
执行这段代码会发现,等待了1秒钟后(因为我在setTimeout中设置的等待时间是1000毫秒),输出了‘work done!’。
这时候并没有发现Promise有什么特别的作用,而且resolve和reject这两个的作用也并没有体现出来。
之前我们说过Promise这个构造函数上有then、catch方法,在上面的代码片段中,runPro函数最后return了一个Promise对象,所以我们可以在runPro函数执行完成之后使用then对Promise对象进行进一步的操作:
runPro().then((res) => {console.log('then:', res);//TODO something
});
输出结果:

在runPro返回的Promise对象上直接调用then方法,then方法接收一个函数作为参数A,并且这个箭头函数也会接收一个参数B,这个参数B的值就是前面代码中resolve方法所传递的字符串‘success’。
执行代码,会在1秒后首先输出‘work done!’,紧接着输出‘then: success’。
这个时候,就可以简单的体现出来Promise的作用了,在前面的代码中,then方法就像是Promise的回调函数,当Promise中的异步操作执行完之后,通过链式调用的方式执行回调函数。
这里的关键点就在于链式调用上,当实际使用中遇见多层回调的情况时,Promise的强大之处才能够体现出来:
function runPro2(){var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');console.log('this is runPro2');}, 1000);});return a;
};function runPro3(){var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');console.log('this is runPro3');}, 1000);});return a;
};runPro().then(() => {return runPro2();
}).then(() => {return runPro3();
}).then(() => {console.log('all done')
})
到这里为止,大概已经明白了,Promise是一个在异步操作过程中,等待其中异步操作完成之后执行其回调函数的一种结构体。但是其中的原理还是模糊不清,其中resolve和reject这两个参数还没有搞清楚,只知道在前面的几个代码片段中都调用了resolve函数,resolve是做什么的并没有体现出来。
关于resolve和reject,我以前的理解是Promise中的异步操作执行成功后调用resolve函数,异步操作执行失败后调用reject函数,后来发现这种理解其实是不准确的。
在理解这两个函数的正确作用之前,我们首先要知道Promise一个重要的特性:状态
Promise的状态:
一个Promise对象的当前状态必须为以下三种状态中的一种:等待(Pending)、完成(Fulfilled)、拒绝(Rejected)。
- Pending:
异步操作完成之前,Promise处于等待状态,这时候的Promise可以迁移至Fulfilled或者Rejected。 - Fulfilled:
异步操作完成之后,Promise可能从Pending状态迁移至Fulfilled状态,Fulfilled状态的Promise必须拥有一个不可变的终值,并且Fulfilled状态的Promise不能迁移为其他状态。 - Rejected:
异步操作完成之后,Promise可能从Pending状态迁移至Rejected状态,Rejected状态的Promise必须拥有一个不可变的拒绝原因,并且Rejected状态的Promise不能迁移为其他状态。
了解了Promise的三种状态之后,我们再来说说resolve和reject这两个函数的作用:
- resolve函数将Promise设置为Fulfilled状态,reject函数将Promise设置为Rejected状态。
- 设置为Fulfilled或者rejected状态后,即表示Promise中的异步操作执行完成,这时程序就会执行then回调函数。
- resolve和reject函数传递的参数,将由then函数中的箭头函数接收。
实际上,理解Promise的关键点就在于这个状态,通过维护状态、传递状态的方法来进行及时的回调。
所以,如下面代码所示,当使用Promise进行异步操作的时候,其中有几个关键点需要特别注意:
- 在一个函数中new了一个Promise对象之后,函数的最后必须把这个Promise对象return出来,否则这个函数就无法使用then函数进行回调;
- 异步操作中必须执行resolve或者reject函数,否则这个Promise一直处于Pending状态,代码就不会执行它的回调函数。
function runPro(){var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('resolve');console.log('this is runPro');}, 1000);});return a;
}runPro().then((res) => {console.log(res);
})
同样的道理,当异步操作执行失败时,代码通过执行reject函数的方式,将Promise的状态设置为Rejected,并返回一个拒绝原因:
function runPro(item){var a = new Promise((resolve, reject) => {setTimeout(() => {if(item >= 18) {console.log('item 大于 18');resolve('一切正常!');}else {console.log('item 小于 18');reject('18+电影不允许放映!');}}, 1000);});return a;
}runPro(13)
.then((res) => {console.log('resolve:',res);
},(rej) => {console.log('reject:', rej);
})
我们给runPro函数传递不同的参数,runPro接受参数后进行一个异步的判断,如果这个参数的值小于18,执行reject函数,反之则执行resolve函数,异步操作完成之后,执行then回调函数,这里的回调函数可以接收两个箭头函数作为参数,分别对应了resolve函数的回调和reject函数的回调,这两个箭头函数可以分别拿到resolve和reject传递的参数。
如下面截图所示,分别给runPro传递两个不同值后,得到了两种不同的结果:

catch函数的用法
在Promise中,catch函数可以替代reject函数使用,用来指定接收reject的回调:
function runPro(item){var a = new Promise((resolve, reject) => {setTimeout(() => {if(item >= 18) {console.log('item 大于 18');resolve('一切正常!');}else {console.log('item 小于 18');reject('18+电影不允许放映!');}}, 1000);});return a;
}runPro(13)
.then((res) => {console.log('resolve:',res);
})
.catch((rej) => {console.log('catch:', rej);
})
如上面代码所示,对调函数then只有一个箭头函数作为参数,这种情况下,这个箭头函数就被指定用来接收resolve函数的回调,而reject函数的回调则被catch函数来接收:

这个地方使用catch函数来接收reject的回调有一个优点,当前面的then回调函数中出现位置错误时,catch函数可以对错误信息进行处理,而不会导致代码报错。这个原理和常用的try/catch语句相同。
function runPro(item){var a = new Promise((resolve, reject) => {setTimeout(() => {if(item >= 18) {console.log('item 大于 18');resolve('一切正常!');}else {console.log('item 小于 18');reject('18+电影不允许放映!');}}, 1000);});return a;
}
runPro(19)
.then((res) => {console.log(adc); // 这里的adc是一个未定义的变量,当代码执行到这里时,会抛出Error信息导致代码卡死console.log('resolve:',res);
}, (rej) => {console.log('reject:',rej);
});
runPro(19)
.then((res) => {console.log(abc); // 这里的abc是一个未定义的变量,但是由于后边使用.catch函数进行了异常捕获,所以程序不会报错。而且错误原因也会作为参数传递到后面.catch函数的参数中console.log('resolve:',res);
})
.catch((rej) => {console.log('catch:', rej);
})

all函数 / race函数并行异步操作
Promise的all函数和race函数都提供了并行异步操作的能力,二者的区别在于,当这些并行的异步操作耗时不同时,all函数是在所有的异步操作都执行完之后才会执行,而race函数则会在第一个异步操作完成之后立即执行。
function runPro1(){var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');console.log('this is runPro1');}, 1000);});return a;
}
function runPro2(){var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');console.log('this is runPro2');}, 2000);});return a;
};function runPro3(){var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');console.log('this is runPro3');}, 3000);});return a;
};Promise.all([runPro1(), runPro2(), runPro3()])
.then((res) => {console.log('all:', res);
})

function runPro1(){var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');console.log('this is runPro1');}, 1000);});return a;
}
function runPro2(){var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');console.log('this is runPro2');}, 2000);});return a;
};function runPro3(){var a = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');console.log('this is runPro3');}, 3000);});return a;
};Promise.races([runPro1(), runPro2(), runPro3()])
.then((res) => {console.log('all:', res);
})

如上两个代码片段所示,all函数和race都接收一个数组作为参数,这个数组中的值就是我们要进行并行执行的异步操作。这里我们同样使用then函数作为异步操作完成的回调函数。
同时我们通过console输出发现,在race函数的回调函数开始执行的时候,另外两个没有执行完成的异步操作并没有停止,依旧在执行。
相关文章:
JavaScript中Promise的简单使用及其原理
Promise是ES6最重要的特性之一,今天来系统且细致的研究一下Promise的用法以及原理。 按照我往常的理解,Promise是一个构造函数,有all、resolve、reject、then、catch等几个方法,一般情况下,在涉及到异步操作时才会用到…...
SpringBoot RabbitMQ 延时队列取消订单【SpringBoot系列14】
SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。 程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发 1 项目准备 SpringBoot 雪花算法生成商品订单…...
【论文阅读 WWW‘23】Zero-shot Clarifying Question Generation for Conversational Search
文章目录前言MotivationContributionsMethodFacet-constrained Question GenerationMultiform Question Prompting and RankingExperimentsDatasetResultAuto-metric evaluationHuman evaluationKnowledge前言 最近对一些之前的文章进行了重读,因此整理了之前的笔记…...
ouc 网络安全实验 格式化字符串漏洞
文章目录要求lab1lab2lab3lab4结语因为当时自己做实验的时候出现了很多疑问不会解决,在网上看到了一位大佬 王森ouc 的专栏文章解决了很多问题,也学到了很多知识和解决问题的方法,现在把我的实验解决方法也发上来,希望有不会的同…...
PMSM矢量控制笔记(1.1)——电机的机械结构与运行原理
前言:重新整理以前的知识和文章发现,仍然有许多地方没有学得明白,懵懵懂懂含含糊糊的地方多如牛毛,尤其是到了真正实际写东西或者做项目时,如果不是系统的学习了知识,很容易遇到问题就卡壳,也想…...
2022年全国职业院校技能大赛(中职组)网络安全竞赛试题——中间人攻击渗透测试解析(详细)
B-4任务四:中间人攻击渗透测试 *任务说明:仅能获取Server4的IP地址 *任务说明:仅能获取Server11的IP地址 1.通过上题渗透后得到控制权限的服务器场景Server4进行查看本地的arp缓存表的操作,并将该操作所使用的命令作为Flag值提交; 2.通过上题渗透后得到控制权限的服务…...
MySQL必知必会 | 安全、维护、性能
全球化和本地化 关于MySQL处理不同字符集和语言 字符集和校对顺序 数据库被用来存储和检索数据,不同的语言和字符集需要以不同的方式存储和检索,因此,MySQL需要适应不同的字符集,适应不同的排序方式 一些术语: 字符…...
MaaS Model as a Service 模型即服务
大模型是人工智能的发展趋势和未来。大模型是“大算力强算法” 结合的产物。目前,大模型生态已初具规模。大模型能够实现 AI 从“手工作坊”到“工厂模式”的转变,大模型通常是在大规模无标注 数据上进行训练,学习出一种特征和规则…...
【编程基础】027.C语言中函数在解题中的应用(三)
文章目录C语言中函数的应用1、自定义函数实现二维数组的转置2、自定义函数之整数处理3、自定义函数之数字后移4、自定义函数之字符串拷贝C语言中函数的应用 1、自定义函数实现二维数组的转置 题目描述 写一个函数,使给定的一个二维数组(3&a…...
echart图表之highcharts
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、HighCharts是什么?二、使用步骤1.引入库2.前端代码3.展现结果4.后台自动截图总结前言 提示:这里可以添加本文要记录的大概内容&…...
关于.Net和Java的看法——我见过最牛的一个小实习生经历
1、背景 笔者(小方同学在学习)是一个专科院校的一名普通学生,目前就职于某三线城市的WEB方面.Net开发实习生,在找实习期间和就业期间的一些看法,发表此文,纯个人想法,欢迎讨论,指正…...
基于springboot+vue的“智慧食堂”程序设计实现【毕业论文,源码】
系统登录界面系统架构开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7数据库工具:Navicat开发软件:eclipse/myeclipse/ideaMaven包:Maven浏览器…...
学计算机选择什么编程语言好一些?
工资水平的话,目前人工智能、大数据和云计算等领域的工资相对较高,但是要求也高,学历,学习能力什么的。然后是后端开发,Python、Java、C等编程语言的工资普遍较高。 不用开发语言的优势 Java:Java是一种…...
持续集成 在 Linux 上搭建 Jenkins,自动构建接口测试
本篇把从 0 开始搭建 Jenkins 的过程分享给大家,希望对小伙伴们有所帮助。 文章目录 在 Linux 上安装 Jenkins在 Linux 上安装 Git在 Linux 上安装 Python在 Linux 上安装 Allure配置 Jenkinsjenkins 赋能 - 使用邮箱发送测试报告jenkins 赋能 - 优化测试报告内容…...
MySQL学习笔记(总结)
1. 数据库服务器操作命令 启动数据库:net start mysql80 (注释:windows命令) 停止数据库:net stop mysql80 (注释:windows命令) 重启数据库:systemctl restart mysql;…...
Android开发 Layout布局 ScrollView
1.LinearLayout 属性 orientation:内部组件排列方式,可选vertical、horizontal,默认horizontal layout_weight: 与平级组件长宽比例,需要将layout_width、layout_height其中一个设置为0dp,表明长或宽与平级组件的长…...
手撕数据结构与算法——树(三指针描述一棵树)
🏆作者主页:king&南星 🎄专栏链接:数据结构 🏅文章目录🌱树一、🌲概念与定义二、🌳定义与预备三、🌴创建结点函数四、🍀查找五、🍁插入六、&a…...
字节跳动Java后端开发实习面经
最近在和同学一起找实习,投了b站、字节和miHoYo的后端开发。b站二月底就投了,但现在也还没回复;miHoYo也还没回复,估计是只面向24届了;感谢字节,给了我面试的机会。字节真的处理好快,不到一周官…...
STM32实战项目-触摸按键
前言: 通过触摸按键控制LED灯以及继电器,具体实现功能如下: 1、触摸按键1单击与长按,控制LED1; 2、触摸按键2单击与长按,控制LED2; 3、触摸按键3单击与长按,控制LED3; 4、触摸按键4单击与长…...
安全行业-术语(万字)
肉鸡 所谓“肉鸡”说一种很形象的比喻,比喻那些可以任意被我们控制的电脑,对方可以是Windows系统,也可以说UNIX/linux系统,可以说普通的个人电脑,也可以是大型的服务器,我们可以像操作自己的电脑那样来操控…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
