论Promise在前端江湖的地位及作用

系列文章:

  1. 先撸清楚:并发/并行、单线程/多线程、同步/异步
  2. 论Promise在前端江湖的地位及作用

前言

上篇文章阐述了并发/并行、单线程/多线程、同步/异步等概念,这篇将会分析Promise的江湖地位。
通过本篇文章,你将了解到:

  1. 为什么需要回调?
  2. 什么是回调地狱?
  3. Promise解决了什么问题?
  4. Promise常用的API
  5. async和await 如影随形
  6. Promise的江湖地位

1. 为什么需要回调?

1.1 同步回调

先看个简单的Demo:

function add(a: number, b: number) {return a + b
}function reprocess(a: number) {return a * a
}function calculate() {//加法运算let sum = add(4, 5)//进行再处理let result = reprocess(sum)//输出最终结果console.log("result:", result)
}

先进行加法运算,再对运算的结果进行处理,最终输出结果。
在reprocess()函数里我们对结果进行了平方,现在想要对它进行除法操作,那么依葫芦画瓢,需要再定义一个函数:

function reprocess2(a: number) {return a / 2
}

再后来,还需要继续增加其它功能如减法、乘法、取模等运算,那不是要新增不少函数吗?
假设该模块的主要功能是进行加法,至于对加法结果的再加工它并不关心,外界调用者想怎么玩就怎么玩。于是,回调出现了。
我们重新设计一下代码:

//新增函数作为入参
function add(a: number, b: number, callbackFun: (sum: number) => number) {let sum = a + breturn callbackFun(sum)
}function calculate() {//加法运算let result = add(4, 5, (sum) => {return sum / sum})//输出最终结果console.log("result:", result)let result2 = add(6, 8, (sum) => {return sum * sum - sum / 2})//输出最终结果console.log("result2:", result2)
}

add()函数最后一个入参是函数类型的参数,调用者需要实现这个函数,我们称这个函数为回调函数。于是在calculate()函数里,我们可以针对不同的需求调用add()函数,并通过回调函数实现不同的数据加工逻辑。

calculate()函数和回调函数是在同一线程里执行,并且按照代码书写的先后顺序执行,此时的回调函数是同步回调

1.2 异步回调

假若add()函数里对数据的加工需要一定的时间,我们用setTimeout模拟一下耗时操作:

//新增函数作为入参
function add(a: number, b: number, callbackFun: (sum: number) => void) {setTimeout(() => {let sum = a + bcallbackFun(sum)})
}function calculate() {//加法运算add(4, 5, (sum) => {let result = sum / sum//输出最终结果console.log("result:", result)//第1个打印})console.log("calculate end...")//第2个打印
}

从打印结果看,第2个打印反而比第一个打印先出现,说明第二个打印语句先执行。
calculate()函数执行add()函数的时候,并没有一直等待回调的结果,而是立马执行了第二个打印语句,而当add()函数内部实现执行时,才会执行回调函数,虽然calculate()和回调函数在同一线程执行,但是它们并没有按照代码书写的先后顺序执行,此时的回调函数是异步回调

1.3 为什么需要它?

回调函数的出现使得代码设计更灵活。
你可能会说:异步回调我还可以理解,毕竟或多或少都会涉及到异步调用,但同步回调不是脱裤子放屁吗?
其实不然,同步回调更多的表现在灵活度上,比如我们遍历一个数组:

const score = [60, 70, 80, 90, 100]
score.forEach((value, index, array) => {console.log("value:", value, " index:", index)
})

forEach()函数接收的是一个同步回调函数,该函数里可以获取到数组里每一个值,并可以对它进行自定义的逻辑操作。
除了forEach()函数,同步回调还大量地被运用于其它场景。

2. 什么是回调地狱?

先看一段代码:

interface NetCallback {//错误返回error: (errMsg: string) => void//成功返回succeed: (data: object) => void
}function fetchNetData(url: string, netCallback: NetCallback) {//模拟网络耗时setTimeout(() => {if (Math.random() > 0.2) {//成功netCallback.succeed({code: 200, msg: 'success'})} else {//失败netCallback.error(`${url} fetch error`)}}, 1000)
}function fetchStuInfo() {fetchNetData('/info/stu', {error: (errMsg) => {console.log(errMsg)},succeed: (data) => {console.log(data)}})
}fetchStuInfo()

上述代码是很常规的异步回调过程,看起来很正经没啥问题。
想象一种场景:通过stuId获取stuInfo,stuInfo里存有teacherId,通过teacherId获取teacherInfo,teacherInfo里有schoolId,通过schoolId获取schoolInfo。
很显然这三个接口是逐层(串行)依赖的,我们可以写出如下代码:

function fetchSchoolInfo() {//先获取学生信息,成功后带有teacherIdfetchNetData('/info/stu', {error: (errMsg) => {console.log(errMsg)},succeed: (data) => {//通过teacherId,再获取教师信息,成功后带有schoolIdfetchNetData('/info/teacher', {error: (errMsg) => {console.log(errMsg)},succeed: (data) => {//通过schoolId,再获取学校信息fetchNetData('/info/school', {error: (errMsg) => {console.log(errMsg)},succeed: (data) => {console.log(data)}})}})}})
}

可以看到fetchSchoolInfo()函数里嵌套地调用了fetchNetData()函数,层层递进,并且伴随着error和succeed分支判断,同时异常的错误很难抛出去。
此种场景下代码并不简洁,分支多容易出错且不易调试,当需要依赖的更多时,我们就陷入了回调地狱

3. Promise解决了什么问题?

3.1 Promise替代回调

怎么解决回调地狱的问题呢?这个时候Promise出现了。
还是以获取学生信息为例:

function fetchNetData(url: string): Promise<any> {//模拟网络耗时return new Promise((resolve, reject) => {setTimeout(() => {if (Math.random() > 0.2) {//成功resolve({code: 200, msg: 'success'})} else {//失败reject(`${url} fetch error`)}}, 1000)})
}

与之前的对比,fetchNetData()函数只需要传入一个参数,无需回调函数,它返回一个Promise。
当网络请求成功,则调用resolve()函数,当网络请求失败则调用reject()函数。
既然返回了Promise,接着看看如何使用这个返回值。

function fetchStuInfo() {fetchNetData('/info/stu').then(data => {//成功console.log(data)}, error => {//失败console.log(error)})
}

你可能会说,这看起来和使用回调的方式差不多呢,then()函数的闭包就相当于回调嘛。
确实,单看这个例子和回调差不多,接着尝试用Promise改造之前的回调地狱。

function fetchSchoolInfo() {//先获取学生信息,成功后带有teacherIdfetchNetData('/info/stu').then(data => fetchNetData('/info/teacher')).then(data => fetchNetData('/info/school')).then(data => console.log(data)).catch(err => console.log(err))
}

这么看,使用Promise是不是简洁了许多,回调方式代码一直往右增长,而使用Promise每个接口请求都是平铺,并且它们的逻辑关系是递进的。
三个接口都成功,则打印成功的结果。
其中一个接口失败,剩下的接口都不会再请求,并且错误结果被catch()函数捕获。

3.2 Promise基本使用

Promise 是个接口,它有两个函数:
image.png

  1. then(resolve,reject)函数,入参有两个(都是可选的),返回Promise类型
  2. catch(reject)函数,入参有一个(可选),返回Promise类型
  3. 构造Promise需要传递一个参数,其是函数类型,该函数类型包括两个入参:resolve和reject,当解决了Promise时需要调用resolve()函数,当拒绝了Promise时调用reject()函数

Promise中文意思是承诺,将Promise暴露出去意思就是将承诺放出来。

  1. 就像小明请小红帮个忙
  2. 小红不会立即帮忙,而是给小明一个承诺:我会回复你到底是帮还是不帮
  3. 小红决定帮忙:调用resolve()函数,表示这个忙我帮定了
  4. 小红决定不帮忙,调用reject()函数拒绝,表示爱莫能助
  5. 不论小红作出了什么样的答复,这个承诺就算结束了

用代码表示如下:

function helpXiaoMing(): Promise<string> {return new Promise((resolve, reject) => {//掷骰子if (Math.random() > 0.5) {resolve('这个忙我帮定了')} else {reject('爱莫能助')}})
}

无论小红resolve()还是reject(),最终小明得要知道结果。
当小明发起帮助请求时,他有两种方式可以拿到小红的回复:

  1. 一直等到小红回复,对应await()函数
  2. 先去做别的事,等小红通知,对应Promise.then()函数

我们先看第二种方式:

helpXiaoMing().then(value => {//成功的结果,value就是resolve的参数console.log(value)
}, reason => {//失败的结果,reason就是reject的参数console.log(reason)
})

从上我们也发现了Promise一个特点:无论外部是否有监听Promise结果,Promise都会按照既定逻辑更改它的状态。也就是说无论小明是否关注小红的承诺,她都需要给个准信。

回到最初的问题,Promise解决了什么问题:

  1. Promise本质上也是基于回调,只是把回调封装了
  2. Promise解决嵌套回调地狱的问题
  3. Promise使得异步代码更简洁
  4. Promise支持链式调用,很好地关联了多个异步逻辑

4. Promise常用的API

4.1 Promise 常用的API

上面列举了使用Promise基础三板斧:

  • new Promise((resolve,reject)),构造Promise对象
  • 修改状态resolve()/reject()
  • 监听(接收)Promise状态

1. then()可选参数
then()函数的两个参数都是可选的
只关注成功状态:

helpXiaoMing().then(value=>{console.log('success:',value)
})

只关注失败状态:

helpXiaoMing().then(null, reason => {console.log('fail:', reason)
})

两者皆关注:

helpXiaoMing().then(value => {console.log('success:', value)
}, reason => {console.log('fail:', reason)
})

2. catch()可选参数
不想在then里监听失败的状态,也可以单独使用catch()

helpXiaoMing().then(value => {console.log('success:', value)
}).catch(reason => {})

失败状态有两个来源:

  1. 显示调用了Promise.reject()函数
  2. 代码抛出了异常throw Error()

失败的状态会先找到最近能够处理该状态的地方。

3. finally()始终会执行
当Promise状态更改后,finally始终会执行,执行的顺序和书写顺序一致。

helpXiaoMing().then(value => {console.log('success:', value)
}).catch(reason => {console.log('error:', reason)
}).finally(() => {console.log('finally called')
})

Promise状态只要变成了成功或失败,那么finally打印将会执行,此时因为finally写在最后,因此最后执行。
交换个位置:

helpXiaoMing().finally(() => {console.log('finally called')
}).then(value => {console.log('success:', value)
}).catch(reason => {console.log('error:', reason)
})

finally打印先执行。

4. then()/catch()/finally() 函数返回值
这三个函数都是返回了Promise,那他们的Promise的状态由谁更改呢?

helpXiaoMing().then(value => {console.log('success:', value)return 'success occur'
}).then(value => {console.log('second then value:', value)
}).catch(() => {
})

第一个then()函数返回了一个Promise,而这个Promise的值就是第一个then()函数闭包里返回的 ‘success occur’。
当第二个then()执行时,会等待第一个then()函数返回的Promise状态更改,此时return 'success occur’之后就会执行Promise.resolve( ‘success occur’),因此第二个then()函数打印:second then value: success occur

同样的,当在catch()函数的闭包里返回值时,该值也作为下一个then()的入参。

helpXiaoMing().then(value => {console.log('success:', value)return 'success occur'
}).catch(() => {return '抓到错误,将信息传递给下一个then'
}).then(value => {console.log('second then value:', value)
})

至于finally(),它的闭包里没有参数,返回值也不会传递下去。

then()/catch()函数特性使得Promise可以进行链式调用。

5. then()/catch()/finally() 函数闭包返回值
理论上这几个函数的的闭包能够返回任意值,先看Promise构造函数闭包里传递的类型:

function helpXiaoMing(): Promise<any> {return new Promise((resolve, reject) => {//掷骰子if (Math.random() > 0.5) {console.log('resolve')//resolve('这个忙我帮定了') 返回普通字符串(基本类型)resolve({msg: '这个忙我帮定了'})//返回对象} else {console.log('reject')//reject('爱莫能助') 返回普通字符串(基本类型)reject({reason: '爱莫能助'})//返回对象}})
}

由上可知,传递了引用对象类型,那么helpXiaoMing().then()闭包接收的参数也是对象。而对象里比较特殊的是返回Promise类型的对象。

function helpXiaoMing(): Promise<any> {//外层Promise对象return new Promise((resolve, reject) => {//掷骰子if (Math.random() > 0.5) {console.log('resolve')//内层Promise对象resolve(new Promise((resolve2, reject2) => {setTimeout(() => {resolve2('我是内部的Promise')}, 2000)}))} else {console.log('reject')//reject('爱莫能助') 返回普通字符串reject({reason: '爱莫能助'})//返回对象}})
}

当调用:

helpXiaoMing().then(value => {console.log('success:', value)return 'success occur'
})

then监听的是内层Promise对象的变化,因此最终打印的结果是:

resolve
success: 我是内部的Promise

同样的,then()/catch()/finally()闭包里也可以返回Promise对象

helpXiaoMing().then(value => {console.log('success:', value)return new Promise((resolve2, reject2) => {setTimeout(() => {resolve2('我是内部的Promise')}, 2000)})
}).then(value => {console.log('second then value:', value)
})

基于这种特性,Promise可作链式调用,就像最开始那会儿用Promise替代回调的写法就涉及到了Promise链式调用。

4.2 Promise 易混淆的地方

先看第一个易混点:

helpXiaoMing().then(value => {console.log('success:', value)
}).then(value => {//猜猜这里的打印结果是什么console.log(value)
})

如果第一个then闭包执行成功,那么第二个then闭包的结果是啥?
答案是输出:undefined
因为想要将数据往下传递,then()/catch()函数闭包里必须显式返回数据:

helpXiaoMing().then(value => {console.log('success:', value)return value
}).then(value => {//猜猜这里的打印结果是什么console.log(value)
})

当然如果是简单的表达式,那就可以忽略return:

helpXiaoMing().then(value => value).then(value => {//猜猜这里的打印结果是什么console.log(value)
})

与上面效果一致。

第二个易混点:

helpXiaoMing().then(value => {throw Error
}).catch()

catch()能够捕获到异常吗?
答案是:不能
catch()需要传入参数:

helpXiaoMing().then(value => {throw Error
}).catch(()=>{})

一个空的实现,就能捕获异常。

第三个易混点:
finally()闭包在then()或catch()闭包之后执行?
答案是:不一定
这和传统的try{…}catch{…}finally{…}不太一样,传统的先执行try里面的或者是catch里的,最终才执行finally,而此处Promise里的finally是表示该Promise状态变为了"settled",至于在then()闭包还是catch()闭包前执行,决定点在于书写的顺序,具体的Demo在上一节。

第四个易混点:
Promise需要调用then()才会触发状态变化吗?
答案是:不一定

function test() {return new Promise((resolve, reject) => {console.log('hello')resolve('hello')})
}
//没有.then,Promise状态也会变化
test()

4.3 Promise其它API

还有一些比较高级的API,如Promise.all()/Promise.allSettled()/Promise.race()/Promise.any()/Promise.reject()/Promise.resolve()等,此处就不再细说。

5. async和await 如影随形

5.1 await 返回值

Promise确实比较好用,你可能已经发现了监听Promise的状态变化是个异步的过程,then()函数里的闭包其实就是传一个回调函数进去。
有些时候我们需要等待异步任务的结果回来后再进行下一步操作,这个时候该怎么做呢?
之前提到过的Demo里,小明可以选择一直等小红的回复,也可以先去做别的事等小红的通知,第二种场景上边已经分析过了,这次我们来看看第一种场景。

async function testWait() {console.log('before get result')const result = await helpXiaoMing()console.log('after result:', result)
}
testWait()

使用await操作符会使得当前调用者一直等待Promise状态变为完成(可能成功、可能失败),如上第二条语句一直等到Promise结束。
如果Promise成功,则拿到具体结果,如果Promise失败则会返回异常,因此需要对await本身进行异常捕获:

async function testWait() {console.log('before get result')try {const result = await helpXiaoMing()console.log('after result:', result)} catch (e) {console.log(e)}
}
  1. await 作用是挂起当前线程,而不是让线程停止执行(sleep等),挂起的意思是线程执行到await 这地方就暂时不往下执行了,但它不会休息,而是先去执行其它任务
  2. 等到await 的Promise返回,线程继续执行await之后的代码
  3. await 只能在async 修饰的函数里调用

5.2 async 修饰的函数返回值

async 修饰的函数最终会返回Promise
image.png
如上图,经过async修饰的函数,它的返回值被包装为Promise对象,而该Promise对象的值来源于async 函数的return 语句,此处我们没有return,因此值类型是void。
image.png
此时Promise值类型是string。

await helpXiaoMing()发生了异常,await之后的代码不会再执行。同时async返回的Promise会调用reject()函数将异常传递出去。

async function testWait() {console.log('before get result')const result = await helpXiaoMing()console.log('after result:', result)return '完成了'
}testWait().then(value => {//成功,走这console.log('value=>', value)
}, error => {//失败走这console.log('error=>', error)
})

5.3 理解async和await的时序

看以下例子,猜猜打印结果是什么?

function waitPromise2() {return new Promise((resolve, reject) => {setTimeout(() => {resolve('waitPromise2返回')}, 1000)})
}async function testWait1() {console.log('before1 get result')const result = await waitPromise1()console.log('after1 result:', result)return '完成了testWait1'
}async function testWait2() {console.log('before2 get result')const result = await waitPromise2()console.log('after2 result:', result)return '完成了testWait2'
}testWait1()
testWait2()

答案是:

before1 get result
before2 get result
after2 result: waitPromise2返回
after1 result: waitPromise1返回

刚接触async/await 的小伙伴可能会认为:

testWait1()里不是有await 阻塞了吗?此时线程一直阻塞在await处,testWait2()没机会执行,必须等到testWait1()结束后才能执行?

而实际的效果却是:

  1. 线程执行到testWait1()里的await后挂起,并退出testWait1(),进而继续执行testWait2()
  2. 在执行testWait2()的await后也会挂起
  3. 此时testWait1()和testWait2()都执行到await了,等待各自的Promise返回结果
  4. 由于testWait2()里的await时间较短,它先完成了所以先打印了"after2 result: waitPromise2返回",紧接着testWait1()的await 也返回了

当然,如果想要testWait1()和testWait2()按顺序执行怎么办呢?
我们知道testWait1()和testWait2()都会返回Promise,我们只需要await Promise即可:

async function testWait() {await testWait1()await testWait2()
}
testWait()

其打印结果如下:

before1 get result
after1 result: waitPromise1返回
before2 get result
after2 result: waitPromise2返回

5.4 async和await 作用

Promise代表的是异步编程,而通过async和await的亲密配合,我们可以使用同步的方式编写异步的代码。
其它语言也有类似的操作,比如Koltin的协程里的withcontext()函数。

6. Promise的江湖地位

好了说了一大篇Promise,是时候总结一下了。

  1. Promise 是前端实现异步任务的基石
  2. Promise 存在于前端代码的各个方面

至于地位嘛,类比阁老
image.png

本篇介绍了Promise的基本用法以及坑点,下篇将重点分析异步任务的时序(宏任务、微任务),相信你看完再也不用担心时序问题了,敬请期待~

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

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

相关文章

AI崛起,掌握它,开启智能新生活!

AI崛起&#xff0c;掌握它&#xff0c;开启智能新生活&#xff01; &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f3c6; 博客首页 怒放吧德德 To记录领地 &…

Linux中vim的基本使用

目录 vim中的三种模式以及基本操作命令模式(默认模式)插入模式底行模式 命令模式下的命令底行模式下的命令 vim是Linux和Unix环境下最基本的文本编辑器&#xff0c;类似于windows上的记事本 vim和Visual studio相比&#xff0c;vim并不集成&#xff0c;vim只能用来写代码 VS把写…

2024年5月24日 十二生肖 今日运势

小运播报&#xff1a;2024年5月24日&#xff0c;星期五&#xff0c;农历四月十七 &#xff08;甲辰年己巳月戊子日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;龙、牛、猴 需要注意&#xff1a;兔、羊、马 喜神方位&#xff1a;东南方 财神方位&#xff1a;…

深度学习之基于Matlab的BP神经网络交通标志识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着智能交通系统&#xff08;ITS&#xff09;的快速发展&#xff0c;交通标志识别&#xff0…

BUUCTF---misc---[MRCTF2020]ezmisc

1、附件下载后是一张图片 2、查看属性&#xff0c;winhex分析&#xff0c;没发现什么 3、在kali中binwalk和foremost也没找到什么信息 4、用stegsolve分析也没发现什么 5、这里几乎常见的misc方法都试过了&#xff0c;还是没有发现什么 6、回归到图片本身&#xff0c;想到的…

基于51单片机智能大棚浇花花盆浇水灌溉补光散热设计

一.硬件方案 本设计通过光敏电阻检测光照强度&#xff0c;然后A/D模块PCF8591处理后&#xff0c;将光照强度值实时显示在液晶上&#xff0c;并且可以按键控制光照的强度值&#xff0c;当光照低于设定的阈值&#xff0c;1颗白色高亮LED灯亮进行补光&#xff0c;光照高于设定的阈…

第六节 自动装配源码理解

tips&#xff1a;不同版本代码实现有差异。 前面两章了解的流程&#xff0c;就是 SpringBoot 自动转配的核心。 一、自动装配 1.1 什么是 SpringBoot 自动装配? 自动装配是 Spring 框架用来减少配置的显式需求而引入的一个特性&#xff0c;该特性通过 Autowired或者Resource…

Redis数据库知识点

Redis set get del keys redis中有哪些数据类型 string 最大512m key层级 redis的key允许有多个单词形成层级结构&#xff0c;多个单词之间用‘:’隔开 set get del keys hash 本身在redis中存储方式就为key-value, 而hash数据结构中value又是一对key-value hset key …

【easyx】快速入门——弹球小游戏(第一代)

目录 1.需求 2.运动的小球 3.碰到边缘反弹 4.圆周撞击或越过边界反弹 5.绘制和移动挡板 6.小球碰到挡板反弹 7.游戏失败时该如何处理 8.随机初始条件 9.完整代码 我们这一节将结合动画和键盘交互的知识来做一个小游戏 1.需求 我们先看需求:小球在窗体内运动,撞到除…

后端数据库开发JDBC编程Mybatis之用基于XML文件的方式映射SQL语句实操

之前的SQL语句是基于注解 以后开发中一般是一个接口对应一个映射文件 书写映射文件 基本结构 框架 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.or…

尽在掌握:Android 13 通知新功能详解

尽在掌握&#xff1a;Android 13 通知新功能详解 在移动应用开发中&#xff0c;通知扮演着至关重要的角色&#xff0c;它如同应用程序与用户之间的桥梁&#xff0c;及时传递关键信息&#xff0c;提升用户体验。Android 13 作为最新的安卓版本&#xff0c;在通知方面带来了诸多…

QLExpress入门及实战总结

文章目录 1.背景2.简介3.QLExpress实战3.1 基础例子3.2 低代码实战3.2.1 需求描述3.2.1 使用规则引擎3.3.2 运行结果 参考文档 1.背景 最近研究低代码实现后端业务逻辑相关功能&#xff0c;使用LiteFlow作为流程编排后端service服务, 但是LiteFlow官方未提供图形界面编排流程。…

使用RAG和文本转语音功能,我构建了一个 QA 问答机器人

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学. 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总合集&…

代码随想录算法训练营第36期DAY37

DAY37 先二刷昨天的3道题目&#xff0c;每种方法都写&#xff1a;是否已完成&#xff1a;是。 报告&#xff1a;134加油站的朴素法没写对。原因是&#xff1a;在if中缺少了store>0的判断&#xff0c;只给出了indexi的判断。前进法没写出来。因为忘记了总油量的判断。Sum。…

VMware 安装Windows Server 2008 R2

1.下载镜像 迅雷&#xff1a;ed2k://|file|cn_windows_server_2008_r2_standard_enterprise_datacenter_and_web_with_sp1_x64_dvd_617598.iso|3368839168|D282F613A80C2F45FF23B79212A3CF67|/ 2.安装过程 自定义名字&#xff0c;点击【浏览】选择安装路径 点击【浏览】选择前…

(Oracle)SQL优化基础(三):看懂执行计划顺序

往期内容&#xff1a; &#xff08;Oracle&#xff09;SQL优化基础&#xff08;一&#xff09;&#xff1a;获取执行计划 &#xff08;Oracle&#xff09;SQL优化基础&#xff08;二&#xff09;&#xff1a;统计信息 获取到执行计划后&#xff0c;对于新手朋友来讲可能不知道…

Qt笔记:动态处理多个按钮点击事件以更新UI

问题描述 在开发Qt应用程序时&#xff0c;经常需要处理多个按钮的点击事件&#xff0c;并根据点击的按钮来更新用户界面&#xff08;UI&#xff09;&#xff0c;如下图。例如&#xff0c;你可能有一个包含多个按钮的界面&#xff0c;每个按钮都与一个文本框和一个复选框相关联…

基于springboot+vue+Mysql的逍遥大药房管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

【flutter】 Running Gradle task ‘assembleDebug‘...超时问题

关联搜索&#xff1a;flutter下载gradle失败、AndroidStudio下载gradle失败 构建Flutter项目时遇到控制台一直卡在 Running Gradle task ‘assembleDebug’… 解决方案 1. 修改gradle-wrapper.properties 文件 如果找不到就直接搜索&#xff1a; 把https\://services.gradl…

vscode更改语言,记录一下

首先打开安装好的Vscode软件&#xff0c;可以看到页面上显示的是英文效果。 同时按键ctrlshiftp&#xff0c;接着在输入框中输入 configure Display language如图&#xff1a; 选择中文简体就ok了&#xff0c;如果没有则安装 chinese Language pack