Nodejs Promise 讀書筆記

Nodejs Promise 讀書筆記

前言

Promise是抽象異步處理對象以及對其進行各類操做的組件。(Promise並非從JavaScript中發源的概念)。簡單說就是一個容器, 裏面保存着某個將來纔會結束的事件( 一般是一個異步操做)的結果。javascript

JavaScript中是經過回調函數來處理異步邏輯的,好比讀取文件的代碼,以下所示html

getAsync("fileA.txt", function(error, result){
    if(error){
        // 取得失敗時的處理 throw error;
    }
}
複製代碼

Nodejs中規定在Javascript的回掉函數的第一個參數是Error對象。像上面這樣基於回調函數的異步處理若是統一參數使用規則的話,寫法也會很明瞭。可是,這也僅是編碼規約而已,即便採用不一樣的寫法也不會出錯。java

Promise則是把相似的異步處理對象和處理規則進行規範化,並按照採用統一的接口來編寫,而採起規定方法以外的寫法都會出錯。node

下面經過Promise寫法改寫上面的函數es6

var promise = getAsyncPromise("fileA.txt"); //返回promise對象
promise.then(function(result){
    // 獲取文件內容成功時的處理
}).catch(function(error){
    // 獲取文件內容失敗時的處理
});
複製代碼

咱們能夠向這個預設了抽象化異步處理的promise對象,註冊這個promise對象執行成功 時和失敗時相應的回調函數。編程

這和回調函數方式相比有哪些不一樣之處呢? 在使用promise進行一步處理的時候,咱們 必須按照接口規定的方法編寫處理代碼。json

也就是說,除promise對象規定的方法(這裏的 thencatch )之外的方法都是不可使用的,而不會像回調函數方式那樣能夠本身自由的定義回調函數的參數,而必須嚴格遵照固定、統一的編程方式來編寫代碼。api

這樣,基於Promise的統一接口的作法, 就能夠造成基於接口的各類各樣的異步處理模 式。因此,promise的功能是能夠將複雜的異步處理輕鬆地進行模式化,這也能夠說得上是 使用promise的理由之一。promise

Promise 簡介

構造器

要想建立一個Promise對象,可使用new調用Promise的構造器來進行實例化。瀏覽器

var promise = new Promise(function(resolve, reject) { // 異步處理
// 處理結束後、調用resolve 或 reject
});
複製代碼

實例方法

Promise.prototype.then

對經過new生成的promise對象爲了設置其值在 resolve (成功)/ reject(失敗)時調用的回調函數 可使用 promise.then()實例方法(也就是說做用是爲 Promise 實例添加狀態改變時的回調函數。)。

promise.then(onFulfilled, onRejected)
複製代碼

then方法的第一個參數是 Resolved 狀態的回調函數, 第二個參數( 可選) 是 Rejected 狀態的回調函數。

  • resolve(成功)時 onFulfilled 會被調用
  • reject(失敗)時 onRejected 會被調用

onFulfilled 、 onRejected 兩個都爲可選參數。

then方法返回的是一個新的 Promise 實例( 注意,不是原來那個 Promise 實例)。 所以能夠採用鏈式寫法, 即then方法後面再調用另外一個`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方法,依次指定了兩個回調函數。 第一個回調函數完成之後,會將返回結果做爲參數,傳入第二個回調函數。採用鏈式的then,能夠指定一組按照次序調用的回調函數。

Promise.prototype.catch()

promise.then成功和失敗時均可以使用。另外在只想對異常進行處理時能夠採用Promise.then(undefined, onRejected)這種方式,只指定reject時的回調函數便可。Promise.prototype.catch方法是.then(null, rejection) 的別名, 用於指定發生錯誤時的回調函數,等同於拋出錯誤。 上文的代碼能夠改形成以下

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 狀態已經變成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 對象的錯誤具備「 冒泡」 性質, 會一直向後傳遞, 直到被捕獲爲止。 也就是說, 錯誤老是會被下一個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'產生, 三個由then產生。它們之中的第二個then方法出了錯誤,中斷了下面的then方法,直接被最後一個catch捕獲。

建議老是使用catch方法, 而不使用then方法的第二個處理錯誤的參數。

跟傳統的try / catch代碼塊不一樣的是,若是沒有使用catch方法指定錯誤處理的回調函數,Promise 對象拋出的錯誤不會傳遞到外層代碼, 即不會有任何反應。

var someAsyncThing = function() {  
    return new Promise(function(resolve, reject) {  
        //  下面一行會報錯,由於 x 沒有聲明  
        resolve(x + 2);  
    });  
};  
someAsyncThing().then(function() {  
    console.log('everything is great');  
});  
複製代碼

上面代碼中,someAsyncThing函數產生的 Promise 對象會報錯, 可是因爲沒有指定catch方法,這個錯誤不會被捕獲,也不會傳遞到外層代碼, 致使運行後沒有任何輸出。

注意, Chrome 瀏覽器不遵照這條規定, 它會拋出錯誤「 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指定在下一輪「 事件循環」 再拋出錯誤, 結果因爲沒有指定使用try...catch語句,就冒泡到最外層,成了未捕獲的錯誤。 由於此時,Promise 的函數體已經運行結束了, 因此這個錯誤是在Promise函數體外拋出的。

Node.js 有一個unhandledRejection事件,專門監聽未捕獲的reject錯誤。unhandledRejection事件的監聽函數有兩個參數, 第一個是錯誤對象, 第二個是報錯的 Promise 實例, 它能夠用來了解發生錯誤的環境信息。

process.on('unhandledRejection', function(err, p) {  
    console.error(err.stack)  
}); 
複製代碼

須要注意的是,catch方法返回的仍是一個Promise對象,所以後面還能夠接着調用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方法指定的回調函數,會接着運行後面那個then方法指定的回調函數。 若是沒有報錯, 則會跳過catch方法。

靜態方法

像 Promise 這樣的全局對象還擁有一些靜態方法。

包括 Promise.all() 還有 Promise.resolve() 等在內,主要都是一些對Promise進行操做的 輔助方法。

Promise 狀態

咱們已經大概瞭解了Promise的處理流程,接下來讓咱們來稍微整理一下Promise的狀態。

用 new Promise 實例化的promise對象有如下三個狀態。

  • "has-resolution" 即Fulfilled resolve(成功)時。此時會調用 onFulfilled
  • "has-rejection" 即Rejected reject(失敗)時。此時會調用 onRejected
  • "unresolved" 即Pending 既不是resolve也不是reject的狀態。也就是promise對象剛被建立後的初始化狀態等

關於上面這三種狀態的讀法,其中左側爲在 ES6 Promises 規範中定義的術語, 而右側則是在 Promises/A+ 中描述狀態的術語。

01.png-250.7kB

promise對象的狀態,從Pending轉換爲Fulfilled或Rejected以後, 這個promise對象的狀態就不會再發生任何變化。也就是說,只有異步操做的結果能夠決定當前是哪種狀態,其餘任何操做都沒法改變這種狀態;一旦狀態改變,就不會再改變。

Promise與Event等不一樣,在 .then 後執行的函數能夠確定地說只會被調用一次。

還有須要注意,Promise建立後回馬上執行,看下面代碼

var promise = new Promise(function(resolve, reject) {  
    console.log('Promise');  
    resolve();  
});  
promise.then(function() {  
    console.log('Resolved.');  
});  
console.log('Hi!');  
// Promise  
// Hi!  
// Resolved 
複製代碼

上面代碼中, Promise 新建後當即執行, 因此首先輸出的是「 Promise」。 而後, then方法指定的回調函數, 將在當前腳本全部同步任務執行完纔會執行, 因此「 Resolved」 最後輸出。

Promise也是有缺點的

  • 沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。
  • 若是不設置回調函數, Promise內部拋出的錯誤,不會反應到外部。
  • 當處於Pending狀態時, 沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。

編寫 Promise代碼

下面介紹一下如何編寫一下Promise代碼。

建立Promise對象流程

  1. new Promise(fn)返回一個Promise對象
  2. fn中指定異步等處理邏輯 • 處理結果正常的話,調用 resolve(處理結果值) • 處理結果錯誤的話,調用 reject(Error對象)

按照這個流程,咱們來寫一段promise代碼吧。任務是用Promise經過異步處理方式來獲取XMLHttpRequest(XHR)的數據。

建立XHR的promise對象

首先,建立一個用Promise把XHR處理包裝起來的名爲 getURL 的函數。

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取得結果狀態爲200時纔會調用 resolve,而其餘狀況(取得失敗)時則會調用 reject 方法。

  • resolve(req.responseText) resolve函數的做用是, 將 Promise對象的狀態從「 未完成」 變爲「 成功」( 即從 Pending變爲Resolved),在異步操做成功時調用,並將異步操做結果,做爲參數傳遞出去。 參數並無特別的規則,基本上把要傳給回調函數參數放進去就能夠了。 ( then 方法能夠接收到這個參數值)

  • reject(new Error(req.statusText)); reject函數的做用是,將 Promise 對象的狀態從「 未完成」 變爲「 失敗」( 即從 Pending 變爲Rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。

    上文中,XHR中 onerror事件被觸發的時候就是發生錯誤時,因此理所固然調用 reject 。發生錯誤時,建立一個Error對象後再將具體的值傳進去。傳給 的參數也沒有什麼特殊的限制,通常只要是Error對象(或者 繼承自Error對象)就能夠。

Promise實例

Promise實現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 對象的封裝, 用於發出一個針對 JSON 數據的 HTTP 請求, 而且返回一個 Promise 對象。 須要注意的是,在getJSON內部, resolve函數和reject函數調用時, 都帶有參數。關於參數傳遞,上文作過簡要介紹

若是調用resolve函數和reject函數時帶有參數,那麼它們的參數會被傳遞給回調函數。 reject函數的參數一般是 Error 對象的實例,表示拋出的錯誤; resolve函數的參數除了正常的值之外,還多是另外一個 Promise實例, 表示異步操做的結果有多是一個值,也有多是另外一個異步操做,好比像下面這樣。

var p1 = new Promise(function(resolve, reject) {  
    // ...  
});  
var p2 = new Promise(function(resolve, reject) {  
    // ...  
    resolve(p1);  
})
複製代碼

上面代碼中,p1p2都是 Promise 的實例, 可是p2resolve方法將p1做爲參數,即一個異步操做的結果是返回另外一個異步操做

注意,這時p1的狀態就會傳遞給p2,也就是說,p1的狀態決定了p2的狀態。若是p1的狀態是Pending,那麼p2的回調函數就會等待p1的狀態改變; 若是p1的狀態已是Resolved或者Rejected, 那麼p2的回調函數將會馬上執行。

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 秒以後變爲rejectedp2的狀態在 1 秒以後改變, resolve方法返回的是p1。 此時, 因爲p2返回的是另外一個Promise, 因此後面的then語句都變成針對後者( p1)。 又過了 2 秒 p1變爲rejected,致使觸發catch方法指定的回調函數。

使用bluebird爬蟲實踐

推薦文章

關於Promise 你真的瞭解多少es6中的Promise JavaScript Promise 迷你書 中文版 JavaScript Promise 迷你書

相關文章
相關標籤/搜索