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對象規定的方法(這裏的 then
或 catch
)之外的方法都是不可使用的,而不會像回調函數方式那樣能夠本身自由的定義回調函數的參數,而必須嚴格遵照固定、統一的編程方式來編寫代碼。api
這樣,基於Promise的統一接口的作法, 就能夠造成基於接口的各類各樣的異步處理模 式。因此,promise的功能是能夠將複雜的異步處理輕鬆地進行模式化,這也能夠說得上是 使用promise的理由之一。promise
要想建立一個Promise對象,可使用new
調用Promise
的構造器來進行實例化。瀏覽器
var promise = new Promise(function(resolve, reject) { // 異步處理
// 處理結束後、調用resolve 或 reject
});
複製代碼
對經過new生成的promise對象爲了設置其值在 resolve (成功)/ reject(失敗)時調用的回調函數 可使用 promise.then()
實例方法(也就是說做用是爲 Promise 實例添加狀態改變時的回調函數。)。
promise.then(onFulfilled, onRejected)
複製代碼
then
方法的第一個參數是 Resolved 狀態的回調函數, 第二個參數( 可選) 是 Rejected 狀態的回調函數。
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.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的狀態。
用 new Promise 實例化的promise對象有如下三個狀態。
關於上面這三種狀態的讀法,其中左側爲在 ES6 Promises 規範中定義的術語, 而右側則是在 Promises/A+ 中描述狀態的術語。
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代碼。
new Promise(fn)
返回一個Promise對象fn
中指定異步等處理邏輯 • 處理結果正常的話,調用 resolve(處理結果值) • 處理結果錯誤的話,調用 reject(Error對象)按照這個流程,咱們來寫一段promise代碼吧。任務是用Promise經過異步處理方式來獲取XMLHttpRequest(XHR)的數據。
首先,建立一個用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對象
)就能夠。
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);
})
複製代碼
上面代碼中,p1
和p2
都是 Promise
的實例, 可是p2
的resolve
方法將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 秒以後變爲rejected
。 p2
的狀態在 1 秒以後改變, resolve
方法返回的是p1
。 此時, 因爲p2
返回的是另外一個Promise, 因此後面的then
語句都變成針對後者( p1)。 又過了 2 秒 p1
變爲rejected
,致使觸發catch
方法指定的回調函數。
關於Promise 你真的瞭解多少? es6中的Promise JavaScript Promise 迷你書 中文版 JavaScript Promise 迷你書