promise是什么?
1、主要用于異步計(jì)算
2、可以將異步操作隊(duì)列化,按照期望的順序執(zhí)行,返回符合預(yù)期的結(jié)果
3、可以在對(duì)象之間傳遞和操作promise,幫助我們處理隊(duì)列
為什么會(huì)有promise?
為了避免界面凍結(jié)(任務(wù))
同步:假設(shè)你去了一家飯店,找個(gè)位置,叫來(lái)服務(wù)員,這個(gè)時(shí)候服務(wù)員對(duì)你說(shuō),對(duì)不起我是“同步”服務(wù)員,我要服務(wù)完這張桌子才能招呼你。那桌客人明明已經(jīng)吃上了,你只是想要個(gè)菜單,這么小的動(dòng)作,服務(wù)員卻要你等到別人的一個(gè)大動(dòng)作完成之后,才能再來(lái)招呼你,這個(gè)便是同步的問(wèn)題:也就是“順序交付的工作1234,必須按照1234的順序完成”。
異步:則是將耗時(shí)很長(zhǎng)的A交付的工作交給系統(tǒng)之后,就去繼續(xù)做B交付的工作,。等到系統(tǒng)完成了前面的工作之后,再通過(guò)回調(diào)或者事件,繼續(xù)做A剩下的工作。
AB工作的完成順序,和交付他們的時(shí)間順序無(wú)關(guān),所以叫“異步”。
異步操作的常見(jiàn)語(yǔ)法
事件監(jiān)聽(tīng)
document.getElementById('#start').addEventListener('click',start,false);functionstart(){// 響應(yīng)事件,進(jìn)行相應(yīng)的操作}// jquery on 監(jiān)聽(tīng)$('#start').on('click',start)
回調(diào)
// 比較常見(jiàn)的有ajax$.ajax('http://www.wyunfei.com/',{success(res){// 這里可以監(jiān)聽(tīng)res返回的數(shù)據(jù)做回調(diào)邏輯的處理}})// 或者在頁(yè)面加載完畢后回調(diào)$(function(){// 頁(yè)面結(jié)構(gòu)加載完成,做回調(diào)邏輯處理})
有了nodeJS之后...對(duì)異步的依賴進(jìn)一步加劇了
大家都知道在nodeJS出來(lái)之前PHP、Java、python等后臺(tái)語(yǔ)言已經(jīng)很成熟了,nodejs要想能夠有自己的一片天,那就得拿出點(diǎn)自己的絕活:
無(wú)阻塞高并發(fā),是nodeJS的招牌,要達(dá)到無(wú)阻塞高并發(fā)異步是其基本保障
舉例:查詢數(shù)據(jù)從數(shù)據(jù)庫(kù),PHP第一個(gè)任務(wù)查詢數(shù)據(jù),后面有了新任務(wù),那么后面任務(wù)會(huì)被掛起排隊(duì);而nodeJS是第一個(gè)任務(wù)掛起交給數(shù)據(jù)庫(kù)去跑,然后去接待第二個(gè)任務(wù)交給對(duì)應(yīng)的系統(tǒng)組件去處理掛起,接著去接待第三個(gè)任務(wù)...那這樣子的處理必然要依賴于異步操作
異步回調(diào)的問(wèn)題:
之前處理異步是通過(guò)純粹的回調(diào)函數(shù)的形式進(jìn)行處理
很容易進(jìn)入到回調(diào)地獄中,剝奪了函數(shù)return的能力
問(wèn)題可以解決,但是難以讀懂,維護(hù)困難
稍有不慎就會(huì)踏入回調(diào)地獄 - 嵌套層次深,不好維護(hù)

回調(diào)地獄
一般情況我們一次性調(diào)用API就可以完成請(qǐng)求。
有些情況需要多次調(diào)用服務(wù)器API,就會(huì)形成一個(gè)鏈?zhǔn)秸{(diào)用,比如為了完成一個(gè)功能,我們需要調(diào)用API1、API2、API3,依次按照順序進(jìn)行調(diào)用,這個(gè)時(shí)候就會(huì)出現(xiàn)回調(diào)地獄的問(wèn)題
promise
promise是一個(gè)對(duì)象,對(duì)象和函數(shù)的區(qū)別就是對(duì)象可以保存狀態(tài),函數(shù)不可以(閉包除外)
并未剝奪函數(shù)return的能力,因此無(wú)需層層傳遞callback,進(jìn)行回調(diào)獲取數(shù)據(jù)
代碼風(fēng)格,容易理解,便于維護(hù)
多個(gè)異步等待合并便于解決
promise詳解
newPromise(function(resolve,reject){// 一段耗時(shí)的異步操作resolve('成功')// 數(shù)據(jù)處理完成// reject('失敗') // 數(shù)據(jù)處理出錯(cuò)}).then((res)=>{console.log(res)},// 成功(err)=>{console.log(err)}// 失敗)
resolve作用是,將Promise對(duì)象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?resolved),在異步操作成功時(shí)調(diào)用,并將異步操作的結(jié)果,作為參數(shù)傳遞出去;
reject作用是,將Promise對(duì)象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected),在異步操作失敗時(shí)調(diào)用,并將異步操作報(bào)出的錯(cuò)誤,作為參數(shù)傳遞出去。
promise有三個(gè)狀態(tài):
1、pending[待定]初始狀態(tài)
2、fulfilled[實(shí)現(xiàn)]操作成功
3、rejected[被否決]操作失敗
當(dāng)promise狀態(tài)發(fā)生改變,就會(huì)觸發(fā)then()里的響應(yīng)函數(shù)處理后續(xù)步驟;
promise狀態(tài)一經(jīng)改變,不會(huì)再變。
Promise對(duì)象的狀態(tài)改變,只有兩種可能:
從pending變?yōu)閒ulfilled
從pending變?yōu)閞ejected。
這兩種情況只要發(fā)生,狀態(tài)就凝固了,不會(huì)再變了。
最簡(jiǎn)單示例:
newPromise(resolve=>{setTimeout(()=>{resolve('hello')},2000)}).then(res=>{console.log(res)})
分兩次,順序執(zhí)行
newPromise(resolve=>{setTimeout(()=>{resolve('hello')},2000)}).then(val=>{console.log(val)//? 參數(shù)val = 'hello'returnnewPromise(resolve=>{setTimeout(()=>{resolve('world')},2000)})}).then(val=>{console.log(val)// 參數(shù)val = 'world'})
promise完成后then()
letpro=newPromise(resolve=>{setTimeout(()=>{resolve('hello world')},2000)})setTimeout(()=>{pro.then(value=>{console.log(value)// hello world})},2000)
結(jié)論:promise作為隊(duì)列最為重要的特性,我們?cè)谌魏我粋€(gè)地方生成了一個(gè)promise隊(duì)列之后,我們可以把他作為一個(gè)變量傳遞到其他地方。
假如在.then()的函數(shù)里面不返回新的promise,會(huì)怎樣?
.then()
1、接收兩個(gè)函數(shù)作為參數(shù),分別代表fulfilled(成功)和rejected(失敗)
2、.then()返回一個(gè)新的Promise實(shí)例,所以它可以鏈?zhǔn)秸{(diào)用
3、當(dāng)前面的Promise狀態(tài)改變時(shí),.then()根據(jù)其最終狀態(tài),選擇特定的狀態(tài)響應(yīng)函數(shù)執(zhí)行
4、狀態(tài)響應(yīng)函數(shù)可以返回新的promise,或其他值,不返回值也可以我們可以認(rèn)為它返回了一個(gè)null;
5、如果返回新的promise,那么下一級(jí).then()會(huì)在新的promise狀態(tài)改變之后執(zhí)行
6、如果返回其他任何值,則會(huì)立即執(zhí)行下一級(jí).then()
.then()里面有.then()的情況
1、因?yàn)?then()返回的還是Promise實(shí)例
2、會(huì)等里面的then()執(zhí)行完,再執(zhí)行外面的

then嵌套
對(duì)于我們來(lái)說(shuō),此時(shí)最好將其展開(kāi),也是一樣的結(jié)果,而且會(huì)更好讀:

展開(kāi)增加可讀性
錯(cuò)誤處理
Promise會(huì)自動(dòng)捕獲內(nèi)部異常,并交給rejected響應(yīng)函數(shù)處理。
第一種錯(cuò)誤處理

第一種錯(cuò)誤處理
第二種錯(cuò)誤處理

第二種錯(cuò)誤處理
錯(cuò)誤處理兩種做法:
第一種:reject('錯(cuò)誤信息').then(() => {}, () => {錯(cuò)誤處理邏輯})
第二種:throw new Error('錯(cuò)誤信息').catch( () => {錯(cuò)誤處理邏輯})
推薦使用第二種方式,更加清晰好讀,并且可以捕獲前面所有的錯(cuò)誤(可以捕獲N個(gè)then回調(diào)錯(cuò)誤)
catch() + then()
第一種情況:

第一種情況

第一種情況 - 結(jié)果
結(jié)論:catch也會(huì)返回一個(gè)promise實(shí)例,并且是resolved狀態(tài)
第二種情況:

第二種情況

第二種情況結(jié)果
結(jié)論:拋出錯(cuò)誤變?yōu)閞ejected狀態(tài),所以繞過(guò)兩個(gè)then直接跑到最下面的catch
Promise.all() 批量執(zhí)行
Promise.all([p1, p2, p3])用于將多個(gè)promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例,返回的實(shí)例就是普通的promise
它接收一個(gè)數(shù)組作為參數(shù)
數(shù)組里可以是Promise對(duì)象,也可以是別的值,只有Promise會(huì)等待狀態(tài)改變
當(dāng)所有的子Promise都完成,該P(yáng)romise完成,返回值是全部值得數(shù)組
有任何一個(gè)失敗,該P(yáng)romise失敗,返回值是第一個(gè)失敗的子Promise結(jié)果
//切菜functioncutUp(){console.log('開(kāi)始切菜。');varp=newPromise(function(resolve,reject){//做一些異步操作setTimeout(function(){console.log('切菜完畢!');resolve('切好的菜');},1000);});returnp;}//燒水functionboil(){console.log('開(kāi)始燒水。');varp=newPromise(function(resolve,reject){//做一些異步操作setTimeout(function(){console.log('燒水完畢!');resolve('燒好的水');},1000);});returnp;}Promise.all([cutUp(),boil()]).then((result)=>{console.log('準(zhǔn)備工作完畢');console.log(result);})
Promise.race() 類似于Promise.all() ,區(qū)別在于它有任意一個(gè)完成就算完成
letp1=newPromise(resolve=>{setTimeout(()=>{resolve('I\`m p1 ')},1000)});letp2=newPromise(resolve=>{setTimeout(()=>{resolve('I\`m p2 ')},2000)});Promise.race([p1,p2]).then(value=>{console.log(value)})
常見(jiàn)用法:
異步操作和定時(shí)器放在一起,,如果定時(shí)器先觸發(fā),就認(rèn)為超時(shí),告知用戶;
例如我們要從遠(yuǎn)程的服務(wù)家在資源如果5000ms還沒(méi)有加載過(guò)來(lái)我們就告知用戶加載失敗
現(xiàn)實(shí)中的用法
回調(diào)包裝成Promise,他有兩個(gè)顯而易見(jiàn)的好處:
1、可讀性好
2、返回 的結(jié)果可以加入任何Promise隊(duì)列
實(shí)戰(zhàn)示例,回調(diào)地獄和promise對(duì)比:
/***
? 第一步:找到北京的id
? 第二步:根據(jù)北京的id -> 找到北京公司的id
? 第三步:根據(jù)北京公司的id -> 找到北京公司的詳情
? 目的:模擬鏈?zhǔn)秸{(diào)用、回調(diào)地獄
***/// 回調(diào)地獄// 請(qǐng)求第一個(gè)API: 地址在北京的公司的id$.ajax({url:'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/city',success(resCity){letfindCityId=resCity.filter(item=>{if(item.id=='c1'){returnitem}})[0].id? ? ? ? ? $.ajax({//? 請(qǐng)求第二個(gè)API: 根據(jù)上一個(gè)返回的在北京公司的id “findCityId”,找到北京公司的第一家公司的idurl:'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/position-list',success(resPosition){letfindPostionId=resPosition.filter(item=>{if(item.cityId==findCityId){returnitem}})[0].id// 請(qǐng)求第三個(gè)API: 根據(jù)上一個(gè)API的id(findPostionId)找到具體公司,然后返回公司詳情$.ajax({url:'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/company',success(resCom){letcomInfo=resCom.filter(item=>{if(findPostionId==item.id){returnitem}})[0]console.log(comInfo)}})}})}})
// Promise 寫(xiě)法// 第一步:獲取城市列表constcityList=newPromise((resolve,reject)=>{$.ajax({url:'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/city',success(res){resolve(res)}})})// 第二步:找到城市是北京的idcityList.then(res=>{letfindCityId=res.filter(item=>{if(item.id=='c1'){returnitem}})[0].idfindCompanyId().then(res=>{// 第三步(2):根據(jù)北京的id -> 找到北京公司的idletfindPostionId=res.filter(item=>{if(item.cityId==findCityId){returnitem}})[0].id// 第四步(2):傳入公司的idcompanyInfo(findPostionId)})})// 第三步(1):根據(jù)北京的id -> 找到北京公司的idfunctionfindCompanyId(){letaaa=newPromise((resolve,reject)=>{$.ajax({url:'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/position-list',success(res){resolve(res)}})})returnaaa}// 第四步:根據(jù)上一個(gè)API的id(findPostionId)找到具體公司,然后返回公司詳情functioncompanyInfo(id){letcompanyList=newPromise((resolve,reject)=>{$.ajax({url:'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/company',success(res){letcomInfo=res.filter(item=>{if(id==item.id){returnitem}})[0]console.log(comInfo)}})})}
作者:王云飛_小四_wyunfei
鏈接:http://www.itdecent.cn/p/1b63a13c2701
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。