論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.pswp.cn/diannao/13941.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/13941.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/13941.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

100base-tx、100base-fx的區別

100表示網線設計的頻率&#xff0c;單位MHz。值越大&#xff0c;網線的速度越快。baseBASEband的縮寫&#xff0c;基帶t物理介質是雙絞線纜f物理介質是光纖x同一個傳輸效率下的多種不同的標準 T表示雙絞線&#xff0c;base-tx是運行超五類雙絞線的快速以太網端口&#xff0c;全…

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把寫…

Nginx限制IP訪問詳解

在Web服務器管理中&#xff0c;限制某些IP地址訪問網站是一個常見的需求。Nginx作為一款高性能的HTTP服務器和反向代理服務器&#xff0c;提供了靈活強大的配置選項來實現這一功能。本文將詳細講解如何在Nginx中限制IP訪問&#xff0c;并通過示例代碼展示具體操作。 一、Nginx…

使用 Python 簡單幾步去除 PDF 水印

推薦一個AI網站&#xff0c;免費使用豆包AI模型&#xff0c;快去白嫖&#x1f449;海鯨AI 在處理 PDF 文件時&#xff0c;水印有時會影響文件的可讀性或美觀性。幸運的是&#xff0c;Python 提供了多種庫來操作 PDF 文件&#xff0c;其中 PyMuPDF&#xff08;又名 fitz&#xf…

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;想到的…

Nginx性能調優:深入剖析配置與調優技巧

一、引言 Nginx作為一款高性能的HTTP和反向代理服務器&#xff0c;已經被廣泛應用于各種Web服務中。然而&#xff0c;要想充分發揮Nginx的性能優勢&#xff0c;僅僅安裝和配置默認設置是遠遠不夠的。本文將深入剖析Nginx的配置與調優技巧&#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.需求 我們先看需求:小球在窗體內運動,撞到除…

從入門到精通:詳解Linux環境基礎開發工具的使用

前言 在這篇文章中&#xff0c;我將深入學習和理解Linux環境基礎開發工具的使用。無論你是初學者還是有一定經驗的開發者&#xff0c;相信這篇文章都會對你有所幫助。我們將詳細講解軟件包管理器、編輯器、編譯器、調試器、自動化構建工具以及版本控制工具的使用。 Linux軟件…

后端數據庫開發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;在通知方面帶來了諸多…

Rom應用開發遇到得一些小bug

記錄一些細碎得bug ROM時間類問題 問題描述&#xff1a; 設備拔電重啟&#xff0c;ROM時間為默認時間如1970年1月1日&#xff0c;與某些業務場景互斥 問題原因&#xff1a; 后臺接口校驗https證書校驗失敗&#xff0c;要求是2年內得請求頭校驗了時間戳&#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。…