Nodejs Promise 讀書筆記
前言
Promise是抽象異步處理對象以及對其進行各種操作的組件爬虱。(Promise并不是從JavaScript中發(fā)源的概念)伦腐。簡單說就是一個容器辟狈, 里面保存著某個未來才會結(jié)束的事件( 通常是一個異步操作)的結(jié)果藤韵。
JavaScript中是通過回調(diào)函數(shù)來處理異步邏輯的,比如讀取文件的代碼,如下所示
getAsync("fileA.txt", function(error, result){
if(error){
// 取得失敗時的處理 throw error;
}
}
Nodejs中規(guī)定在Javascript的回掉函數(shù)的第一個參數(shù)是Error
對象稠肘。像上面這樣基于回調(diào)函數(shù)的異步處理如果統(tǒng)一參數(shù)使用規(guī)則的話,寫法也會很明了萝毛。但是项阴,這也僅是編碼規(guī)約而已,即使采用不同的寫法也不會出錯笆包。
Promise則是把類似的異步處理對象和處理規(guī)則進行規(guī)范化环揽,并按照采用統(tǒng)一的接口來編寫,而采取規(guī)定方法之外的寫法都會出錯庵佣。
下面通過Promise寫法改寫上面的函數(shù)
var promise = getAsyncPromise("fileA.txt"); //返回promise對象
promise.then(function(result){
// 獲取文件內(nèi)容成功時的處理
}).catch(function(error){
// 獲取文件內(nèi)容失敗時的處理
});
我們可以向這個預(yù)設(shè)了抽象化異步處理的promise對象歉胶,注冊這個promise對象執(zhí)行成功 時和失敗時相應(yīng)的回調(diào)函數(shù)。
這和回調(diào)函數(shù)方式相比有哪些不同之處呢? 在使用promise進行一步處理的時候巴粪,我們 必須按照接口規(guī)定的方法編寫處理代碼通今。
也就是說粥谬,除promise對象規(guī)定的方法(這里的 then
或 catch
)以外的方法都是不可以使用的,而不會像回調(diào)函數(shù)方式那樣可以自己自由的定義回調(diào)函數(shù)的參數(shù)辫塌,而必須嚴格遵守固定漏策、統(tǒng)一的編程方式來編寫代碼。
這樣臼氨,基于Promise的統(tǒng)一接口的做法掺喻, 就可以形成基于接口的各種各樣的異步處理模 式。所以一也,promise的功能是可以將復(fù)雜的異步處理輕松地進行模式化巢寡,這也可以說得上是 使用promise的理由之一喉脖。
Promise 簡介
構(gòu)造器
要想創(chuàng)建一個Promise對象椰苟,可以使用new
調(diào)用Promise
的構(gòu)造器來進行實例化。
var promise = new Promise(function(resolve, reject) { // 異步處理
// 處理結(jié)束后树叽、調(diào)用resolve 或 reject
});
實例方法
Promise.prototype.then
對通過new生成的promise對象為了設(shè)置其值在 resolve (成功)/ reject(失斢吆)時調(diào)用的回調(diào)函數(shù) 可以使用 promise.then()
實例方法(也就是說作用是為 Promise 實例添加狀態(tài)改變時的回調(diào)函數(shù)。)题诵。
promise.then(onFulfilled, onRejected)
then
方法的第一個參數(shù)是 Resolved 狀態(tài)的回調(diào)函數(shù)洁仗, 第二個參數(shù)( 可選) 是 Rejected 狀態(tài)的回調(diào)函數(shù)。
- resolve(成功)時 onFulfilled 會被調(diào)用
- reject(失敗)時 onRejected 會被調(diào)用
onFulfilled 性锭、 onRejected 兩個都為可選參數(shù)赠潦。
then
方法返回的是一個新的 Promise 實例( 注意,不是原來那個 Promise 實例)草冈。 因此可以采用鏈式寫法她奥, 即then
方法后面再調(diào)用另一個`then方法。
getJSON("/post/1.json") //返回一個Promise對象怎棱,詳見下文
.then(function(post) {
return getJSON(post.commentURL); //返回一個Promise對象
})
.then(function funcA(comments) {
console.log("Resolved: ", comments);
}, function funcB(err) {
console.log("Rejected: ", err);
});
上面的代碼使用then
方法哩俭,依次指定了兩個回調(diào)函數(shù)。 第一個回調(diào)函數(shù)完成以后拳恋,會將返回結(jié)果作為參數(shù)凡资,傳入第二個回調(diào)函數(shù)。采用鏈式的then谬运,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)隙赁。
Promise.prototype.catch()
promise.then
成功和失敗時都可以使用。另外在只想對異常進行處理時可以采用Promise.then(undefined, onRejected)
這種方式梆暖,只指定reject時的回調(diào)函數(shù)即可伞访。Promise.prototype.catch
方法是.then(null, rejection)
的別名, 用于指定發(fā)生錯誤時的回調(diào)函數(shù),等同于拋出錯誤式廷。
上文的代碼可以改造成如下
getJSON("/post/1.json") //返回一個Promise對象咐扭,詳見下文
.then(function(post) {
return getJSON(post.commentURL); //返回一個Promise對象
})
.then(function (comments) {
console.log("Resolved: ", comments);
})
.catch(err) {
console.log("Rejected: ", err);
});
需要注意的是,如果 Promise 狀態(tài)已經(jīng)變成Resolved, 再拋出錯誤是無效的蝗肪。
var promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) {
console.log(value)
})
.catch(function(error) {
console.log(error)
});
// ok
上面代碼中袜爪, Promise 在resolve語句后面,再拋出錯誤薛闪,不會被捕獲辛馆, 等于沒有拋出。
Promise 對象的錯誤具有“ 冒泡” 性質(zhì)豁延, 會一直向后傳遞昙篙, 直到被捕獲為止。 也就是說诱咏, 錯誤總是會被下一個catch語句捕獲苔可。
var catchTest = new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('aa')
}, 1000)
})
catchTest
.then(function(value){
console.log('a')
})
.then(function(value){
throw new Error('test');
console.log('b')
})
.then(function(value){
console.log('c')
})
.catch(function(error){
console.log(error)
})
//a
//[Error: test]
上面代碼中,一共有四個Promise 對象:一個由'catchTest'產(chǎn)生袋狞, 三個由then產(chǎn)生焚辅。它們之中的第二個then方法出了錯誤拼卵,中斷了下面的then方法酣溃,直接被最后一個catch捕獲。
建議總是使用catch方法昵时, 而不使用then方法的第二個處理錯誤的參數(shù)早处。
跟傳統(tǒng)的try / catch
代碼塊不同的是湾蔓,如果沒有使用catch方法指定錯誤處理的回調(diào)函數(shù),Promise 對象拋出的錯誤不會傳遞到外層代碼砌梆, 即不會有任何反應(yīng)默责。
var someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行會報錯,因為 x 沒有聲明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
上面代碼中么库,someAsyncThing
函數(shù)產(chǎn)生的 Promise 對象會報錯傻丝, 但是由于沒有指定catch方法,這個錯誤不會被捕獲诉儒,也不會傳遞到外層代碼葡缰, 導(dǎo)致運行后沒有任何輸出。
注意忱反, Chrome 瀏覽器不遵守這條規(guī)定泛释, 它會拋出錯誤“ ReferenceError: x is not defined”。
var promise = new Promise(function(resolve, reject) {
resolve("ok");
setTimeout(function() {
throw new Error('test')
}, 0)
});
promise.then(function(value) {
console.log(value)
});
// ok
// Uncaught Error: test
上面代碼中温算,Promise指定在下一輪“ 事件循環(huán)” 再拋出錯誤怜校, 結(jié)果由于沒有指定使用try...catch語句
,就冒泡到最外層注竿,成了未捕獲的錯誤茄茁。 因為此時魂贬,Promise 的函數(shù)體已經(jīng)運行結(jié)束了, 所以這個錯誤是在Promise函數(shù)體外拋出的裙顽。
Node.js 有一個unhandledRejection
事件付燥,專門監(jiān)聽未捕獲的reject錯誤。unhandledRejection
事件的監(jiān)聽函數(shù)有兩個參數(shù)愈犹, 第一個是錯誤對象键科, 第二個是報錯的 Promise 實例, 它可以用來了解發(fā)生錯誤的環(huán)境信息漩怎。
process.on('unhandledRejection', function(err, p) {
console.error(err.stack)
});
需要注意的是勋颖,catch方法返回的還是一個Promise對象,因此后面還可以接著調(diào)用then方法勋锤。
var someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行會報錯饭玲,因為 x 沒有聲明
resolve(x + 2);
});
};
someAsyncThing()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
上面代碼運行完catch方法指定的回調(diào)函數(shù),會接著運行后面那個then方法指定的回調(diào)函數(shù)怪得。 如果沒有報錯咱枉, 則會跳過catch方法卑硫。
靜態(tài)方法
像 Promise 這樣的全局對象還擁有一些靜態(tài)方法徒恋。
包括 Promise.all()
還有 Promise.resolve()
等在內(nèi),主要都是一些對Promise進行操作的 輔助方法欢伏。
Promise 狀態(tài)
我們已經(jīng)大概了解了Promise的處理流程入挣,接下來讓我們來稍微整理一下Promise的狀態(tài)。
用 new Promise 實例化的promise對象有以下三個狀態(tài)硝拧。
- "has-resolution" 即Fulfilled
resolve(成功)時径筏。此時會調(diào)用 onFulfilled - "has-rejection" 即Rejected
reject(失敗)時。此時會調(diào)用 onRejected - "unresolved" 即Pending
既不是resolve也不是reject的狀態(tài)障陶。也就是promise對象剛被創(chuàng)建后的初始化狀態(tài)等
關(guān)于上面這三種狀態(tài)的讀法滋恬,其中左側(cè)為在 ES6 Promises 規(guī)范中定義的術(shù)語, 而右側(cè)則是在 Promises/A+ 中描述狀態(tài)的術(shù)語抱究。
![01.png-250.7kB](http://static.zybuluo.com/hellobeifeng1314/nnsa2aw5vbsk60gphm9h31qs/01.png)
promise對象的狀態(tài)恢氯,從Pending轉(zhuǎn)換為Fulfilled或Rejected之后, 這個promise對象的狀態(tài)就不會再發(fā)生任何變化鼓寺。也就是說勋拟,只有異步操作的結(jié)果可以決定當前是哪一種狀態(tài),其他任何操作都無法改變這種狀態(tài)妈候;一旦狀態(tài)改變敢靡,就不會再改變。
Promise與Event等不同苦银,在
.then
后執(zhí)行的函數(shù)可以肯定地說只會被調(diào)用一次啸胧。
還有需要注意赶站,Promise創(chuàng)建后回立刻執(zhí)行,看下面代碼
var promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('Resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// Resolved
上面代碼中, Promise 新建后立即執(zhí)行纺念, 所以首先輸出的是“ Promise”亲怠。 然后, then方法指定的回調(diào)函數(shù)柠辞, 將在當前腳本所有同步任務(wù)執(zhí)行完才會執(zhí)行团秽, 所以“ Resolved” 最后輸出。
Promise也是有缺點的
- 無法取消Promise叭首,一旦新建它就會立即執(zhí)行习勤,無法中途取消。
- 如果不設(shè)置回調(diào)函數(shù)焙格, Promise內(nèi)部拋出的錯誤图毕,不會反應(yīng)到外部。
- 當處于Pending狀態(tài)時眷唉, 無法得知目前進展到哪一個階段(剛剛開始還是即將完成)予颤。
編寫 Promise代碼
下面介紹一下如何編寫一下Promise代碼。
創(chuàng)建Promise對象流程
-
new Promise(fn)
返回一個Promise對象 - 在
fn
中指定異步等處理邏輯
? 處理結(jié)果正常的話冬阳,調(diào)用 resolve(處理結(jié)果值)
? 處理結(jié)果錯誤的話蛤虐,調(diào)用 reject(Error對象)
按照這個流程,我們來寫一段promise代碼吧肝陪。任務(wù)是用Promise通過異步處理方式來獲取XMLHttpRequest(XHR)的數(shù)據(jù)驳庭。
創(chuàng)建XHR的promise對象
首先,創(chuàng)建一個用Promise把XHR處理包裝起來的名為 getURL
的函數(shù)氯窍。
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
// 運行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(function onRejected(error){
console.error(error);
});
getURL
只有在通過XHR取得結(jié)果狀態(tài)為200
時才會調(diào)用resolve
,而其他情況(取得失敗)時則會調(diào)用reject
方法饲常。
resolve(req.responseText)
resolve
函數(shù)的作用是, 將Promise
對象的狀態(tài)從“ 未完成” 變?yōu)椤?成功”( 即從Pending
變?yōu)?code>Resolved)狼讨,在異步操作成功時調(diào)用贝淤,并將異步操作結(jié)果,作為參數(shù)傳遞出去政供。
參數(shù)并沒有特別的規(guī)則播聪,基本上把要傳給回調(diào)函數(shù)參數(shù)放進去就可以了。 ( then 方法可以接收到這個參數(shù)值)-
reject(new Error(req.statusText));
reject
函數(shù)的作用是鲫骗,將Promise
對象的狀態(tài)從“ 未完成” 變?yōu)椤?失敗”( 即從Pending
變?yōu)?code>Rejected)犬耻,在異步操作失敗時調(diào)用,并將異步操作報出的錯誤执泰,作為參數(shù)傳遞出去枕磁。上文中,XHR中
onerror
事件被觸發(fā)的時候就是發(fā)生錯誤時术吝,所以理所當然調(diào)用reject
计济。發(fā)生錯誤時茸苇,創(chuàng)建一個Error
對象后再將具體的值傳進去。傳給 的參數(shù)也沒有什么特殊的限制沦寂,一般只要是Error
對象(或者 繼承自Error對象
)就可以学密。
Promise實例
Promise實現(xiàn)Ajax操作
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject) {
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if(this.readyState !== 4) {
return;
}
if(this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error(' 出錯了 ', error);
});
上面代碼中,getJSON
是對 XMLHttpRequest
對象的封裝传藏, 用于發(fā)出一個針對 JSON
數(shù)據(jù)的 HTTP
請求腻暮, 并且返回一個 Promise 對象。 需要注意的是毯侦,在getJSON
內(nèi)部哭靖, resolve
函數(shù)和reject
函數(shù)調(diào)用時, 都帶有參數(shù)侈离。關(guān)于參數(shù)傳遞试幽,上文做過簡要介紹
如果調(diào)用resolve
函數(shù)和reject
函數(shù)時帶有參數(shù),那么它們的參數(shù)會被傳遞給回調(diào)函數(shù)卦碾。
reject函數(shù)的參數(shù)通常是 Error 對象的實例铺坞,表示拋出的錯誤;
resolve函數(shù)的參數(shù)除了正常的值以外洲胖,還可能是另一個 Promise
實例济榨, 表示異步操作的結(jié)果有可能是一個值,也有可能是另一個異步操作宾濒,比如像下面這樣腿短。
var p1 = new Promise(function(resolve, reject) {
// ...
});
var p2 = new Promise(function(resolve, reject) {
// ...
resolve(p1);
})
上面代碼中,p1
和p2
都是 Promise
的實例绘梦, 但是p2
的resolve
方法將p1
作為參數(shù),即一個異步操作的結(jié)果是返回另一個異步操作赴魁。
注意卸奉,這時p1
的狀態(tài)就會傳遞給p2
,也就是說颖御,p1
的狀態(tài)決定了p2
的狀態(tài)榄棵。如果p1
的狀態(tài)是Pending
,那么p2
的回調(diào)函數(shù)就會等待p1
的狀態(tài)改變潘拱; 如果p1
的狀態(tài)已經(jīng)是Resolved
或者Rejected
疹鳄, 那么p2
的回調(diào)函數(shù)將會立刻執(zhí)行。
var p1 = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
var p2 = new Promise(function(resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result))
.catch(error => console.log(error))
上面代碼中芦岂,p1
是一個 Promise
瘪弓, 3 秒之后變?yōu)?code>rejected。 p2
的狀態(tài)在 1 秒之后改變禽最, resolve
方法返回的是p1
腺怯。 此時袱饭, 由于p2
返回的是另一個Promise, 所以后面的then
語句都變成針對后者( p1)呛占。 又過了 2 秒 p1
變?yōu)?code>rejected虑乖,導(dǎo)致觸發(fā)catch
方法指定的回調(diào)函數(shù)。
使用bluebird爬蟲實踐
推薦文章
關(guān)于Promise 你真的了解多少晾虑?
es6中的Promise
JavaScript Promise 迷你書
中文版 JavaScript Promise 迷你書