[JS] promise知识点与应用场景

Promise是JS中用于处理异步操作的方法,- 支持链式调用从而解决了地狱回调问题。

Promise的基础用法

状态

promise有三种状态:

  • Pending(待定):初始状态,既不是成功也不是失败。
  • Fulfilled(已成功):操作成功完成。
  • Rejected(已失败):操作失败。
const promise = new Promise((resolve, reject) => {// 异步操作if (成功) {resolve(value);} else {reject(error);}
});

实例方法

Promise有三个实例方法,分别是thencatch,和finally

  • then用于处理Promise成功的情况:
promise.then((value) => {console.log(value);
});
  • catch用于处理Promise失败的情况,即异常捕获:
promise.catch((error) => {console.error(error);
});
  • finally:无论Promise最终状态如何(成功或失败),都会执行finally中的回调。
promise.finally(() => {console.log('操作完成');
});

链式调用

then方法可以返回一个Promise,并在后续链式地继续调用then方法。

doSomething().then((result) => {return doSomethingElse(result);}).then((newResult) => {return doThirdThing(newResult);}).then((finalResult) => {console.log(`Final result: ${finalResult}`);}).catch((error) => {console.error(error);});

链式调用只需要在尾部调用一次catch,在链式调用的过程中发生的异常都会被这个尾部的catch捕获。

静态方法

  • Promise.resolve(value):返回一个成功的Promise,值为value;常见于后面跟上then方法将一个函数推入微任务队列;
  • Promise.reject(reason):返回一个失败的Promise,原因为reason
  • Promise.all(iterable):并行执行多个Promise,所有Promise都成功时返回一个包含所有结果的新Promise,如果有任何一个失败,则返回失败的Promise。
Promise.all([promise1, promise2, promise3]).then((values) => console.log(values)).catch((error) => console.error(error));
  • Promise.race(iterable):返回第一个完成的Promise,无论成功还是失败。
Promise.race([promise1, promise2, promise3]).then((value) => console.log(value)).catch((error) => console.error(error));

Promise.all的应用场景

并发请求,有时候在一个页面中需要使用多个GET请求获取页面数据并渲染,并且这些GET请求没有依赖关系,即不需要考虑请求顺序。那么这时就可以使用Promise.all并发执行这些GET请求。

const fetchUser = fetch('https://api.example.com/user');
const fetchPosts = fetch('https://api.example.com/posts');
const fetchComments = fetch('https://api.example.com/comments');Promise.all([fetchUser, fetchPosts, fetchComments]).then(([userResponse, postsResponse, commentsResponse]) => {return Promise.all([userResponse.json(), postsResponse.json(), commentsResponse.json()]);}).then(([userData, postsData, commentsData]) => {console.log(userData, postsData, commentsData);}).catch((error) => {console.error('请求失败', error);});

并发执行需要注意并发量不要太大,我们可以通过实现一个并发控制的类来限制并发量。

class RequestScheduler {constructor(concurrencyLimit) {this.concurrencyLimit = concurrencyLimit;this.running = 0;this.queue = [];}// 添加请求到队列add(requestFn) {return new Promise((resolve, reject) => {this.queue.push({ requestFn, resolve, reject });this.runNext();});}// 执行下一个请求runNext() {if (this.running >= this.concurrencyLimit || this.queue.length === 0) {return;}const { requestFn, resolve, reject } = this.queue.shift();this.running++;requestFn().then((result) => {resolve(result);}).catch((error) => {reject(error);}).finally(() => {this.running--;this.runNext();});}
}// 使用示例
const scheduler = new RequestScheduler(3); // 限制并发请求数量为3const createRequest = (url) => () => fetch(url).then((response) => response.json());const urls = ['https://jsonplaceholder.typicode.com/posts/1','https://jsonplaceholder.typicode.com/posts/2','https://jsonplaceholder.typicode.com/posts/3','https://jsonplaceholder.typicode.com/posts/4','https://jsonplaceholder.typicode.com/posts/5'
];const requestPromises = urls.map((url) => scheduler.add(createRequest(url)));Promise.all(requestPromises).then((results) => {console.log('所有请求完成:', results);}).catch((error) => {console.error('请求失败:', error);});
  • createRequest方法生成返回Promise的请求函数;
  • scheduler.add方法将一个请求添加到调度器中,并在并发限制允许的情况下执行;
  • Promise.all的作用是等待所有请求完成,并且统一处理异常。

Promise.race的应用场景

Promise.race方法关注的是最快出结果(不管是fulfilled还是rejected)的promise,可以实现超时处理。
超时处理:在race中传入网络请求的promise和定时器的promise,如果网络请求在指定时间内到达则正常执行then流程,如果定时器先到达则表示超时,调用reject走catch流程。

const fetchWithTimeout = (url, timeout) => {const fetchPromise = fetch(url);const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), timeout));return Promise.race([fetchPromise, timeoutPromise]);
};fetchWithTimeout('https://api.example.com/data', 5000).then((response) => response.json()).then((data) => {console.log('请求成功', data);}).catch((error) => {console.error('请求失败或超时', error);});

Promise.allSettled

Promise.allSettled 方法返回一个在所有给定的 Promise 已经 fulfilled 或 rejected 后的 Promise,并且带有一个对象数组,每个对象表示对应的 Promise 结果。
如果是fulfilled,则结果字段为value
如果是rejected,则结果字段为reason

const promises = [Promise.resolve('resolved'),Promise.reject('rejected'),new Promise((resolve) => setTimeout(resolve, 1000, 'pending resolved'))
];Promise.allSettled(promises).then((results) => {results.forEach((result) => console.log(result));});// 输出:
// { status: 'fulfilled', value: 'resolved' }
// { status: 'rejected', reason: 'rejected' }
// { status: 'fulfilled', value: 'pending resolved' }

Promise.any

接受一个promise数组,返回一个promise。
和Promise.race不同,Promise.any会过滤掉所有rejected 的promise,而关注第一个fulfilled的promise的值。
如果数组中所有promise都被rejected的话,那么会返回一个AggregateError类型的实例,带有errors字段,是一个数组,指明了每一个promise的reason
应用场景:any可以用来在多个备用资源中获取最先成功响应的资源。
最快成功返回的备用资源:假设一个数据有多个可用来源,我们只需要拿到其中一个成功响应就可以了,那么肯定是想要拿最快返回的那一个,这个时候用any就很nice~

const loadImage = (url) => new Promise((resolve, reject) => {const img = new Image();img.onload = () => resolve(url);img.onerror = () => reject(new Error(`Failed to load image at ${url}`));img.src = url;
});const imageUrls = ['image1.png', 'image2.png', 'image3.png'];
const imagePromises = imageUrls.map(loadImage);Promise.any(imagePromises).then((result) => {console.log('第一个加载完成的图片', result);}).catch((error) => {console.error('所有图片加载失败', error);});

Promise.withResolvers

这个方法返回一个新的promise对象和用于解决或拒绝它的resolvereject方法。
可以简单地使用Promise手动实现:

Promise.withResolvers = function() {let resolve, reject;const promise = new Promise((res, rej) => {resolve = res;reject = rej;});return { promise, resolve, reject };
};

使用 Promise.withResolvers() 关键的区别在于解决和拒绝函数现在与 Promise 本身处于同一作用域,而不是在执行器中被创建和一次性使用。
通常在一些重复事件中使用,例如在处理流数据或者队列的时候,在这些场景下通常可以减少嵌套,优化代码结构。
这里介绍MDN上面的案例:将流转换为异步可迭代对象。

// 定义 async generator 函数 readableToAsyncIterable,将流转换为异步可迭代对象
async function* readableToAsyncIterable(stream) {// 创建 Promise 和解析器对象let { promise, resolve, reject } = Promise.withResolvers();// 监听流的错误事件,一旦出错则调用 reject 方法stream.on("error", (error) => reject(error));// 监听流的结束事件,一旦结束则调用 resolve 方法stream.on("end", () => resolve());// 监听流的可读事件,一旦流准备好可以读取则调用 resolve 方法stream.on("readable", () => resolve());// 循环处理流中的数据块,直到流不再可读while (stream.readable) {// 等待当前的 Promise 解决await promise;let chunk;// 循环读取流中的数据块while ((chunk = stream.read())) {// 生成数据块yield chunk;}// 获取新的 Promise 和解析器对象,以便下一轮循环使用({ promise, resolve, reject } = Promise.withResolvers());}
}

创建一个简单的可读流测试一下:

const { Readable } = require('stream');// 测试函数
async function testReadableToAsyncIterable() {// 创建一个简单的可读流const data = ['Hello', 'World'];const readableStream = Readable.from(data);// 将可读流转换为异步可迭代对象const asyncIterable = readableToAsyncIterable(readableStream);// 使用 for await...of 循环遍历异步可迭代对象中的数据块,并验证结果let result = '';for await (const chunk of asyncIterable) {result += chunk.toString();}// 断言结果是否符合预期if (result === data.join('')) {console.log('测试通过:数据正常读取和处理。');} else {console.error('测试失败:数据读取和处理出现问题。');}
}// 执行测试函数
testReadableToAsyncIterable();

Promise规范与手写Promise

👉 Promises/A+ 规范
示例代码:

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';class MyPromise {constructor(executor) {this.state = PENDING; // 初始状态为 pendingthis.value = undefined; // 成功时的值this.reason = undefined; // 失败时的原因this.onFulfilledCallbacks = []; // 存储成功时的回调函数this.onRejectedCallbacks = []; // 存储失败时的回调函数// 定义 resolve 函数,用于将状态转变为 fulfilled,并执行成功的回调函数const resolve = (value) => {if (this.state === PENDING) {this.state = FULFILLED;this.value = value;// 执行所有成功回调函数this._executeCallbacks(this.onFulfilledCallbacks, this.value);}};// 定义 reject 函数,用于将状态转变为 rejected,并执行失败的回调函数const reject = (reason) => {if (this.state === PENDING) {this.state = REJECTED;this.reason = reason;// 执行所有失败回调函数this._executeCallbacks(this.onRejectedCallbacks, this.reason);}};try {// 执行执行器函数,并传入 resolve 和 reject 函数executor(resolve, reject);} catch (error) {// 如果执行器函数抛出异常,则直接 rejectreject(error);}}// 定义 then 方法,用于链式调用then(onFulfilled, onRejected) {return new MyPromise((resolve, reject) => {// 定义处理成功的函数const handleFulfilled = (value) => {try {// 如果 onFulfilled 存在,则执行它,并获取结果const result = onFulfilled ? onFulfilled(value) : value;this._handleResult(result, resolve, reject);} catch (error) {reject(error);}};// 定义处理失败的函数const handleRejected = (reason) => {try {// 如果 onRejected 存在,则执行它,并获取结果const result = onRejected ? onRejected(reason) : reason;this._handleResult(result, resolve, reject);} catch (error) {reject(error);}};// 根据当前 Promise 的状态执行不同的逻辑if (this.state === FULFILLED) {// 使用 queueMicrotask 来模拟微任务,确保在当前事件循环结束后执行 handleFulfilledqueueMicrotask(() => handleFulfilled(this.value));} else if (this.state === REJECTED) {// 使用 queueMicrotask 来模拟微任务,确保在当前事件循环结束后执行 handleRejectedqueueMicrotask(() => handleRejected(this.reason));} else if (this.state === PENDING) {// 如果当前状态仍为 pending,则将处理成功和失败的函数加入对应的回调数组中this.onFulfilledCallbacks.push(value => {queueMicrotask(() => handleFulfilled(value));});this.onRejectedCallbacks.push(reason => {queueMicrotask(() => handleRejected(reason));});}});}// 定义 catch 方法,用于捕获 Promise 链中的错误catch(onRejected) {return this.then(null, onRejected);}// 静态方法 resolve,返回一个立即 resolved 的 Promise 对象static resolve(value) {return new MyPromise((resolve) => {resolve(value);});}// 静态方法 reject,返回一个立即 rejected 的 Promise 对象static reject(reason) {return new MyPromise((resolve, reject) => {reject(reason);});}// 静态方法 all,接收一个 Promise 数组,返回一个新的 Promise,当所有 Promise 都成功时才成功,结果为一个值数组static all(promises) {return new MyPromise((resolve, reject) => {const results = [];let count = 0;promises.forEach((promise, index) => {promise.then((value) => {results[index] = value;count++;// 当所有 Promise 都成功时,resolve 结果数组if (count === promises.length) {resolve(results);}}).catch(reject); // 一旦有 Promise 失败,则整体 Promise 也失败});});}// 静态方法 race,接收一个 Promise 数组,返回一个新的 Promise,以最先 resolved 或 rejected 的 Promise 的结果作为结果static race(promises) {return new MyPromise((resolve, reject) => {// 遍历 Promise 数组,一旦有 Promise 解决或拒绝,则立即 resolve 或 rejectpromises.forEach((promise) => {promise.then(resolve).catch(reject);});});}// 私有方法,用于处理 then 方法返回的结果_handleResult(result, resolve, reject) {if (result instanceof MyPromise) {// 如果返回结果是一个 Promise 实例,则继续链式调用result.then(resolve, reject);} else {// 否则直接将结果传递给下一个 Promise 的 resolveresolve(result);}}// 私有方法,用于执行回调函数数组中的所有回调_executeCallbacks(callbacks, arg) {callbacks.forEach(callback => {// 使用 queueMicrotask 来模拟微任务,确保在当前事件循环结束后执行回调queueMicrotask(() => callback(arg));});}
}

为什么使用数组存储回调函数?
通常我们在使用promise的时候只会调用一次then方法并传入一个回调函数,但其实then方法是可以多次调用的,例如下面这段代码,则会添加多个回调,因此需要使用数组存储回调函数。

const promise = new Promise((resolve, reject) => {// 执行异步操作setTimeout(() => {resolve('成功'); // 改变状态为 fulfilled}, 1000);
});promise.then(value => console.log('成功处理1:', value),reason => console.error('失败处理1:', reason)
);promise.then(value => console.log('成功处理2:', value),reason => console.error('失败处理2:', reason)
);

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

#cmd的常用命令(Dos)

cmd的常用命令首先win+r输入cmd并回车进入cmd命令中cd 命令:进入指定目录cd d:进入d盘目录.会发现进入不了d盘,因为cd只能在当前目录下操作不能跨区操作. 键入d:回车进入d盘.我d盘下有aaa文件夹cd aaa进入文件夹aaa目录下提示 ".."为上一级目录."."为当前…

StarRocks数据导入慢问题解决

一、问题描述依据StarRocks官网快速开始安装教程,用docker compose安装了starrocks,log模块从rabbitMq的队列批量获取log消息,发现队列消息有堆积,一晚上下来大概能对接4000条消息。经单元测试发现insert into到starrocks中时间竟然相差几百倍。 mysql每条insert sql执行3.…

CAN转PN网关模块连接激光切割机的配置方法

本文介绍了兴达易控CAN转Profinet网关模块(XD-PN_CAN20)用于连接CAN激光切割机的使用方法,激光切割机在工业生产中被广泛应用,而激光发射器与控制设备常以不同的协议存在两者之间,CAN总线和Profinet以各自的特点被广泛用于设备当中。本文将介绍介绍兴达易控CAN转Profinet网…

R语言、SAS潜类别(分类)轨迹模型LCTM分析体重指数 (BMI)数据可视化|附代码数据

全文下载链接: http://tecdat.cn/?p=26105 最近我们被客户要求撰写关于LCTM的研究报告,包括一些图形和统计输出。 在本文中,潜类别轨迹建模 (LCTM) 是流行病学中一种相对较新的方法,用于描述生命过程中的暴露,它将异质人群简化为同质模式或类别。然而,对于给定的数据集…

第二章 和式

记号 求和的符号有两种形式 第一种是确定界限的形式,也叫封闭形式,例如:\(\sum\limits_{k=1}^n a_k\) 第二种叫做一般形式,就是把一个或者多个条件写在 \(\sum\) 符号的下面,例如刚刚的例子可以写成 \(\sum\limits_{1\le k \le n} a_k\) 和式和递归式的转化 和式和递归式之…

Andriod SDK安装教程

前言 最简单的方式 我们使用ANDROID STUDIO这款开发工具下载对应的Andriod SDK。 可是我们如果不开发安卓,只是用它的一些SDK包的话而安装整个开发工具,就没必要了。 这里讲的是用独立的 命令行工具 来操作。 下载命令行工具 点击此处进入下载页面, 滑动到最下边,选择合适的…

动态规划--打家劫舍-零钱兑换-算法刷题01

目录1. 概念2. 打家劫舍3 零钱兑换 1. 概念 关于动态规划这类问题 强烈建议学完下面的帖子: https://blog.csdn.net/qq_16664581/article/details/89598243 理解动态规划的使用场景强烈建议读一下这个故事: https://www.cnblogs.com/sdjl/articles/1274312.html 步骤:确定问…

一天快速入门Django:从0到1创建属于自己的Web应用

本文详细讲解了从零开始构建自己的 Web 应用所需的 Django 操作步骤。文章以简明易懂的方式引导读者设置开发环境,创建 Django 项目和应用,定义数据模型,编写视图函数和模板,以及配置 URL 路由。强调了 Django 框架的高效性和灵活性,特别是其基于 MTV(模型、模板、视图)…

羽云十六进制编辑器之插件开发文档

羽云十六进制编辑器的开发文档首页【占位】本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/18286419

Mybatis PageHelper编译SQL引发的一次性能问题.18286262

起源 最近一直在跟大佬们做公司项目的性能优化,我这种小卡乐咪基本上负责的就是慢接口优化,但实际上只有以下几种情况需要进行接口代码级别的改造:循环查库、RPC 数据库设计不合理 业务流程太长,代码耦合性太高等随着对接口分析的深入,我们越来越发现系统中有很多拖后腿的…

蓝牙音箱App设计总结

前言 最近做了一个关于带Sound bar的智能电视的蓝牙项目,就是将电视Sound bar当作蓝牙音箱,将手机、电脑等设备的声音传输到电视,通过电视Soundbar播放声音。做这个项目的时候遇到了各种大大小小的问题,好在都解决了。本篇文章总结了在设计蓝牙相关的项目时需要了解的小知识…

设计模式学习(二)工厂模式——抽象工厂模式

介绍抽象工厂模式,并说明其优缺点目录背景抽象工厂模式优点与缺点 背景 现在我需要开发一个相机操作模块,它可能在Windows下运行,也可能在Linux下运行。由于在厂家提供的SDK中,Windows下的SDK和Linux下的SDK是有区别的,因此我们要创建两个类去封装这两个不同平台下的API。…