闲谈Promise
预备知识
- 回调函数:当一个函数作为参数传入另一个函数中,并且它不会立刻执行,当满足一定条件之后,才会执行,这种函数称为回调函数。比如:定时器。
- 异步任务:与之对应的概念是同步任务,同步任务是在主线程上排队执行,当前面的任务执行完成,才会执行下一个任务。异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完成不影响下一个任务的指向。简单理解就是同步要按代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
为什么出现Promise
存在异步任务的代码,不能保证代码按照顺序执行,但在特定的需要下,就是需要在异步中实现顺序执行,这时常使用回调函数去实现,比如下面的代码:
setTimeout(()=>{console.log('找实习');setTimeout(()=>{console.log('面试通过');setTimeout(()=>{console.log('拿offer');setTimeout(()=>{console.log('上班');},2000)},1000)},1000)},2000)
执行结果:

虽然上面的代码能实现我们想要的结果,但一些情形下,需要嵌套更多回调函数时,多层嵌套让代码可读性非常差,后期的维护或者异常处理等都变得特别繁琐,让缩进格式也变得非常麻烦。
回调函数A中嵌套回调函数B,回调函数B中嵌套回调函数C.......这种回调函数多层嵌套的情况被称为回调地狱。回调地狱就是为了实现在异步任务中代码也能顺序执行而出现的一种操作。
为解决回调地狱带来的问题,ES6新增了Promise类,让我们能更优雅的书写复杂的异步任务。
Promise
先用一段代码认识一下Promise:
new Promise((resolve,reject)=>{setTimeout(()=>{resolve('3秒后执行')},3000)
}).then(res=>{console.log(res);})
- 是一个类,需要使用new Promise来创建实例对象。
- promise有三种状态:pending(待定),fulfilled(已兑现),rejected(已拒绝)。fulfilled和rejected也可以称为已敲定。promise初始状态是pending,promise的状态转换有两条路:一条pending->fulfilled,一条pending->rejected。状态一旦改变,就不能再发生变化。
- 调用resolve()方法,pending状态会变成fulfilled状态,并且触发then()中第一个回调方法;调用reject()方法,pending状态会变成rejected状态,并且触发then()中第二个回调方法。
- promise有三个实例方法,then、catch、finally。其中catch和finally实际都是在调用then方法,只是它们往then中传递的参数不一样。
catch : then(undefined,reject的回调方法) finally : then(A回调,A回调) - promise的三个实例方法和六个静态方法都是返回一个新的promise。六个静态方法为:resolve、reject、race、all、allSettled、any。其中race、all、allSettled、any将一个 Promise 可迭代对象作为输入(也可以简单理解成:传递数组作为参数)。
promise的实例方法
下面会对三个实例方法进行重写,对于相同的代码进行了提取:
// 将方法放到任务队列中的方法function runAsynctask(callback) {if (typeof queueMicrotask === "function") {queueMicrotask(callback);} else if (typeof MutationObserver === "function") {// 创建观察器const obs = new MutationObserver(callback);// 创建元素,添加监听const divNode = document.createElement("div");// 参数1 观察的dom节点// 参数2 观察的选项(childList 观察子节点改变)obs.observe(divNode, { childList: true });// 修改元素内容divNode.innerText = "999999";} else {setTimeout(callback, 0);}}// 对then不同的返回值进行不同操作function judgmentFn(x, p, resolve, reject) {if (x === p) {throw new TypeError("Chaining cycle detected for promise #<Promise>");}if (x instanceof QPromise) {x.then((res) => resolve(res),(err) => reject(err));} else {resolve(x);}}
then方法
then() 方法最多接受两个参数:用于 Promise 兑现和拒绝情况的回调函数。它返回一个 Promise 对象。
手写then方法:
then(onFulfilled, onRejected) {// 参数判断onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;onRejected = typeof onRejected === "function" ? onRejected : (x) => {throw x;};const p = new QPromise((resolve, reject) => {// 执行方法if (this.state === FULFILLED) {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === REJECTED) {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === PENDING) {// 保存回调函数this.#handlers.push({onFulfilled: () => {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},onRejected: () => {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},});}});return p;}
catch方法
catch() :在 promise 被拒绝时调用catch()里的函数;返回一个 Promise 对象。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。
catch(onRejected) {// 内部调用then方法return this.then(undefined, onRejected);
}
finally方法
finally() :用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数,无论是兑现还是拒绝,都是调用同一个函数。
finally(onFinally) {return this.then(onFinally, onFinally);}
promise的静态方法
resolve方法
Promise.resolve() :将给定的值转换为一个 Promise。如果这个值本身就是promise,则直接返回这个值(就算这个promise的状态是已拒绝的);其它值就封装成promise,返回的 Promise 将会以该值兑现。
static resolve(value) {// 如果是promise 直接返回if (value instanceof QPromise) {return value;}// 其他值,就直接封装成promise返回return new QPromise((res) => {res(value);});
}
reject方法
reject():返回一个已拒绝的 Promise 对象,拒绝原因 是给定的参数。
static reject(value) {// 不管是什么值(包括 Promise 对象),都重新封装成promise返回return new QPromise((undefined, reject) => {reject(value);});
}
race方法
race():等待第一个敲定;返回一个 Promise,这个返回的 promise 会随着第一个 promise 的敲定而敲定。
static race(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 等待第一个敲定(promise状态的敲定)promises.forEach((p) => {// 对数组中的每一项使用resolve,可以保证每一项都是promise,所以每一项都可以使用thenQPromise.resolve(p).then((res) => {resolve(res);},(err) => {reject(err);});});});
}
allSettled方法
allSettled():等待全部敲定;当数组中的每一个都已敲定时,返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。
static allSettled(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 等待全部敲定// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {// 对数组中的每一项使用resolve,可以保证每一项都是promise,所以每一项都可以使用thenQPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = { status: FULFILLED, value: res };// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {results[index] = { status: REJECTED, reason: err };count++;count === promises.length && resolve(results);});});});}
all方法
all():两种情况:
- 当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是空数组),并返回一个包含所有兑现值的数组。
- 如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
static all(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 处理全部兑现// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {// 对数组中的每一项使用resolve,可以保证每一项都是promise,所以每一项都可以使用thenQPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = res;// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {// 处理第一个拒绝reject(err);});});});}
any方法
any():两种情况:
- 当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。
- 当所有输入 Promise 都被拒绝(包括空数组)时,它会以一个包含拒绝原因数组的AggregateError 拒绝。
static any(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && reject(new AggregateError(promises,'All promises were rejected'))// 等待结果const errors=[]let count=0promises.forEach((p,index)=>{QPromise.resolve(p).then(res=>{// 第一个兑现resolve(res)},err=>{// 全部拒绝errors[index]=errcount++count===promises.length && reject(new AggregateError(errors,'All promises were rejected'))})})});}
手写Promise
手写promise的完整代码
// 三个状态const PENDING = "pending";const FULFILLED = "fulfilled";const REJECTED = "rejected";// 定义QPromise类class QPromise {// 初始状态state = PENDING;// 初始原因result = undefined;// 定义实例属性#handlers = [];// 构造函数constructor(fn) {const resolve = (result) => {// 状态不可逆if (this.state === PENDING) {// 修改状态this.state = FULFILLED;this.result = result;// 调用成功回调this.#handlers.forEach(({ onFulfilled }) => {onFulfilled(this.result);});}};const reject = (result) => {// 状态不可逆if (this.state === PENDING) {// 修改状态this.state = REJECTED;this.result = result;// 调用失败回调this.#handlers.forEach(({ onRejected }) => {onRejected(this.result);});}};try {// 执行fn(resolve, reject);} catch (error) {reject(error);}}// then :将方法放到微任务队列中then(onFulfilled, onRejected) {// 参数判断onFulfilled =typeof onFulfilled === "function" ? onFulfilled : (x) => x;onRejected =typeof onRejected === "function"? onRejected: (x) => {throw x;};const p = new QPromise((resolve, reject) => {// 执行方法if (this.state === FULFILLED) {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === REJECTED) {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === PENDING) {// 保存回调函数this.#handlers.push({onFulfilled: () => {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},onRejected: () => {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},});}});return p;}// 实例方法catch(onRejected) {// 内部调用then方法return this.then(undefined, onRejected);}// 实例方法finally(onFinally) {return this.then(onFinally, onFinally);}// 静态方法static resolve(value) {// 如果是promise 直接返回if (value instanceof QPromise) {return value;}// 其他值,就直接封装成promise返回return new QPromise((res) => {res(value);});}static reject(value) {// 不管是什么值(包括 Promise 对象),都重新封装成promise返回return new QPromise((undefined, reject) => {reject(value);});}// 等待第一个敲定static race(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 等待第一个敲定(promise状态的敲定)promises.forEach((p) => {QPromise.resolve(p).then((res) => {resolve(res);},(err) => {reject(err);});});});}// 全部状态为成功,则返回一个数组// 若有一个失败,则处理失败static all(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 处理全部兑现// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {QPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = res;// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {// 处理第一个拒绝reject(err);});});});}// 等待全部状态敲定,不管是失败还是成功状态,都放在一个数组中返回static allSettled(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 等待全部敲定// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {QPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = { status: FULFILLED, value: res };// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {results[index] = { status: REJECTED, reason: err };count++;count === promises.length && resolve(results);});});});}// 返回第一个状态兑现(成功)的结果// 全部拒绝时,返回拒绝的结果数组static any(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && reject(new AggregateError(promises,'All promises were rejected'))// 等待结果const errors=[]let count=0promises.forEach((p,index)=>{QPromise.resolve(p).then(res=>{// 第一个兑现resolve(res)},err=>{// 全部拒绝errors[index]=errcount++count===promises.length && reject(new AggregateError(errors,'All promises were rejected'))})})});}}// 异步函数function runAsynctask(callback) {if (typeof queueMicrotask === "function") {queueMicrotask(callback);} else if (typeof MutationObserver === "function") {// 创建观察器const obs = new MutationObserver(callback);// 创建元素,添加监听const divNode = document.createElement("div");// 参数1 观察的dom节点// 参数2 观察的选项(childList 观察子节点改变)obs.observe(divNode, { childList: true });// 修改元素内容divNode.innerText = "999999";} else {setTimeout(callback, 0);}}// 对then不同的返回值进行不同操作function judgmentFn(x, p, resolve, reject) {if (x === p) {throw new TypeError("Chaining cycle detected for promise #<Promise>");}if (x instanceof QPromise) {x.then((res) => resolve(res),(err) => reject(err));} else {resolve(x);}}
如果本文对你有帮助,希望能得到你的点赞或收藏或关注,这是对我最好的鼓励;
如你有问题或疑惑,欢迎在评论区写下,必将努力解答;
如本文有误区,希望你不吝赐教,让我们共勉!
相关文章:
闲谈Promise
预备知识 回调函数:当一个函数作为参数传入另一个函数中,并且它不会立刻执行,当满足一定条件之后,才会执行,这种函数称为回调函数。比如:定时器。异步任务:与之对应的概念是同步任务࿰…...
【C++堆(优先队列)】1882. 使用服务器处理任务|1979
本文涉及知识点 C堆(优先队列) LeetCode1882. 使用服务器处理任务 给你两个 下标从 0 开始 的整数数组 servers 和 tasks ,长度分别为 n 和 m 。servers[i] 是第 i 台服务器的 权重 ,而 tasks[j] 是处理…...
VBA高级应用30例应用3Excel中的ListObject对象:选择表的一部分
《VBA高级应用30例》(版权10178985),是我推出的第十套教程,教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开,这套教程案例与理论结合,紧贴“实战”,并做“战术总结”,以…...
C语言-变量
全局变量可以定义在头文件中吗? 在C和C编程中,全局变量可以定义在头文件中,但通常不建议这样做,因为这可能导致多个源文件(.c 或 .cpp 文件)包含同一个头文件时,发生多重定义错误(m…...
linux下位机出现使用TCP socket为0的问题
问题现象:下位机做TCP服务器,上位机来连接下位机的TCP服务,中间会有主动断开(上位机主动关闭socket)和异常断开(网线断开)的情况,出现异常的时候,上位机连接下位机的TCP …...
论文笔记:Prototypical Verbalizer for Prompt-based Few-shot Tuning
论文来源:ACL 2022 论文地址:https://arxiv.org/pdf/2203.09770.pdfhttps://arxiv.org/pdf/2203.09770.pdf 论文代码:https://github.com/thunlp/OpenPrompthttps://github.com/thunlp/OpenPrompt Abstract 基于提示的预训练语言模型&#…...
nn.functional.softmax(X, dim=-1)
dim-1表示在最后一个维度(大概率是一行)应用Softmax函数,将值标准化为概率分布。 实例 假设我们有一个张量X,形状为(2,3),内容如下: import torch import torch.nn.…...
【动态规划】子数组系列(上)
1. 最大子数组和 53. 最大子数组和 状态表示:以 i 位置为结尾时的所有子数组中的最大和 状态转移方程: i 位置为结尾的子数组又可以分为长度为 1 的和大于 1 的,长度为 1 就是 nums[i] ,长度不为 1 就是 dp[i - 1] nums[i]&…...
字节青训营入门算法题:飞行棋分组
链接:飞行棋分组🔗🔗 题目 现在有一堆飞行棋棋子,每个棋子上标有数字序号。需要将这些棋子分成若干组,每组包含5个棋子,且组内所有棋子的数字序号必须相同。需要判断是否可以完成这样的分组。 解答 为了…...
# 执行 rpm -qa | grep qq 查询软件安装情况时报错 数据库损坏 db3 error(-30974)
执行 rpm -qa | grep qq 查询软件安装情况时报错 数据库损坏 db3 error(-30974) 一、问题描述: 在 linux 系统上,使用包管理工具 rpm 查询某一个软件安装情况,如:执行 rpm -qa | grep qq 时,报错 数据库损坏 db3 err…...
离线服务器上复现G3SR论文实验
代码地址:https://github.com/AllminerLab/Code-for-G3SR-master 论文地址:https://ieeexplore.ieee.org/abstract/document/9741079/ 因为直接按照作者的方法操作会出现问题,故笔者在这里记录一下的实验过程。 实验环境 python=3.6 pytorch pytorch的下载命令需要自行前往…...
Android 未来可能支持 Linux 应用,Linux 终端可能登陆 Android 平台
近日,根据 android authority 的消息,Google 正在开发适用于 Android 的 Linux 终端应用,而终端应用可以通过开发人员选项启用,并将 Debian 安装在虚拟机中。 在几周前,Google 的工程师开始为 Android 开发新的 Termi…...
PostgreSQL学习笔记十四:PL/Python自定义函数
在 PostgreSQL 中可以使用 PL/Python 语言来创建自定义函数。以下是一个示例步骤: 一、创建自定义函数 连接到 PostgreSQL 数据库,可以使用 psql 命令行工具或者通过数据库管理工具。 执行以下 SQL 语句创建一个简单的 PL/Python 函数: C…...
计算机毕业设计 | springboot商城售后管理系统 购物平台(附源码)
1,绪论 1.1 开发背景 在数字化时代的推动下,产品售后服务管理机构面临着信息化和网络化的挑战。传统的手工管理和纸质档案已经无法满足管理人员和读者的需求。为了提高产品售后服务管理机构的管理效率和服务质量,开发和实现一个基于Java的售…...
(全网独家)面试要懂运维真实案例:HDFS重新平衡(HDFS Balancer)没触发问题排查
在面试时,面试官为了考察面试者是否真的有经验,经常会问运维集群时遇到什么问题,解决具体流程。下面是自己遇到HDFS Balancer没执行,花了半天时间进行排查,全网独家的案例和解决方案。 目录 使用CDH自带重新平衡操作…...
【数据结构笔记】搜索树
二叉搜索树 任一节点x的左/右子树中,所有非空节点均不大于(不小于)x 必须是所有的非空节点,仅左右孩子不够(左孩子的右孩子可能很大)一棵二叉树是二叉搜索树当且仅当中序遍历序列是单调非降序列 两棵二叉…...
如何使用UART(STM32 HAL库)
UART (通用异步收发器)是在 USART (通用同步异步收发器)基础上裁剪掉了同步通信功能,只剩下异步通信功能。关于通信和串口的基本知识,可参见文章《串口通信简介-CSDN博客》和《数据通信的一些基础概念-CSDN…...
星巴克英语
用流利的英文点星巴克 一杯咖啡 英文中文英文中文barista咖啡师coffee maker家用咖啡机cup sleeve杯套coffee stirrer咖啡棒coffee cup lid咖啡杯盖子straw吸管latte art咖啡拉花for here内用to go外带 例句: Could I have a cup sleeve for my coffee , please…...
权重衰减与暂退法——paddle部分
权重衰减与暂退法——paddle部分 本文部分为paddle框架以及部分理论分析,torch框架对应代码可见权重衰减与暂退法torch import paddle print("paddle version:",paddle.__version__)paddle version: 2.6.1当我们谈论机器学习模型的性能时,经…...
golang获取当天最小的时间,以DateTime的string格式返回
推荐学习文档 golang应用级os框架,欢迎stargolang应用级os框架使用案例,欢迎star案例:基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总想学习更多golang知识,这里有免费的golang学习笔…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
