当前位置: 首页 > news >正文

浅谈对Promise的理解以及在工作中的应用

浅谈对Promise的理解以及在工作中的应用

  • Promise的概念
    • 背景知识
    • JavaScript的同步和异步
    • JavaScript事件循环
    • 回调函数进行异步操作
    • 解决方案:Promise
  • Promise 在工作中的运用
    • 创建Promise
    • Promise封装AJAX
    • Promise链式操作
    • Promise.all()
    • Promise.race()
    • async和await
  • 总结

Promise的概念

在开始讲解Promise前,我们先大致了解一下js的运行机制以及多个任务是怎么运作的。

背景知识

众所周知,JavaScript是一门单线程语言,也就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着,这样会造成浏览器处于假死状态,严重影响用户体验。为了解决这个问题,js引入了异步的概念,在执行任务时挂起处于等待中的任务,先运行排在后面的任务。等到刚才挂起的任务返回了结果,再回过头,把挂起的任务继续执行下去。于是,js将所有的任务分成了两种,一种是同步任务synchronous),另一种是异步任务asynchronous)。

JavaScript的同步和异步

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行;
除了广义的同步任务和异步任务,对任务更精细的定义:

宏任务(macro-task):包括整体代码script,setTimeout,setInterval
微任务(micro-task):Promise,process.nextTick

宏任务中有微任务,一定要将宏任务中的微任务执行完毕,再去执行下一个宏任务
那究竟什么是任务队列呢,同步任务和异步任务又是怎么执行的,这就要引入js的运行机制:事件循环(event loop)

JavaScript事件循环

在这里插入图片描述
js事件循环运作机制如下:

  1. 同步任务和异步任务分别进入不同的“场所”,同步任务进入主线程,异步任务进入Event Table (事件表)并注册函数
  2. 指定的事件完成后,Event Table (事件表)会将这个函数移入到Event Queue (事件队列)
  3. 当主线程的任务完成后,会检查Event Queue (事件队列),如果有任务就全部执行,如果没有就进入下一个宏任务
  4. 这个过程会不断的重复,这就叫事件循环

回调函数进行异步操作

和同步操作不同,异步操作是不会立即返回结果的(如发起网络请求,下载文件,操作数据库等)。如果我们后续的函数需要之前返回的结果,又怎样使之前的异步操作在其完成时通知到后续函数来执行呢?

通常,我们可以将这个函数先定义,存储在内存中,将其当做参数传入之前的异步操作函数中,等异步操作结束,就会调用执行这个函数,这个函数就叫做回调函数(callback)

// 下载
function download(callback){// 模拟异步操作setTimeout(function(){// 调用回调函数callback('下载完成');}, 1000);
}function callback(value){// 下载完成的处理console.log(value);
}download(callback);// 这段代码将在1秒后在控制台打印“下载完成”

但假如callback函数同样是个异步函数,且callback里又嵌入了callback呢? 例如需求是等待第一个文件下载完成后,再下载第二个文件,等待第二个文件下载完成后,再下载第三个文件…,这样的话,上面这种方法就不可取了,因为会产生很多的函数嵌套,嵌套太深容易引发回调地狱(指的是回调函数里嵌套回调函数,使得代码可读性非常差,容易陷于无止尽的循环)

     //回调地狱setTimeout(function () {  //第一层console.log('张三');//等3秒打印张三在执行下一个回调函数setTimeout(function () {  //第二层console.log('李四');//等2秒打印李四在执行下一个回调函数setTimeout(function () {   //第三层console.log('王五');//等一秒打印王五}, 1000)}, 2000)}, 3000)

解决方案:Promise

所以为了回调地狱的问题,promise方案应运而生,它是对回调方法的一种封装,是用来处理异步操作的,可以让我们写异步调用的时候写起来更加优雅,更加美观。
以下是promise的一些特点

  1. Promise 对象代表一个异步操作,对象的状态不受外界影响,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)、settled (结束)只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,且一旦状态改变就不可再变
  2. Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据,其中then方法的两个参数是resolve(成功回调),reject(失败回调)。异步任务执行成功时调用resolve函数返回结果,反之调用reject,根据不同的任务,由开发者来决定resolve和reject在函数体内的位置
  3. then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then/catch方法,链式调用可以保证代码的执行顺序
    promise解决刚才的地狱回调问题:
// promise解决方式
function fn(str) {var promise = new Promise(function (resolve, reject) { //resolve是成功的方法  reject是失败的方法  //处理异步任务var flag = true;setTimeout(function () {if (flag) {resolve(str)}else {reject('失败')}})})return promise;
}fn('张三').then((res) => { //then是成功执行的方法 返回的还是一个promise对象console.log(res);//打印张三  res是结果return fn('李四');}).then((res) => {console.log(res);return fn('王五')}).then((res) => {console.log(res);}).catch((res) => { //catch是失败执行的方法console.log(res);})

Promise 在工作中的运用

创建Promise

promise构造器只接收一个参数,该参数被称为执行器(executor)的函数。该函数会被传递两个参数(方法),一个叫做resolve,另一个叫做reject。

resolve函数在成功时调用,reject函数在失败时被调用。并且resolve和reject只能被使用一次,如果之后还有resolve和reject也不会被执行了,有点儿类似于return,但是不同点在于,其他代码还会被照常执行。

new Promise((resolve, reject)=> {resolve('我是第一次调用resolve');console.log('我是其他代码');resolve('我是第二次调用resolve'); // 不在起作用reject('我来调用reject'); // 不在起作用
})

也可以直接使用Promise.resolve或者Promise.reject来创建成功或者失败的Promise

let p1 = Promise.resolve('我是成功的Promise'),p2 = Promise.reject('我是失败的Promise');

Promise封装AJAX

var ajax = function(url) {return new Promise(function(resolve, reject) {var xhr = new XMLHttpRequest();xhr.open('get', url, true);xhr.send();xhr.onreadystatechange = function() {if (xhr.readystate == 4 && xhr.status == 200) {resolve(xhr.responseText)} else if (xhr.readystate == 4 && xhr.status !== 200) {reject(xhr.statusText)}}})
}
ajax.then(console.log(xhr.responseText)); //打印出返回的数据

Promise链式操作

由于Promise的then 方法始终返回一个 Promise 对象, 所以Promise 可以一直调用 then 方法,从而实现链式调用(解决地狱回调)。不管 new Promise 创建出来的执行状态是成功 / 失败,只要在 then / catch方法中通过 return 返回一个结果,不管这个值是 Promise 对象还是普通值,都可以通过链式调用的 .then 方法中获取到这个值,因为 promise.then 方法会默认在返回值的外层包裹一层 Promise 对象,这样才可以实现 Promise 一直通过 .then 的方式去链式调用

let p1 = new Promise((resolve, reject)=> {let name = '张三'setTimeout(()=> {resolve(name);}, 2000)
})let p2 = new Promise((resolve, reject)=> {let name = '李四'setTimeout(()=> {resolve(name);}, 1000)
})let p3 = new Promise((resolve, reject)=> {let name = '王五'setTimeout(()=> {resolve(name);}, 3000)
})//方式一:链式操作返回promise对象
p1.then((res) => {console.log(res+'第一个出场');return p2
}).then((res) => {console.log(res+'第二个出场');return p3
}).then((res) => {console.log(res+'第三个出场');
})//链式操作返回promise对象输出结果
张三第一个出场
李四第二个出场
王五第三个出场//方式二:链式操作返回普通值
p1.then((res) => {console.log('我是'+res);return res
}).then((res) => {console.log('我是'+res+'的儿子');return res
}).then((res) => {console.log('我是'+res+'的孙子');
})//链式操作返回普通值输出结果
张三第一个出场
李四第二个出场
王五第三个出场

Promise.all()

这个方法返回一个新的promise对象,该promise对象在参数对象promises里所有的promise对象都成功的时候才会触发成功,一旦有任何一个promises里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含promises里所有promise返回值的数组作为成功回调的返回值,顺序跟promises的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。
简而言之,all方法会将传入的异步操作并行执行,等到它们都执行完后才会进到then方法,从时间上来看取决于最后一个异步任务执行完成的时间

let p1 = new Promise((resolve, reject)=> {let name = '张三'setTimeout(()=> {resolve(name);}, 2000)
})let p2 = new Promise((resolve, reject)=> {let name = '李四'setTimeout(()=> {resolve(name);}, 1000)
})let p3 = new Promise((resolve, reject)=> {let name = '王五'setTimeout(()=> {resolve(name);}, 3000)
})// promise.all用法
Promise.all([p1,p2,p3]).then((res) => {console.log(res+'都已到达终点');
})// promise.all输出结果
张三,李四,王五都已到达终点

Promise.race()

race就是赛跑的意思,谁先出结果就由谁决定,采用第一个 promise 的值作为它的值,当promises参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

let p1 = new Promise((resolve, reject)=> {let name = '张三'setTimeout(()=> {resolve(name);}, 2000)
})let p2 = new Promise((resolve, reject)=> {let name = '李四'setTimeout(()=> {resolve(name);}, 1000)
})let p3 = new Promise((resolve, reject)=> {let name = '王五'setTimeout(()=> {resolve(name);}, 3000)
})// promise.race用法
Promise.race([p1,p2,p3]).then((res) => {console.log(res+'第一个到达终点');
})// promise.all输出结果
李四第一个到达终点

async和await

async 是“异步”的简写,而 await 可以认为是 async wait(等待) 的简写。
所以应该很好理解 async 用于申明一个 function 是异步的,返回的是一个 Promise 对象.
而 await 用于等待一个异步方法执行完成,后面必须跟一个Promise对象,但是不必写then(),直接就可以得到返回值


//基本用法的async函数
let asyncFun = async function(){return 1
}
console.log(asyncFun())
//会返回一个promise对象//使用场景
//摇色子方法
function dice(){return new Promise((resolve,reject)=>{let sino = parseInt(Math.random()*6+1)  //生成一个1~6之间的随机小数setTimeout(()=>{resolve(sino)},2000)})
}
//异步方法async function text(){let n= await dice()//await 关键字后面调用摇色子方法执行完毕之后,才进行变量赋值console.log("摇出来"+n)  //最后打印出摇出来的数}
text()//输出结果
Promise { 1 }
摇出来5

总结

以上就是我个人关于Promise的理解以及在工作中的应用,总的来说Promise在日常开发工作中的使用还是比较多的,他最大的用途在于让多个异步的任务按照我们想要的方式去执行。但想要彻底理解promise的运行方式及原理,还需要了解js引擎的运行逻辑,任务队列、宏任务微任务等。上文中如果有错误的地方欢迎指正,大家共同进步,越秃越强!

相关文章:

浅谈对Promise的理解以及在工作中的应用

浅谈对Promise的理解以及在工作中的应用Promise的概念背景知识JavaScript的同步和异步JavaScript事件循环回调函数进行异步操作解决方案:PromisePromise 在工作中的运用创建PromisePromise封装AJAXPromise链式操作Promise.all()Promise.race()async和await总结Promi…...

开源|快速入门和理解并模拟实现GPS户外机器人的定位与导航

户外机器人的定位导航相对于需要建图的场景来说,是比较简单容易实现的,因为可以借助第三方地图完成定位,并在第三方地图中完成路径规划和下发航点等操作,实现的难题在于如何控制机器人完成步行和转弯。 这些在不引进RTK高精度定位…...

Java多线程系列--synchronized的原理

原文网址:Java多线程系列--synchronized的原理_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java的synchronized的原理。 反编译出字节码 Test.java public class Test {private static Object LOCK new Object();public static int main(String[] args) {synchro…...

QEMU启动ARM64 Linux内核

目录前言前置知识virt开发板ARM处理器家族简介安装qemu-system-aarch64安装交叉编译工具交叉编译ARM64 Linux内核交叉编译ARM64 Busybox使用busybox制作initramfs使用QEMU启动ARM64 Linux内核前言 本文介绍采用 qemu 模拟ARM-64bit开发板(针对ARM-32bit的有另一篇文…...

Linux->进程程序替换

目录 前言: 1 程序替换原理 2 单进程替换 3 替换函数 3.1 函数使用 4 程序去替换自己的另一个程序操作方式 5 实现自己的shell 前言: 通过我们之前对于子进程的应用,我相信大家一定是能够想到创建子进程的目的之一就是为了代劳父进程执…...

最强分布式锁工具:Redisson

1 Redisson概述1.1 什么是Redisson?Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, Sorted…...

Java9-17新特性

Java9-17新特性 一、接口的私有方法 Java8版本接口增加了两类成员: 公共的默认方法公共的静态方法 Java9版本接口又新增了一类成员: 私有的方法 为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范时需要公开…...

电脑开机找不到启动设备怎么办?

电脑正常开机,却提示“找不到启动设备”,这时我们该怎么办呢?本文就为大家介绍几种针对该问题的解决方法,一起来看看吧!“找不到启动设备”是什么意思?可引导设备(又称启动设备)是一…...

使用langchain打造自己的大型语言模型(LLMs)

我们知道Openai的聊天机器人可以回答用户提出的绝大多数问题,它几乎无所不知,无所不能,但是由于有机器人所学习到的是截止到2021年9月以前的知识,所以当用户询问机器人关于2021年9月以后发送的事情时,它无法给出正确的答案&#x…...

assert()宏函数

assert()宏函数 assert是宏&#xff0c;而不是函数。在C的assert.h文件中 #include <assert.h> void assert( int expression );assert的作用是先计算表达式expression&#xff0c; 如果其值为假&#xff08;即为0&#xff09;&#xff0c;那么它会打印出来assert的内容…...

Docker圣经:大白话说Docker底层原理,6W字实现Docker自由

说在前面&#xff1a; 现在拿到offer超级难&#xff0c;甚至连面试电话&#xff0c;一个都搞不到。 尼恩的技术社群&#xff08;50&#xff09;中&#xff0c;很多小伙伴凭借 “左手云原生右手大数据”的绝活&#xff0c;拿到了offer&#xff0c;并且是非常优质的offer&#…...

Redis+Caffeine多级(二级)缓存,让访问速度纵享丝滑

目录多级缓存的引入多级缓存的优势CaffeineRedis实现多级缓存V1.0版本V2.0版本V3.0版本多级缓存的引入 在高性能的服务架构设计中&#xff0c;缓存是一个不可或缺的环节。在实际的项目中&#xff0c;我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中&#xff0…...

C#和.net框架之第一弹

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录C# 简介一、微软平台的编程二、使用VS创建第一个c#程序1、第一步2、第二步3、第三步4、第四步5、第五步C# 简介 C# 是一个现代的、通用的、面向对象的编程语言&…...

C++---背包模型---潜水员(每日一道算法2023.3.12)

注意事项&#xff1a; 本题是"动态规划—01背包"和"背包模型—二维费用的背包问题"的扩展题&#xff0c;优化思路不多赘述&#xff0c;dp思路会稍有不同&#xff0c;下面详细讲解。 题目&#xff1a; 潜水员为了潜水要使用特殊的装备。 他有一个带2种气体…...

C++类的成员变量和成员函数详解

类可以看做是一种数据类型,它类似于普通的数据类型,但是又有别于普通的数据类型。类这种数据类型是一个包含成员变量和成员函数的集合。 类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存。但是,在定义类的时候不能对成员变量赋值,因为类只是一种数据类…...

(枚举)(模拟)(位运算)116. 飞行员兄弟

目录 题目链接 一些话 切入点 流程 套路 ac代码 题目链接 116. 飞行员兄弟 - AcWing题库 我草&#xff0c;又~在&#xff5e;水&#xff5e;字&#xff5e;数&#xff5e;啦&#xff01;我草&#xff0c;又~在&#xff5e;水&#xff5e;字&#xff5e;数&#xff5e;啦…...

详解Array.prototype.shift.call(arguments)

经常看到如下代码&#xff1a; function foo() {let k Array.prototype.shift.call(arguments);console.log(k) } foo(11,22) //11 Array.prototype.shift.call(arguments)的作用是&#xff1a; 取 arguments 中的第一个参数 一、为啥要这么写&#xff0c;不直接使用argume…...

Tina_Linux_Wi-Fi_开发指南

Tina Linux Wi-Fi 开发指南 1 前言 1.1 文档简介 介绍Allwinner 平台上Wi-Fi 驱动移植&#xff0c;介绍Tina Wi-Fi 管理框架&#xff0c;包括Station&#xff0c;Ap 以及Wi-Fi 常见问题。 1.2 目标读者 适用Tina 平台的广大客户和对Tina Wi-Fi 感兴趣的同事。 1.3 适用范…...

Spring AOP(AOP概念、组成、Spring AOP实现及实现原理)

文章目录1. Spring AOP 是什么2. 为什么要用 AOP3. 怎么学 Spring AOP4. AOP 组成5. Spring AOP 实现5.1 添加 Spring AOP 框架支持5.2 定义切面和切点5.3 实现通知方法5.4 使⽤ AOP 统计 UserController 每个⽅法的执⾏时间 StopWatch5.4 切点表达式说明 AspectJ6. Spring AOP…...

8.条件渲染指令

目录 1 v-if v-show 2 v-if v-else-if v-else 1 v-if v-show v-if与v-show都可以控制DOM的显示与隐藏 由于flag是布尔值&#xff0c;所以这里可以直接写 v-if"flag" 当flag为true的时候&#xff0c;v-if与v-show控制的div都会被显示出来 当flag为false的时候&a…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具&#xff0c;支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议&#xff0c;涵盖接口测试、性能测试、数字体验监测等测试类型…...