歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~
Promise是CommonJS提出的一種規範,在ES6中已經原生支援Promise物件,非ES6環境可以用Bluebird等庫來支援。
0.引入
在js中任務的執行模型有兩種:同步模式和非同步模式。
同步模式:後一個任務B等待前一個任務A結束後,再執行。任務的執行順序和任務的排序順序是一致的。
非同步模式:每一個任務有一個或多個回撥函式,前一個任務A結束後,不是執行後一個任務B,而是執行任務A的回撥函式。而後一個任務B是不等任務A結束就執行。任務的執行順序,與任務的排序順序不一致。
非同步模式程式設計有四種方法:回撥函式(最基本的方法,把B寫成A的回撥函式)、事件監聽(為A繫結事件,當A發生某個事件,就執行B)、釋出/訂閱,以及本文要介紹的Promise物件。
Promise是一個用於處理非同步操作的物件,可以將回撥函式寫成鏈式呼叫的寫法,讓程式碼更優雅、流程更加清晰,讓我們可以更合理、更規範地進行非同步處理操作。它的思想是,每一個非同步任務返回一個Promise物件,該物件有一個then方法,允許指定回撥函式。
1.Promise的基本知識
1.1 三種狀態
Pending:進行中,剛建立一個Promise例項時,表示初始狀態;
resolved(fulfilled):resolve方法呼叫的時候,表示操作成功,已經完成;
Rejected:reject方法呼叫的時候,表示操作失敗;
1.2 兩個過程
這三種狀態只能從pendeng–>resolved(fulfilled),或者pending–>rejected,不能逆向轉換,也不能在resolved(fulfilled)和rejected之間轉換。並且一旦狀態改變,就不會再改變,會一直保持這個結果。
彙總上述,建立一個Promise的例項是這樣的:
//建立promise的例項
let promise = new Promise((resolve,reject)=>{
//剛建立例項時的狀態:pending
if(`非同步操作成功`){
//呼叫resolve方法,狀態從pending變為fulfilled
resolve();
}else{
//呼叫reject方法,狀態從pending變為rejected
reject();
}
});
1.3 then()
用於繫結處理操作後的處理程式,分別指定fulfilled狀態和rejected狀態的回撥函式,即它的引數是兩個函式,第一個用於處理操作成功後的業務,第二個用於處理操作失敗後的業務。
//then()
promise.then((res)=> {
//處理操作成功後的業務(即Promise物件的狀態變為fullfilled時呼叫)
},(error)=> {
//處理操作失敗後的業務(即Promise物件的狀態變為rejected時呼叫)
});
1.4 catch()
用於處理操作異常的程式,catch()只接受一個引數
//catch()
promise.catch((error)=> {
//處理操作失敗後的業務
});
一般來說,建議不要在then()裡面定義rejected狀態的回撥函式,而是將then()用於處理操作成功,將catch()用於處理操作異常。因為這樣做可以捕獲then()執行中的錯誤,也更接近同步中try/catch的寫法:
//try-catch
// bad
promise.then((res)=> {
//處理操作成功後的業務
}, (error)=> {
//處理操作失敗後的業務
});
// good
promise
.then((res)=> {
//處理操作成功後的業務
})
.catch((error)=> {
//處理操作失敗後的業務
});
1.5 all()
接受一個陣列作為引數,陣列的元素是Promise例項物件。只有當引數中的例項物件的狀態都為fulfilled時,Promise.all( )才會有返回。
例項程式碼(可直接在瀏覽器中開啟):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Promise例項</title>
<style type="text/css"></style>
<script type="text/javascript">
window.onload = () => {
//建立例項promise1
let promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve(`promise1操作成功`);
console.log(`1`)
}, 3000);
});
//建立例項promise1
let promise2 = new Promise((resolve) => {
setTimeout(() => {
resolve(`promise1操作成功`);
console.log(`2`)
}, 1000);
});
Promise.all([promise1, promise2]).then((result) => {
console.log(result);
});
}
</script>
</head>
<body>
<div></div>
</body>
</html>
結果(注意看時間):
Promise.all()
程式碼說明:
1s後,promise2進入fulfilled狀態,間隔2s,也就是3s後,promise1也進入fulfilled狀態。這時,由於兩個例項都進入了fulfilled狀態,所以Promise.all()才進入了then方法。
使用場景:執行某個操作需要依賴多個介面請求回的資料,且這些介面之間不存在互相依賴的關係。這時使用Promise.all(),等到所有介面都請求成功了,它才會進行操作。
1.6 race()
和all()的引數一樣,引數中的promise例項,只要有一個狀態發生變化(不管是成功fulfilled還是異常rejected),它就會有返回,其他例項中再發生變化,它也不管了。
例項程式碼(可直接在瀏覽器中開啟):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Promise例項</title>
<style type="text/css"></style>
<script type="text/javascript">
window.onload = () => {
//建立例項promise1
let promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve(`promise1操作成功`);
console.log(`1`)
}, 3000);
});
//建立例項promise1
let promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(`promise1操作失敗`);
console.log(`2`)
}, 1000);
});
Promise.race([promise1, promise2])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
})
}
</script>
</head>
<body>
<div></div>
</body>
</html>
結果(注意看時間):
Promise.race()
程式碼說明:
1s後,promise2進入rejected狀態,由於一個例項的狀態發生了變化,所以Promise.race()就立刻執行了。
2 例項
平時開發中可能經常會遇到的問題是,要用ajax進行多次請求。例如現在有三個請求,請求A、請求B、請求C。請求C要將請求B的請求回來的資料做為引數,請求B要將請求A的請求回來的資料做為引數。
按照這個思路,我們可能會直接寫出這樣的層層巢狀的程式碼:
//------請求A 開始---------
$.ajax({
success:function(res1){
//------請求B 開始----
$.ajax({
success:function(res2){
//----請求C 開始---
$.ajax({
success:function(res3){
}
});
//---請求C 結束---
}
});
//------請求B 結束-----
}
});
//------請求A 結束---------
在請求A的success後,請求B傳送請求,在請求B 的success後,請求C傳送請求。請求C結束後,再向上到請求B結束,請求B結束後,再向上到請求A結束。
這樣雖然可以完成任務,但是程式碼層層巢狀,程式碼可讀性差,也不便於除錯和後續的程式碼維護。而如果用Promise,你可以這樣寫(示意程式碼,無ajax請求):
此處附上完整可執行程式碼,可在瀏覽器的控制檯中檢視執行結果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Promise例項</title>
<style type="text/css"></style>
<script type="text/javascript">
window.onload = () => {
let promise = new Promise((resolve, reject) => {
if (true) {
//呼叫操作成功方法
resolve(`操作成功`);
} else {
//呼叫操作異常方法
reject(`操作異常`);
}
});
//then處理操作成功,catch處理操作異常
promise.then(requestA)
.then(requestB)
.then(requestC)
.catch(requestError);
function requestA() {
console.log(`請求A成功`);
return `下一個是請求B`;
}
function requestB(res) {
console.log(`上一步的結果:` + res);
console.log(`請求B成功`);
return `下一個是請求C`;
}
function requestC(res) {
console.log(`上一步的結果:` + res);
console.log(`請求C成功`);
}
function requestError() {
console.log(`請求失敗`);
}
}
</script>
</head>
<body>
<div></div>
</body>
</html>
結果如下:
例項
可以看出請求C依賴請求B的結果,請求B依賴請求A的結果,在請求A中是使用了return將需要的資料返回,傳遞給下一個then()中的請求B,實現了引數的傳遞。同理,請求B中也是用了return,將引數傳遞給了請求C。
3.小結
本文主要介紹了Promise物件的三個狀態和兩個過程。“三個狀態”是:初始化、操作成功、操作異常,“兩個過程”是初始化狀態到操作成功狀態,和初始化狀態到操作異常狀態。除此之前,還有兩種例項方法:then()、catch()來繫結處理程式。類方法:Promise.all()、Promise.race()。如有問題,歡迎指正。
此文已由作者授權騰訊雲+社群釋出,更多原文請點選
搜尋關注公眾號「雲加社群」,第一時間獲取技術乾貨,關注後回覆1024 送你一份技術課程大禮包!
海量技術實踐經驗,盡在雲加社群!