首先說一下promise 是什么?
1、本質(zhì)是構(gòu)造函數(shù)中主要用于異步計(jì)算
2、可以將異步操作隊(duì)列化,按照期望的順序執(zhí)行,返回符合預(yù)期的結(jié)果
其次主要處理異步和操作同步化
promise 有三個(gè)狀態(tài):
1、pending[待定]初始狀態(tài)
2、fulfilled[實(shí)現(xiàn)]操作成功
3、rejected[被否決]操作失敗
最后我們通過new 創(chuàng)建promise,而實(shí)例化之后的promise對(duì)象的參數(shù)是一個(gè)回調(diào)函數(shù),回調(diào)函數(shù)有兩個(gè)參數(shù),分別為成功回調(diào) resolve 和失敗回調(diào) reject ,通過原型上 then() 方法來接收成功之后的數(shù)據(jù)
異步操作的常見語法
????????let str =?new?Promise(function(resolve,?reject)?{
????????????//做一些異步操作
????????????setTimeout(function()?{
????????????????console.log('執(zhí)行完成Promise');
????????????????resolve("");? ?//? 要返回的數(shù)據(jù)可以任何數(shù)據(jù)例如接口返回?cái)?shù)據(jù)
????????????},?2000);
????????})
console.log(str);
事件監(jiān)聽
document.getElementById('#start').addEventListener('click', start, false);
function start() {
? // 響應(yīng)事件,進(jìn)行相應(yīng)的操作
}
// jquery on 監(jiān)聽
$('#start').on('click', start)
回調(diào)
// 比較常見的有ajax
$.ajax('http://www.wyunfei.com/', {
success (res) {
? // 這里可以監(jiān)聽res返回的數(shù)據(jù)做回調(diào)邏輯的處理
}
})
// 或者在頁面加載完畢后回調(diào)
$(function() {
// 頁面結(jié)構(gòu)加載完成,做回調(diào)邏輯處理
})
異步回調(diào)的問題:
之前處理異步是通過純粹的回調(diào)函數(shù)的形式進(jìn)行處理
很容易進(jìn)入到回調(diào)地獄中,剝奪了函數(shù)return的能力
問題可以解決,但是難以讀懂,維護(hù)困難
稍有不慎就會(huì)踏入回調(diào)地獄 - 嵌套層次深,不好維護(hù)
need-to-insert-img
回調(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)地獄的問題
promise
promise是一個(gè)對(duì)象,對(duì)象和函數(shù)的區(qū)別就是對(duì)象可以保存狀態(tài),函數(shù)不可以(閉包除外)
并未剝奪函數(shù)return的能力,因此無需層層傳遞callback,進(jìn)行回調(diào)獲取數(shù)據(jù)
代碼風(fēng)格,容易理解,便于維護(hù)
多個(gè)異步等待合并便于解決
promise詳解
new Promise(
? 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、當(dāng)promise狀態(tài)發(fā)生改變,就會(huì)觸發(fā)then()里的響應(yīng)函數(shù)處理后續(xù)步驟;
2、promise狀態(tài)一經(jīng)改變,不會(huì)再變。
3、Promise對(duì)象的狀態(tài)改變,只有兩種可能:
? ? ? 從pending變?yōu)閒ulfilled
? ? ? 從pending變?yōu)閞ejected。
這兩種情況只要發(fā)生,狀態(tài)就凝固了,不會(huì)再變了。
最簡(jiǎn)單示例:
new Promise(resolve => {
? setTimeout(() => {
? ? resolve('jianyan')
? }, 2000)
}).then(res => {
? console.log(res)
})
分兩次,順序執(zhí)行
new Promise(resolve => {
? ? setTimeout(() => {
? ? ? resolve('jianyan')
? ? }, 2000)
? }).then(val => {
? ? console.log(val) //? 參數(shù)val = 'hello'
? ? return new Promise(resolve => {
? ? ? setTimeout(() => {
? ? ? ? resolve('mochen')
? ? ? }, 2000)
? ? })
? }).then(val => {
? ? console.log(val) // 參數(shù)val = 'world'
? })
promise完成后then()
let pro = new Promise(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(失?。?/p>
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í)行外面的
need-to-insert-img
then嵌套
對(duì)于我們來說,此時(shí)最好將其展開,也是一樣的結(jié)果,而且會(huì)更好讀:
need-to-insert-img
展開增加可讀性
錯(cuò)誤處理
Promise會(huì)自動(dòng)捕獲內(nèi)部異常,并交給rejected響應(yīng)函數(shù)處理。
第一種錯(cuò)誤處理
need-to-insert-img
第一種錯(cuò)誤處理
第二種錯(cuò)誤處理
need-to-insert-img
第二種錯(cuò)誤處理
錯(cuò)誤處理兩種做法:
第一種:reject('錯(cuò)誤信息').then(() => {}, () => {錯(cuò)誤處理邏輯})
第二種:throw new Error('錯(cuò)誤信息').catch( () => {錯(cuò)誤處理邏輯})
推薦使用第二種方式,更加清晰好讀,并且可以捕獲前面所有的錯(cuò)誤(可以捕獲N個(gè)then回調(diào)錯(cuò)誤)
catch() + then()
第一種情況:
need-to-insert-img
第一種情況
need-to-insert-img
第一種情況 - 結(jié)果
結(jié)論:catch也會(huì)返回一個(gè)promise實(shí)例,并且是resolved狀態(tài)
第二種情況:
need-to-insert-img
第二種情況
need-to-insert-img
第二種情況結(jié)果
結(jié)論:拋出錯(cuò)誤變?yōu)閞ejected狀態(tài),所以繞過兩個(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é)果
//切菜
? ? function cutUp(){
? ? ? ? console.log('開始切菜。');
? ? ? ? var p = new Promise(function(resolve, reject){? ? ? ? //做一些異步操作
? ? ? ? ? ? setTimeout(function(){
? ? ? ? ? ? ? ? console.log('切菜完畢!');
? ? ? ? ? ? ? ? resolve('切好的菜');
? ? ? ? ? ? }, 1000);
? ? ? ? });
? ? ? ? return p;
? ? }
? ? //燒水
? ? function boil(){
? ? ? ? console.log('開始燒水。');
? ? ? ? var p = new Promise(function(resolve, reject){? ? ? ? //做一些異步操作
? ? ? ? ? ? setTimeout(function(){
? ? ? ? ? ? ? ? console.log('燒水完畢!');
? ? ? ? ? ? ? ? resolve('燒好的水');
? ? ? ? ? ? }, 1000);
? ? ? ? });
? ? ? ? return p;
? ? }
? ? Promise.all([cutUp(), boil()])
? ? ? ? .then((result) => {
? ? ? ? ? ? console.log('準(zhǔn)備工作完畢');
? ? ? ? ? ? console.log(result);
? ? ? ? })
Promise.race() 類似于Promise.all() ,區(qū)別在于它有任意一個(gè)完成就算完成
let p1 = new Promise(resolve => {
? ? ? ? setTimeout(() => {
? ? ? ? ? ? resolve('I\`m p1 ')
? ? ? ? }, 1000)
? ? });
? ? let p2 = new Promise(resolve => {
? ? ? ? setTimeout(() => {
? ? ? ? ? ? resolve('I\`m p2 ')
? ? ? ? }, 2000)
? ? });
? ? Promise.race([p1, p2])
? ? ? ? .then(value => {
? ? ? ? ? ? console.log(value)
? ? ? ? })
常見用法:
異步操作和定時(shí)器放在一起,,如果定時(shí)器先觸發(fā),就認(rèn)為超時(shí),告知用戶;
例如我們要從遠(yuǎn)程的服務(wù)家在資源如果5000ms還沒有加載過來我們就告知用戶加載失敗
現(xiàn)實(shí)中的用法
回調(diào)包裝成Promise,他有兩個(gè)顯而易見的好處:
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) {
? ? let findCityId = resCity.filter(item => {
? ? ? if (item.id == 'c1') {
? ? ? ? return item
? ? ? }
? ? })[0].id
? ? $.ajax({
? ? ? //? 請(qǐng)求第二個(gè)API: 根據(jù)上一個(gè)返回的在北京公司的id “findCityId”,找到北京公司的第一家公司的id
? ? ? url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/position-list',
? ? ? success (resPosition) {
? ? ? ? let findPostionId = resPosition.filter(item => {
? ? ? ? ? if(item.cityId == findCityId) {
? ? ? ? ? ? return item
? ? ? ? ? }
? ? ? ? })[0].id
? ? ? ? // 請(qǐng)求第三個(gè)API: 根據(jù)上一個(gè)API的id(findPostionId)找到具體公司,然后返回公司詳情
? ? ? ? $.ajax({
? ? ? ? ? url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/company',
? ? ? ? ? success (resCom) {
? ? ? ? ? ? let comInfo = resCom.filter(item => {
? ? ? ? ? ? ? if (findPostionId == item.id) {
? ? ? ? ? ? ? ? return item
? ? ? ? ? ? ? }
? ? ? ? ? ? })[0]
? ? ? ? ? ? console.log(comInfo)
? ? ? ? ? }
? ? ? ? })
? ? ? }
? ? })
? }
})