寫(xiě)在前面
假設(shè)現(xiàn)在一個(gè)日常開(kāi)發(fā)會(huì)遇到這樣一個(gè)需求:多個(gè)接口異步請(qǐng)求嘴高,第二個(gè)接口依賴于第一個(gè)接口執(zhí)行完畢之后才能利用數(shù)據(jù)進(jìn)行一系列操作。一般會(huì)這樣寫(xiě):
A.fetchData({
url: 'http://......',
success: function (data) {
A.fetchData({
// 要在第一個(gè)請(qǐng)求成功后才可以執(zhí)行下一步
url: 'http://......',
success: function (data) {
// ......
}
});
}
});
這樣寫(xiě)沒(méi)問(wèn)題舔痪,但是有兩個(gè)缺點(diǎn):
1、當(dāng)有多個(gè)操作的時(shí)候,會(huì)導(dǎo)致多個(gè)回調(diào)函數(shù)嵌套付秕,不夠美觀
2、如果有幾個(gè)操作沒(méi)有前后順序之分時(shí)侍郭,例如上面的后一個(gè)請(qǐng)求不依賴與前一個(gè)請(qǐng)求的返回結(jié)果的時(shí)候询吴,同樣也需要等待上一個(gè)操作完成再實(shí)行下一個(gè)操作。
從ES6開(kāi)始亮元,Promise對(duì)象可以解決上述問(wèn)題猛计。
什么是Promise對(duì)象
一個(gè)Promise對(duì)象可以理解為一次將要執(zhí)行的操作,使用了Promise對(duì)象之后可以用一種鏈?zhǔn)秸{(diào)用的方式來(lái)組織代碼爆捞,讓代碼更加直觀奉瘤。
resolve和reject
先看代碼:
function helloWorld (ready) {
return new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello World!");
} else {
reject("Good bye!");
}
});
}
helloWorld(true).then(function (message) {
alert(message);
}, function (error) {
alert(error);
});
上面的代碼實(shí)現(xiàn)的功能非常簡(jiǎn)單,helloWord 函數(shù)接受一個(gè)參數(shù)煮甥,如果為 true 就打印 "Hello World!"盗温,如果為 false 就打印錯(cuò)誤的信息。helloWord 函數(shù)返回的是一個(gè) Promise 對(duì)象成肘。
在 Promise 對(duì)象當(dāng)中有兩個(gè)重要方法————resolve 和 reject卖局。
resolve 方法可以使 Promise 對(duì)象的狀態(tài)改變成成功,同時(shí)傳遞一個(gè)參數(shù)用于后續(xù)成功后的操作双霍,在這個(gè)例子當(dāng)中就是 Hello World! 字符串砚偶。
reject 方法則是將 Promise 對(duì)象的狀態(tài)改變?yōu)槭。瑫r(shí)將錯(cuò)誤的信息傳遞到后續(xù)錯(cuò)誤處理的操作洒闸。
then
Promise 對(duì)象有三種狀態(tài):
1.Fulfilled 可以理解為成功的狀態(tài)
2.Rejected 可以理解為失敗的狀態(tài)
3.Pending 既不是 Fulfilld 也不是 Rejected 的狀態(tài)染坯,可以理解為 Promise 對(duì)象實(shí)例創(chuàng)建時(shí)候的初始狀態(tài)。
helloWorld 的例子中的 then方法就是根據(jù) Promise 對(duì)象的狀態(tài)來(lái)確定執(zhí)行的操作丘逸,resolve 時(shí)執(zhí)行第一個(gè)函數(shù)(onFulfilled)单鹿,reject 時(shí)執(zhí)行第二個(gè)函數(shù)(onRejected)。promise模式在任何時(shí)刻都處于以下三種狀態(tài)之一:未完成(unfulfilled)深纲、已完成(resolved)和拒絕(rejected)仲锄。以CommonJS Promise/A 標(biāo)準(zhǔn)為例,promise對(duì)象上的then方法負(fù)責(zé)添加針對(duì)已完成和拒絕狀態(tài)下的處理函數(shù)囤萤。then方法會(huì)返回另一個(gè)promise對(duì)象昼窗,以便于形成promise管道,這種返回promise對(duì)象的方式能夠支持開(kāi)發(fā)人員把異步操作串聯(lián)起來(lái)涛舍,如then(resolvedHandler, rejectedHandler); 澄惊。resolvedHandler 回調(diào)函數(shù)在promise對(duì)象進(jìn)入完成狀態(tài)時(shí)會(huì)觸發(fā),并傳遞結(jié)果;rejectedHandler函數(shù)會(huì)在拒絕狀態(tài)下調(diào)用掸驱。
示例代碼1:
function printHello (ready) {
var promise = new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello");
} else {
reject("Good bye");
}
});
return promise;
}
function printWorld () {
console.log('World');
}
function printExclamation () {
console.log('!!!');
}
printHello(true)
.then(function(message)
.then(printWorld)
.then(printExclamation)
.catch(function(error){
console.log(error);
});;
函數(shù)先執(zhí)行printHello肛搬,返回一個(gè)promise對(duì)象,通過(guò)then將異步操作串聯(lián)起來(lái)毕贼。
執(zhí)行結(jié)果應(yīng)該是 Hello
World
!!!
示例代碼2:
function helloWorld (ready) {
return new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello World!");
} else {
reject("Good bye!");
}
});
}
var _this = this;
printHello(true)
.then(function (message) {
var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
return fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
var movie = responseData.movies[1];
console.log('data = ', movie.title);
return movie.title;
})
},function (error) {
return(error);
}).then(function (message) {
return message + ' World';
}).then(function (message) {
return message + '!!!';
}).then(function (message) {
console.log(message);
console.log('finally');
}).catch(function(error){
console.log(error);
});
上面的代碼中有兩個(gè)promise温赔,第一個(gè)promise執(zhí)行完畢后也就是printHello之后,會(huì)執(zhí)行下一個(gè)then,這個(gè)then返回了一個(gè)獲取數(shù)據(jù)的promise鬼癣,后面的then拿到的promise都是指向這個(gè)promise對(duì)象的陶贼。上述例子通過(guò)鏈?zhǔn)秸{(diào)用的方式,按順序打印出了相應(yīng)的內(nèi)容待秃。then 可以使用鏈?zhǔn)秸{(diào)用的寫(xiě)法原因在于拜秧,每一次執(zhí)行該方法時(shí)總是會(huì)返回一個(gè) Promise 對(duì)象。另外章郁,在 then onFulfilled 的函數(shù)當(dāng)中的返回值枉氮,可以作為后續(xù)操作的參數(shù)。
catch
catch 方法是 then(onFulfilled, onRejected) 方法當(dāng)中 onRejected 函數(shù)的一個(gè)簡(jiǎn)單的寫(xiě)法暖庄,也就是說(shuō)可以寫(xiě)成 then(fn).catch(fn)聊替,相當(dāng)于 then(fn).then(null, fn)。使用 catch 的寫(xiě)法比一般的寫(xiě)法更加清晰明確培廓。
新手使用容易犯的錯(cuò)誤
1.忘記添加catch()方法
這是一個(gè)很常見(jiàn)的錯(cuò)誤惹悄。很多程序員對(duì)他們代碼中的promise調(diào)用十分自信,覺(jué)得代碼永遠(yuǎn)不會(huì)拋出一個(gè) error 医舆,也可能他們只是簡(jiǎn)單的忘了加 catch() 方法俘侠。不幸的是象缀,不加 catch() 方法會(huì)讓回調(diào)函數(shù)中拋出的異常被吞噬蔬将,在你的控制臺(tái)是看不到相應(yīng)的錯(cuò)誤的,這對(duì)調(diào)試來(lái)說(shuō)是非常痛苦的央星。
為了避免這種糟糕的情況霞怀,我已經(jīng)養(yǎng)成了在自己的promise調(diào)用鏈最后添加如下代碼的習(xí)慣:
somePromise().then(function () {
return aPromise();
}).then(function () {
return anotherPromise();
}).catch(function(error){
console.log(error);
});
即使你并不打算在代碼中處理異常,在代碼中添加 catch() 也是一個(gè)謹(jǐn)慎的編程風(fēng)格的體現(xiàn)莉给。在某種情況下你原先的假設(shè)出錯(cuò)的時(shí)候毙石,這會(huì)讓你的調(diào)試工作輕松一些。
2.return的混淆亂用
在then方法內(nèi)部颓遏,我們可以做三件事:
1.return 一個(gè)promise對(duì)象
2.return一個(gè)同步的值或者是 undefined
3.同步的 throw 一個(gè)錯(cuò)誤
理解這三種情況之后徐矩,你就會(huì)理解promise了。
1.返回另一個(gè)promise對(duì)象
在有關(guān)promise的相關(guān)文章中叁幢,這種寫(xiě)法很常見(jiàn)滤灯,就像上文提到的構(gòu)成promise鏈的一段代碼:
getUserByName('nolan').then(function (user) {
return fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
}
}).then(function () {
});
2.返回一個(gè)具體的值或者是 undefined
getUserByName('nolan').then(fcuntion (user) {
if (inMemoryCache[user.id]) {
return inMemoryCache[user.id];
// returning a value!
}
return inMemoryCache[user.id];
// returning a promise
}).then(function (userAccount) {
// I got a user account
})
如果不調(diào)用return語(yǔ)句的話,javaScript里的函數(shù)會(huì)返回 undefined 。這也就意味著在你想返回一些值的時(shí)候鳞骤,不顯式調(diào)用return會(huì)產(chǎn)生一些副作用窒百。
出于上述原因,養(yǎng)成了一個(gè)個(gè)人習(xí)慣就是在then方法內(nèi)部永遠(yuǎn)顯式的調(diào)用return或者throw豫尽。我也推薦你這樣做篙梢。
3.拋出一個(gè)錯(cuò)誤
說(shuō)到throw,這又體現(xiàn)了promise的功能強(qiáng)大美旧。在用戶退出的情況下渤滞,我們的代碼中會(huì)采用拋出異常的方式進(jìn)行處理:
getUserByName('nolan').then(function (user) {
if (user.isLoggedOut()) {
throw new Error('user logged out!');
// throwing a error!
}
if (inMemoryCache[user.id]) {
return inMemoryCache[user.id];
// returning a value!
}
return getUserAccountById(user.id);
// returning a promise!
}).then(function (userAccount) {
// I got a user account!
}).catch(function (err) {
// Boo, I got an error!
});
如果用戶已經(jīng)登出的話, catch() 會(huì)收到一個(gè)錯(cuò)誤榴嗅,如果有promise對(duì)象的狀態(tài)變?yōu)閞ejected的話蔼水,它還會(huì)收到一個(gè)錯(cuò)誤。
在使用promise的時(shí)候拋出異常在開(kāi)發(fā)階段很有用录肯,它能幫助我們定位代碼中的錯(cuò)誤趴腋。比方說(shuō),在then函數(shù)內(nèi)部調(diào)用 JSON.parse() 论咏,如果JSON對(duì)象不合法的話优炬,可能會(huì)拋出異常,在回調(diào)函數(shù)中厅贪,這個(gè)異常會(huì)被吞噬蠢护,但是在使用promise之后,我們就可以捕獲到這個(gè)異常了养涮。
微博賬號(hào):梅嘉慶(點(diǎn)擊關(guān)注)