前端 | 深入理解Promise
1. 引言
JavaScript 是一种单线程语言,这意味着它一次仅能执行一个任务。为了处理异步操作,JavaScript 提供了回调函数,但是随着项目处理并发任务的增加,回调地狱 (Callback Hell) 使异步代码很难维护。为此,ES6带来了Promise给了一种更清晰的异步操作模型。
2. 对Promise的理解
Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
(1)Promise的实例有三个状态:
- Pending(进行中)
- Resolved(已完成)
- Rejected(已拒绝)
当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。
(2)Promise的实例有两个过程:
- pending -> fulfilled : Resolved(已完成)
- pending -> rejected:Rejected(已拒绝)
注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
2.1 Promise的特点
- 对象的状态不受外界影响。
- promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来——“承诺”;
- 一旦状态改变就不会再变,任何时候都可以得到这个结果。
- promise对象的状态改变,只有两种可能:从 pending变为 fulfilled,从 pending变为 rejected。这时就称为 resolved(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。
2.2 Promise的缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
3. Promise的基本用法
3.1 创建Promise对象
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve和 reject。
const promise = new Promise(function(resolve, reject) {// ... some codeif (/* 异步操作成功 */){resolve(value);} else {reject(error);}
});
使用resolve和reject方法
function testPromise(ready) {return new Promise(function(resolve,reject){if(ready) {resolve("hello world");}else {reject("No thanks");}});
};
// 方法调用
testPromise(true).then(function(msg){console.log(msg);
},function(error){console.log(error);
});
上面的代码的含义是给 testPromise方法传递一个参数,返回一个promise对象,如果为 true的话,那么调用promise对象中的 resolve()方法,并且把其中的参数传递给后面的 then第一个函数内,因此打印出 “hello world”, 如果为 false的话,会调用promise对象中的 reject()方法,则会进入 then的第二个函数内,会打印 No thanks;
3.2 Promise方法
(1)then()
第一个回调函数是Promise对象的状态变为 resolved时调用,第二个回调函数是Promise对象的状态变为 rejected时调用。其中第二个参数可以省略。
promise.then(function(value) {// success
}, function(error) {// failure
});
当要写有顺序的异步事件时,需要串行写:
let promise = new Promise((resolve,reject)=>{ajax('first').success(function(res){resolve(res);})
})
promise.then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)})})
}).then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)})})
}).then(res=>{})
事件没有顺序或者关系时,还如何写呢?可以使用 all 方法来解决。
(2)catch()
该方法相当于 then方法的第二个参数,指向 reject的回调函数。
p.then((data) => {console.log('resolved',data);
},(err) => {console.log('rejected',err);}
);
// 或:
p.then((data) => {console.log('resolved',data);
}).catch((err) => {console.log('rejected',err);
});
(3)all()
all方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个 promise对象。当数组中所有的 promise的状态都达到 resolved的时候,all方法的状态就会变成 resolved,如果有一个状态变成了 rejected,那么 all方法的状态就会变成 rejected。
let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(1);},2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);},1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);},3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{console.log(res);//结果为:[1,2,3]
})
(4)race()
race方法和 all一样,接受的参数是一个每项都是 promise的数组,但是与 all不同的是,当最先执行完的事件执行完之后,就直接返回该 promise对象的值。如果第一个 promise对象状态变成 resolved,那自身的状态变成了 resolved;反之第一个 promise变成 rejected,那自身状态就会变成 rejected。
let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{reject(1);},2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);},1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);},3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{console.log(res);//结果:2
},rej=>{console.log(rej)};
)
race方法有什么实际作用呢?当要做一件事,超过多长时间就不做了,可以用这个方法来解决:
Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
(5)finally()
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
4. 使用async/await代替Promise
4.1 对async/await的理解
async function testAsy(){return 'hello world';
}
let result = testAsy();
console.log(result)
async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,当然应该用原来的方式:then() 链来处理这个 Promise 对象,就像这样:
async function testAsy(){return 'hello world'
}
let result = testAsy()
console.log(result)
result.then(v=>{console.log(v) // hello world
})
那如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。
联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。
注意:Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。
4.2 async/await的优势
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。仍然用 setTimeout 来模拟异步操作:
/*** 传入参数 n,表示这个函数执行的时间(毫秒)* 执行的结果是 n + 200,这个值将用于下一步骤*/
function takeLongTime(n) {return new Promise(resolve => {setTimeout(() => resolve(n + 200), n);});
}
function step1(n) {console.log(`step1 with ${n}`);return takeLongTime(n);
}
function step2(n) {console.log(`step2 with ${n}`);return takeLongTime(n);
}
function step3(n) {console.log(`step3 with ${n}`);return takeLongTime(n);
}
现在用 Promise 方式来实现这三个步骤的处理:
function doIt() {console.time("doIt");const time1 = 300;step1(time1).then(time2 => step2(time2)).then(time3 => step3(time3)).then(result => {console.log(`result is ${result}`);console.timeEnd("doIt");});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
如果用 async/await 来实现呢,会是这样:
async function doIt() {console.time("doIt");const time1 = 300;const time2 = await step1(time1);const time3 = await step2(time2);const result = await step3(time3);console.log(`result is ${result}`);console.timeEnd("doIt");
}
doIt();
4.3 async/await对比Promise的优势
- 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担
- Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅
- 错误处理友好,async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余
- 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。
4.4 async/await如何捕获异常
使用 try…catch…
async function fn(){try{let a = await Promise.reject('error')}catch(error){console.log(error)}
}
5. 补充:Promise及其方法的手写
5.1 手写Promise
- Promise 具有 pending(进行中)、fulfilled(已成功)和 rejected(已失败)三种状态,且状态不可逆。
- new Promise(executor) 立即执行 executor 函数,并传入 resolve 和 reject,以控制 Promise 状态。
class MyPromise{constructor(executor){this.state = 'pending';this.value = undefined; // 失败的值this.error = undefined; // 成功的值this.onFulfilledCallbacks = []; // 存储成功回调this.onRejectedCallbacks = []; // 存储失败回调const resolve = (value)=>{if(this.state==='pending'){this.state = 'fulfilled';this.value = value;this.onFulfilledCallbacks.forEach(fn=>fn(value)); // 执行所有成功回调}};const reject = (error)=>{if(this.state==='pending'){this.state = 'rejected';this.error = error;this.onRejectedCallbacks.forEach(fn=>fn(error));}};try{executor(resolve,reject);}catch(error){reject(error);}}
}
5.2. 手写Promise.then
- 在 Promise 变为 fulfilled 时调用 onFulfilled,并传入结果。
- 在 Promise 变为 rejected 时调用 onRejected,并传入错误。
- then 必须返回一个新的 Promise,支持链式调用。
Promise.prototype.then = function(onFulfilled,onRejected){return new Promise((resolve,reject)=>{if(this.state==='fulfilled'){try {const result = onFulfilled?onFulfilled(this.value):this.value;resolve(result);} catch (error) {reject(error);}}else if(this.state==='rejected'){try {const result = onRejected?onRejected(this.error):this.error;reject(result);} catch (error) {reject(error);}}else{this.onFulfilledCallbacks.push((value)=>{try {const result = onFulfilled ? onFulfilled(value) : value;resolve(result);} catch (error) {reject(error);}})this.onRejectedCallbacks.push((error) => {try {const result = onRejected ? onRejected(error) : error;reject(result);} catch (error) {reject(error);}});}})
}
注意:
- Promise.then是实例方法,实例方法是挂载在prototype上的只能通过实例去调用,不能直接挂载在Promise上
- 而all和race是静态方法,可以直接挂载在Promise上
5.3 手写Promise.all
1)核心思路
- 接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
- 这个方法返回一个新的 promise 对象,
- 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
- 参数所有回调成功才是成功,返回值数组与参数顺序一致
- 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。
2)代码实现
一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来,这就看当前页面的耦合程度了
function promiseAll(promises) {return new Promise(function(resolve, reject) {if(!Array.isArray(promises)){throw new TypeError(`argument must be a array`)}var resolvedCounter = 0;var promiseNum = promises.length;var resolvedResult = [];for (let i = 0; i < promiseNum; i++) {Promise.resolve(promises[i]).then(value=>{resolvedCounter++;resolvedResult[i] = value;if (resolvedCounter == promiseNum) {return resolve(resolvedResult)}},error=>{return reject(error)})}})
}
// test
let p1 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(1)}, 1000)
})
let p2 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(2)}, 2000)
})
let p3 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(3)}, 3000)
})
promiseAll([p3, p1, p2]).then(res => {console.log(res) // [3, 1, 2]
})
5.3 手写Promise.race
该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能改变一次, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可。
Promise.race = function (args) {return new Promise((resolve, reject)=>{for(let i = 0; i < args.length; i++){Promise.resolve(args[i]).then(resolve,reject)}})
}
相关文章:

前端 | 深入理解Promise
1. 引言 JavaScript 是一种单线程语言,这意味着它一次仅能执行一个任务。为了处理异步操作,JavaScript 提供了回调函数,但是随着项目处理并发任务的增加,回调地狱 (Callback Hell) 使异步代码很难维护。为此,ES6带来了…...

Visual Studio Code修改terminal字体
个人博客地址:Visual Studio Code修改terminal字体 | 一张假钞的真实世界 默认打开中断后字体显示如下: 打开设置,搜索配置项terminal.integrated.fontFamily,修改配置为monospace。修改后效果如下:...
自然语言处理-词嵌入 (Word Embeddings)
人工智能例子汇总:AI常见的算法和例子-CSDN博客 词嵌入(Word Embedding)是一种将单词或短语映射到高维向量空间的技术,使其能够以数学方式表示单词之间的关系。词嵌入能够捕捉语义信息,使得相似的词在向量空间中具有…...

自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数
import numpy as np import torch import torch.nn as nn import torch.optim as optim from sklearn.metrics import precision_score, recall_score, f1_score# 数据准备 class1_points np.array([[1.9, 1.2],[1.5, 2.1],[1.9, 0.5],[1.5, 0.9],[0.9, 1.2],[1.1, 1.7],[1.4,…...

【论文笔记】Fast3R:前向并行muti-view重建方法
众所周知,DUSt3R只适合做稀疏视角重建,与sapnn3r的目的类似,这篇文章以并行的方法,扩展了DUSt3R在多视图重建中的能力。 abstract 多视角三维重建仍然是计算机视觉领域的核心挑战,尤其是在需要跨不同视角实现精确且可…...
谈谈你所了解的AR技术吧!
深入探讨 AR 技术的原理与应用 在科技飞速发展的今天,AR(增强现实)技术已经悄然改变了我们与周围世界互动的方式。你是否曾想象过如何能够通过手机屏幕与虚拟物体进行实时互动?在这篇文章中,我们将深入探讨AR技术的原…...

upload labs靶场
upload labs靶场 注意:本人关卡后面似乎相比正常的关卡少了一关,所以每次关卡名字都是1才可以和正常关卡在同一关 一.个人信息 个人名称:张嘉玮 二.解题情况 三.解题过程 题目:up load labs靶场 pass 1前后端 思路及解题:…...
搜索引擎友好:设计快速收录的网站架构
本文来自:百万收录网 原文链接:https://www.baiwanshoulu.com/14.html 为了设计一个搜索引擎友好的网站架构,以实现快速收录,可以从以下几个方面入手: 一、清晰的目录结构与层级 合理划分内容:目录结构应…...

基于 oneM2M 标准的空气质量监测系统的互操作性
论文标题 英文标题: Interoperability of Air Quality Monitoring Systems through the oneM2M Standard 中文标题: 基于 oneM2M 标准的空气质量监测系统的互操作性 作者信息 Jonnar Danielle Diosana, Gabriel Angelo Limlingan, Danielle Bryan Sor…...

春晚舞台上的人形机器人:科技与文化的奇妙融合
文章目录 人形机器人Unitree H1的“硬核”实力传统文化与现代科技的创新融合网友热议与文化共鸣未来展望:科技与文化的更多可能结语 2025 年央视春晚的舞台,无疑是全球华人目光聚焦的焦点。就在这个盛大的舞台上,一场名为《秧BOT》的创意融合…...

零基础学习书生.浦语大模型-入门岛
第一关:Linux基础知识 Cursor连接服务器 使用Remote - SSH插件即可 注:46561:服务器端口号 运行指令 python hello_world.py端口映射 ssh -p 46561 rootssh.intern-ai.org.cn -CNg -L 7860:127.0.0.1:7860 -o StrictHostKeyCheckingno …...
Gurobi基础语法之 addConstr, addConstrs, addQConstr, addMQConstr
在新版本的 Gurobi 中,向 addConstr 这个方法中传入一个 TempConstr 对象,在模型中就会根据这个对象生成一个约束。更重要的是:TempConstr 对象可以传给所有addConstr系列方法,所以下面先介绍 TempConstr 对象 TempConstr TempC…...

数据结构---图的遍历
图的遍历(Travering Graph):从图的某一顶点出发,访遍图中的其余顶点,且每个顶点仅被访问一次,图的遍历算法是各种图的操作的基础。 复杂性:图的任意顶点可能和其余的顶点相邻接,可能在访问了某个顶点后,沿某条路径搜索…...
Qwen 模型自动构建知识图谱,生成病例 + 评价指标优化策略
关于数据库和检索方式的选择 AI Medical Consultant for Visual Question Answering (VQA) 系统:更适合在前端使用向量数据库(如FAISS)结合关系型数据库来实现图像和文本的检索与存储。因为在 VQA 场景中,你需要对患者上传的图像或…...
.Net Web API 访问权限限定
看到一个代码是这样的: c# webapi 上 [Route("api/admin/file-service"), AuthorizeAdmin] AuthorizeAdmin 的定义是这样的 public class AuthorizeAdminAttribute : AuthorizeAttribute {public AuthorizeAdminAttribute(){Roles "admin"…...

项目架构调整,切换版本并发布到中央仓库
文章目录 0.完成运维篇maven发布到中央仓库的部分1.配置server到settings.xml2.配置gpg 1.架构调整1.sunrays-dependencies(统一管理依赖和配置)1.作为单独的模块2.填写发布到中央仓库的配置1.基础属性2.基本配置3.插件配置 3.完整的pom.xml 2.sunrays-f…...
考试知识点位运算
深入理解位运算 在C编程的世界里,位运算作为一种直接对二进制位进行操作的运算方式,虽然不像加减乘除等算术运算那样广为人知,却在许多关键领域发挥着至关重要的作用。从底层系统开发到高效算法设计,位运算都展现出其独特的魅力与…...
matlab快速入门(2)-- 数据处理与可视化
MATLAB的数据处理 1. 数据导入与导出 (1) 从文件读取数据 Excel 文件:data readtable(data.xlsx); % 读取为表格(Table)CSV 文件:data readtable(data.csv); % 自动处理表头和分隔符文本文件:data load(data.t…...

Kafka中文文档
文章来源:https://kafka.cadn.net.cn 什么是事件流式处理? 事件流是人体中枢神经系统的数字等价物。它是 为“永远在线”的世界奠定技术基础,在这个世界里,企业越来越多地使用软件定义 和 automated,而软件的用户更…...
Python-列表
3.1 列表是什么 在Python中,列表是一种非常重要的数据结构,用于存储一系列有序的元素。列表中的每个元素都有一个索引,索引从0开始。列表可以包含任何类型的元素,包括其他列表。 # 创建一个列表my_list [1, 2, 3, four, 5.0]…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...