- 作者:Leo曉華
回撥地獄
由於 JavaScript 的單執行緒性質,我們必須等待上一個請求返回結果後才能處理下一步,如下:
$.ajax({
url: "/step1",
success: function(){
$.ajax({
url: "/step2",
success: function(){
$.ajax({
url: "/step3",
success: function(){
}
});
}
});
}
});
複製程式碼
這種回撥地獄巢狀層級多了,程式碼結構就容易變得很不直觀,可讀性比較差。
Promise 是 ES6原生支援的,把原來巢狀的回撥改為了級聯的方式。
瞭解Promise
Promise 可以簡單理解為一個事務,這個事務存在三種狀態:
![ES6 Promise介紹](https://i.iter01.com/images/0c7c4ff2320d90901607d6a4a19bb8b4bc17864aeae4c15fb81b8c114f805f09.png)
- 已經完成了 resolved(完成態)
- 因為某種原因被中斷了 rejected(失敗態)
- 初始狀態 pending(未完成)
注意,這種狀態的改變只會出現從未完成態向完成態或失敗態轉化,不能逆反。完成態和失敗態不能互相轉化,而且,狀態一旦轉化,將不能更改。
只有非同步操作的結果可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思是承諾,表示其他手段無法改變。
例子:
var p = new Promise(function (resolve, reject) {
if(/* 非同步操作成功 */){
resolve(ret);
} else {
reject(error);
}
});
複製程式碼
在宣告一個Promise物件例項時,我們傳入的匿名函式引數中:
resolve 就對應著完成態之後的操作
reject 對應著失敗態之後的操作
Promise的then方法
p.then(function (value) {
// 完成態,value是上面resolve傳入的值
}, function (error) {
// 失敗態,error是上面reject傳入的值
});
複製程式碼
then()方法傳遞的兩個引數中:
-
第一個引數(函式)對應著完成態的操作,也就是resolve時呼叫
-
第二個引數(函式)對應著失敗態的操作,也就是reject時呼叫
-
第二個引數可以沒有
多個promise鏈式
例子1:
var p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p1'), 1000);
});
p1.then( ret => {
console.log(ret); //p1
return'then1';
}).then( ret => {
console.log(ret); //then1
return'then2';
}).then( ret => {
console.log(ret); //then2
});
複製程式碼
在 resolve 之前,promise 的每一個 then 都會將回撥函式壓入佇列,resolve 後,將 resolve 的值送給佇列的第一個函式,第一個函式執行完畢後,將執行結果再送入下一個函式,依次執行完佇列。一連串下來,一氣呵成,沒有絲毫間斷。
鏈式中的then方法(第二個開始),它們的resolve中的引數是什麼?答案就是前一個then()中resolve的return語句的返回值。
例子2:
step1:function(){
var promise = new Promise(function(resolve,reject){
$.ajax({
url:"/step1",
success:function(data){
resolve(data)//在非同步操作成功時呼叫
}
});
})
return promise;
},
step2:function(val){
var promise = new Promise(function(resolve,reject){
$.ajax({
url:"/step2",
data:val, //來自step1的引數
success:function(data){
resolve(data)//在非同步操作成功時呼叫
}
});
})
return promise;
}
step1()
.then(data => return step2(data)) //step1的結果傳給step2作為引數
.then(data => console.log(data))
複製程式碼
錯誤處理
方法1:由then的第二個處理函式處理錯誤
var p = new Promise(function (resolve, reject) {
// ...
if(/* 非同步操作成功 */){
resolve(ret);
} else {
reject(error);
}
});
p.then(function (value) {
// 完成態
}, function (error) {
// 失敗態
});
複製程式碼
方法2:鏈式錯誤處理 (catch 方法)
var p = new Promise(function (resolve, reject) {
// ...
if(/* 非同步操作成功 */){
resolve(ret);
} else {
reject(error);
}
});
p.then(function (value) {
// 完成態
}).then(function (value) {
// 完成態
}).catch( err => {// 可以捕抓到前面的出現的錯誤。
console.log(err.toString());
});
複製程式碼
如果第一個then報錯,第二個then不會執行。
一個活動專案的例子:
//查使用者資訊
querymystate(){
var promise = new Promise(function(resolve,reject){
utils.get('/querystate', {
raffle_code:xxx,
}, (rst) => {
if (rst.return_code === 0) { //成功獲取使用者資訊
resolve(rst.data);
} else { //獲取失敗
reject(rst.error_message);
}
})
})
return promise;
},
//我要領獎,必須先獲取使用者資訊
getAward(){
querymystate()
.then(data => {
//領獎處理
}).catch(ErrMsg => {
//彈出錯誤提示ErrMsg
})
}
複製程式碼
Promise.all()
Promise.all()方法用於將多個Promise例項,包裝成一個新的Promise例項,例如:
var p = Promise.all([p1, p2, p3]);
複製程式碼
新的Promise例項p的狀態由p1, p2, p3決定:
-
當p1, p2, p3的狀態都為完成態時,p為完成態。
-
p1, p2, p3中任一一個狀態為失敗態,則p為失敗態。
例子:
let a = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 2000)
})
let b = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3)
}, 2000)
})
Promise.all([a, b])
.then( (ret) => console.log(ret)) //2秒後,注意這裡返回的是陣列 [2,3]
.catch( err => console.log(err.toString()));
複製程式碼
Promise.race()
race意思是賽跑,看誰先到。只要p1, p2, p3中任意一個例項率先改變狀態,則p的狀態就跟著改變,而且狀態由率先改變的例項決定。
let a = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 3000)
})
let b = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3)
}, 2000)
})
Promise.race([a, b])
.then( (ret) => console.log(ret)) //2秒後顯示3
.catch( err => console.log(err.toString()));
複製程式碼