1、異步編程和回調(diào)函數(shù)
網(wǎng)絡(luò)數(shù)據(jù)傳輸和磁盤讀寫等操作是十分耗時(shí)的,JavaScript引擎會(huì)把這些耗時(shí)的操作陷入其他線程,從而讓主線程能夠一馬平川地跑下去,瀏覽器也不會(huì)因?yàn)槟_本等待耗時(shí)操作的相應(yīng)而卡死。
這對(duì)用戶很好,但對(duì)程序員來說就不那么友好了,因?yàn)楫惒紸PI都是通過接收回調(diào)函數(shù)來把異步操作的處理結(jié)果導(dǎo)出的。
舉個(gè)栗子:node.js的https類的get方法:
const https = require('https');
https.get('https://encrypted.google.com/', (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
});
get方法接收的第一個(gè)參數(shù)是一個(gè)url,node會(huì)向這個(gè)url發(fā)送一個(gè)https請(qǐng)求,并生成一個(gè)response對(duì)象。
第二個(gè)參數(shù)就是所謂的回調(diào)函數(shù),開發(fā)者可以通過定義這個(gè)函數(shù)來接收get方法產(chǎn)生的response對(duì)象,get函數(shù)的定義大概是這樣的:
get(url, callback) {
// do something 異步操作, 生成res對(duì)象
callback(res);// 在生成了response對(duì)象后將這個(gè)對(duì)象傳入回調(diào)函數(shù)
}
如果后面的代碼需要用到response,那么這些代碼都需要寫進(jìn)這個(gè)回調(diào)函數(shù)里,這樣做會(huì)使代碼看起來很難看。
尤其是當(dāng)回調(diào)函數(shù)體內(nèi)還有異步操作要執(zhí)行時(shí),會(huì)出現(xiàn)回調(diào)套回調(diào)的情況,對(duì)邏輯比較復(fù)雜的業(yè)務(wù)甚至?xí)霈F(xiàn)七八個(gè)回調(diào)函數(shù)套在一起的情況,圈內(nèi)管這種情況叫“回調(diào)地獄”,會(huì)嚴(yán)重影響代碼的可讀性和可維護(hù)性。
2、Promise
如果你讀到這,相信你已經(jīng)理解了異步和回調(diào)的概念,Promise為我們提供了一種異步編程的優(yōu)雅寫法。異步編程給我們帶來的困擾在于,我們無法立刻得到異步操作的結(jié)果,只能把大量的業(yè)務(wù)代碼套進(jìn)回調(diào)函數(shù)里。
而使用Promise,我們可以立刻得到異步操作的結(jié)果,或者,更準(zhǔn)確地說,我們可以立刻得到異步操作的狀態(tài),然后在異步操作完成時(shí)獲得它的結(jié)果。
2.1 什么是Promise?
在回答這個(gè)問題之前,首先來看看我們?cè)趺词褂肞romise
var p = new Promise((resolve,reject) => {
console.log(resolve, reject)
}) // ? () { [native code] } ? () { [native code] }
可以看到,首先,我們使用new關(guān)鍵字創(chuàng)建Promise對(duì)象,這意味著Promise是JavaScript的一個(gè)內(nèi)置的類,這個(gè)類的構(gòu)造函數(shù)接收一個(gè)函數(shù),并為該函數(shù)提供兩個(gè)參數(shù),resolve和reject,這兩個(gè)參數(shù)是JavaScript的原生函數(shù)。
然后,讓我們來打印一下這個(gè)Promise類的實(shí)例p,來看看它長什么樣:
console.log(p);
// __proto__: Promise
// [[PromiseStatus]]: "pending"
// [[PromiseValue]]: undefined
我們可以看到三條信息:
1、這個(gè)實(shí)例繼承自Promise類(廢話)
2、PromiseStatus 屬性的值為 pending
3、PromiseValue 屬性為undefined
其中,PromiseStatus 是promise的狀態(tài),狀態(tài)可以有兩種情況:
1、一種是上面這種情況,pending,表明promise還沒有得到任何結(jié)果,處于等待狀態(tài)
2、另一種是settled,promise已經(jīng)得到結(jié)果,而settled又有兩種情況:solved和rejected,
那么是什么決定了promise的狀態(tài)呢? PromiseValue又是什么東西呢?看下面這段代碼:
var p1 = new Promise((resolve,reject) => {
resolve("hello, Promise");
});
var p2 = new Promise((resolve,reject) => {
reject("bye, Promise");
});
console.log("p1: ", p1);
console.log("p2: ", p2);
// p1:
// [[PromiseStatus]]: "resolved";
// [[PromiseValue]]: "hello, Promise"
// p2:
// [[PromiseStatus]]: "rejected"
// [[PromiseValue]]: "bye, Promise"
p1的PromiseStatus值為resolved,PromiseValue值為"hello, Promise",
p2的PromiseStatus值為rejected, PromiseValue值為"bye, Promise",
可以觀察到,在調(diào)用了resolve函數(shù)后,promise的狀態(tài)變?yōu)閞esolved, promise value值為傳給resolve函數(shù)的參數(shù)。如果調(diào)用了reject函數(shù),promise的狀態(tài)變?yōu)閞ejected,promise value為傳給reject函數(shù)的參數(shù)。
通過上面的試驗(yàn),我們可以對(duì)Promise有一個(gè)大概的了解:
1、promise是一個(gè)對(duì)象
2、promise對(duì)象初始化時(shí)接收一個(gè)函數(shù),并為這個(gè)函數(shù)傳入resolve和rejecte兩個(gè)方法
3、promise對(duì)象有兩個(gè)屬性:PromiseStatus 和 PromiseValue
4、在調(diào)用resolve方法后,PromiseStatus的值為resolved,PromiseValue的值為resolve的參數(shù);
5、在調(diào)用reject方法后,PromiseStatus的值為rejected,PromiseValue的值為rejecte的參數(shù);
6、在調(diào)用resolve或reject方法前,PromiseStatus的值為pending,PromiseValue的值為undefined
2.2 Promise 有啥用?
了解了什么是Promise,那么它有什么用?還是舉https.get的例子,只不過這次用promise來寫
const https = require('https');
https.get('https://encrypted.google.com/', (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
}).on('error', (e) => {
console.error(e);
});
// 以上代碼等效于
var p = new Promise((resolve, reject) => {
https.get('https://encrypted.google.com/', (res) => {
if(!_.isEmpty(res)){
resolve(res)
} else {
reject("出錯(cuò)了")
}
})
})
p.then(res =>{
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
}).catch(console.error);
來看看發(fā)生了什么,我們把https.get包進(jìn)了傳給Promise的函數(shù)中,并在拿到get函數(shù)的異步操作得到結(jié)果res后,將res傳給solve函數(shù),如果res為空,則調(diào)用reject,并傳入?yún)?shù)”出錯(cuò)了“。然后用then來接收resolve的參數(shù),用catch來接收reject的參數(shù)。
https.get函數(shù)的回調(diào)函數(shù)觸發(fā)之前,p的PromiseStatus為pending,promiseValue為undefined, 回調(diào)函數(shù)運(yùn)行,我會(huì)判斷res對(duì)象是否為空,若不為空,則將res傳給resolve函數(shù),使p得promiseStatus變?yōu)閞esolved,promisedValue變?yōu)閞es;若res為空,則reject。
你也許會(huì)問,這樣做代碼量比之前多了許多,寫起來更麻煩了,我為什么要這么做?
通過這樣做,我們可以把一個(gè)異步操作裝進(jìn)一個(gè)對(duì)象里,如同這個(gè)例子中,我們將https.get放進(jìn)了promise對(duì)象p中,這樣一來我們可以在任何地方通過p來訪問https.get的res對(duì)象,將業(yè)務(wù)代碼從回調(diào)函數(shù)中解放出來。
2.3 then和catch
promise對(duì)象有兩個(gè)方法,then和catch
其中then方法接收一個(gè)回調(diào)函數(shù)作為參數(shù),在promiseStatus為resolved時(shí),將promiseValue值傳給該回調(diào)函數(shù)
而catch方法同樣接收一個(gè)回調(diào)函數(shù)作為參數(shù),用來在promiseStatus為rejected時(shí)接收promise對(duì)象的promiseValue
var p1 = new Promise((resolve, reject) => {
resolve("Hello, Promise")
});
var p1 = new Promise((resolve, reject) => {
reject("Bye, Promise")
});
p1.then(console.log); // "Hello, Promise"
p2.catch(console.log); // "Bye, Promise"
then和catch都會(huì)返回一個(gè)promise對(duì)象,這個(gè)由then或catch返回的promise對(duì)象會(huì)以then或catch的回調(diào)函數(shù)的返回值作為promiseValue,并且promiseStatus值為resolved。
var p = new Promise((resolve, reject) => {
resolve("Hello, Promise")
});
p.then(data => data+", where are you going?").then(console.log);
// "Hello, Promise, where are you going?"
這個(gè)特性使得promise對(duì)象可以像鏈條一樣,一個(gè)一個(gè)地鏈起來。
在使用promise時(shí),reject通常用來拋出異常,而catch很自然地用來接異常,如果promise對(duì)象的promiseStatus值為rejected,則promiseValue值會(huì)跳過后面所有的then,落進(jìn)遇到的第一個(gè)catch中
var p = new Promise((resolve, reject) => {
reject("Bye, Promise")
});
p.then(data => console.log(1, data)).then(data => console.log(2, data)).catch(errMsg => console.log(3, errMsg));
// 3 "Bye errMsg"
類似地,若promise對(duì)象的promiseStatus為resolved,則promiseValue會(huì)跳過后面的所有catch,落進(jìn)遇到的第一個(gè)then中。
3 回調(diào)函數(shù)風(fēng)格的Promise化
使用Promise有各種各樣的好處,相信你一定會(huì)喜歡它,甚至希望將回調(diào)函數(shù)風(fēng)格的API轉(zhuǎn)為Promise,而你的確可以這么做,來看看如何得到Promise的https.get吧:
const https = require('https');
function get(url){
return new Promise((resolve, reject) => {
https.get('https://encrypted.google.com/', (error, res) => {
if(error){
reject(error);
} else {
resolve(res)
}
});
})
定義一個(gè)返回promise對(duì)象的函數(shù),該promise對(duì)象的resolve值為res,如果出錯(cuò),則reject(error)