Promise 是異步編程的一種解決方案,比傳統的回調更加合理而其人強大。javascript
Promise,簡單來講,是一個容器,裏面存放着某個將來纔會結束的事件的結果。Promise是一個對象,用來獲取異步操做的消息。java
Promise有如下兩個特色:編程
1 對象狀態不受外界影響。json
有三種狀態:Pending(進行中),Fulfilled(已成功),Rejected(已失敗)。只有異步操做的結果能夠決定當前是哪種狀態,任何操做都沒法改變這個狀態。數組
2 一旦改變,不會再變,任什麼時候候均可以獲得這個結果。promise
Promise對象的狀態改變只有兩種可能:從Pending 到 Fulfilled 或者 從Pending 到 Rejected。只要這兩種狀況發生,狀態就會凝固,不會再變,一直保持這個結果。這時就稱爲 Resolved(已定型)。就算改變已經發生,再對Promise添加回調函數,也會獲得這個結果。 這與事件(event)徹底不一樣,事件的特色就是:若是錯過了,再去監聽是得不到結果的。app
Promise 缺點:異步
1 沒法取消。一旦創建就會當即執行,沒法中途取消。async
2 若是不設置回調函數,Promise內部報錯不會拋出到外面。異步編程
3 當處於Pending狀態,沒法得知目前進展到哪一階段(是剛剛開始,仍是即將完成?)
var promise = new Promise((resolve,reject) => {
// ...
if(/* .....*/) {
resolve(value)
} else {
reject(error)
}
})
複製代碼
resolve做用是將Promise狀態由 未完成編程成功,在異步操做成功時調用,並將異步操做結果做爲參數傳播出去。
reject函數做用是,將Promise狀態從未完成變爲失敗,在異步操做失敗時調用,並將異步操做失敗報出的錯誤傳遞出去。
Promise 實例生成後,能夠調用then,指定 resolved 和 rejected 狀態的回調函數。
promise.then(function(val){
// success
}, function(error) {
// failure
})
複製代碼
then方法接受兩個回調函數做爲參數。第一個是 Promise 對象的狀態變爲Resolved時調用,第二個回調是Promise變爲Rejected時調用。第二個函數時可選的。
function timeout(ms){
return new Promise((resolve,reject) => {
setTimeout(resolve,ms,'done')
})
}
timeout(100).then((value) => {
console.log(value)
})
複製代碼
Promise 新建好以後就會當即執行。
let promise = new Promise(function(resolve,reject) {
console.log('Promise');
resolve();
})
promise.then(function(){
console.log('Resolved')
})
console.log('Hi')
// Promise
// Hi
// Resolves
複製代碼
異步加載圖片:
function loadImgAsync(url) {
return new Promise(function(resolve,reject) {
var img = new Image()
img.onload = function(){
resolve(img)
}
img.onerror = function(){
reject(new Error('Could not load img at ' + url))
}
img.src = url
})
}
複製代碼
promise 實現 AJAX
var getJSON = function (url) {
var promise = new Promise((resolve,reject)=> {
var client = new XMLHttpRequest()
client.open('GET',url)
client.onreadystatechange = handle
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("/post.json").then((json)=>{
console.log('Contents ' + json)
}, function(error){
console.log(error)
})
複製代碼
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(rsult => console.log(result))
.catch(error => console.log(error))
// Error: fail
複製代碼
通常來講,調用resolve 或者 reject 之後,Promise的使命就已經完成了,後面的操做應該放到 then ,而不該該直接寫到 resolve 或者 reject 後面,因此最好在它們以前 添加 return 語句,這樣就不會產生意外。
new Promise((resolve,reject) => {
return resolve(1);
// 後面不會執行
console.log(2)
})
複製代碼
Promise實例具備 then 方法,做用是 爲 Promise 實例添加狀態改變時的回調函數。
then 方法 返回一個新的 Promise 實例(不是原來那個Promise),所以 可使用 鏈式寫法,then 後面 再 添加 另一個 then。
getJSON("/posts.json").then(function(json){
return json.post
}).then(function(post){
// ...
})
複製代碼
鏈式調用的 then 能夠指定 一組按照次序調用的回調函數。 前一個回調函數可能返回的仍是一個Promise,後一個回調函數就會等待該Promise 對象的狀態發生變化,在被調用。
getJSON("/post/1.json").then(function(post){
return getJSON(post.commentUrl)
}).then(function funcA(comments) {
console.log("resolved")
},function funcB(error){
console.log(error)
})
複製代碼
這個方法 是 .then(null, rejection) 的別名,指定發生錯誤時的回調。
getJSON('/posts.json').then(function(posts){
// ...
}).catch(function(error){
console.log("error " + error )
})
複製代碼
Promise 在 resolve 語句以後 拋出錯誤,並不會被捕獲,等於沒有拋出。由於Promise狀態一旦發生改變,就會永久保存這個狀態,不會改變了。
Promise 對象錯誤具備「冒泡」性質,會一直向後傳遞,直到捕獲到爲止,錯誤總會被下一個catch捕獲
通常不要在 then 方法中定義 Rejected 狀態的回調函數,而應老是使用catch方法。
var someAsyncThing = function(){
return new Promise((resolve,reject)=>{
// 報錯 x 未聲明
resolve(x + 2)
})
}
someAsyncThing().then(()=>{
return someotherAsyncThing()
}).catch(error => {
console.log('oh no' , error)
// 報錯 y 沒有聲明
y + 2
}).then(()=>{
console.log('carry on')
})
// oh no [ReferenceError: x is not defined ]
複製代碼
catch方法拋出一個錯誤,後面若是沒有catch,致使這個錯誤不會被捕獲,也不會傳遞到外層。
能夠用 第二個 catch 方法捕獲 前一個catch 方法拋出的錯誤。
someAsyncThing().then(()=>{
return someotherAsyncThing()
}).catch(error => {
console.log('oh no' , error)
// 報錯 y 沒有聲明
y + 2
}).catch(error=>{
console.log('carry on ', error)
})
複製代碼
將多個 Promise 實例 包裝成一個新的 Promise實例。
var p = Promise.all([p1, p2, p3])
p 狀態 由 p1,p2,p3 決定,分兩種狀況。
1 只有p1,p2,p3 狀態都變成 fulfilled ,p 的狀態 纔會變成 fulfilled,此時 p1,p2,p3 返回值組成一個數組,傳遞給 p 的回調。
2 只要 p1,p2,p3 有一個被Rejected,p 狀態也會變成 Rejected,此時,第一個被 Rejected 的實例返回值會傳遞給p的回調函數。
var promises = [2,3,5,7,11,13].map(function(id) {
return getJSON('/post/'+id+'.json')
})
promise.all(promises).then(posts=>{
//...
}).catch(reason=>{
//...
})
複製代碼
另外一個例子
const databasePromise = connectDatabase()
const bookPromise = databasePromise.then(findAllBooks)
const userPromise = databasePromise.then(getCurrentUser)
Promise
.all([bookPromise,userPromise])
.then(([books,user])=>{
//...
})
複製代碼
若是做爲參數的Promise自身定義了catch方法,那麼被rejected大時並不會觸發 Promise.all 的 catch 方法
var p = new Promise.race([p1,p2,p3])
複製代碼
只要 p1,p2,p3中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的Promise 實例 返回值就是傳遞給p 的回調函數。
const p = Promise.race([
fetch('/xxx'),
new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(new Error('time out'))
},5000)
})
])
p.then(res=>{
console.log(res)
})
.catch(error => {
console.log(error)
})
複製代碼
不管Promise對象的回調鏈以then方法仍是catch結尾,最後一個方法拋出的錯誤均可能沒法捕捉到(Promise內部的錯誤不會冒泡的全局)所以,提供一個 done 方法,老是處於回調鏈尾端,保證拋出任何可能的錯誤。
asyncFunc()
.then()
.catch()
.then()
.done()
複製代碼
實現
Promise.prototype.done = function(onFulilled, onRejected){
this.then(onFulilled, onRejected)
.catch(function(resason){
setTimeout(() => {throw reason},0)
})
}
複製代碼
finally方法用於指定無論Promise對象最後狀態如何都會執行的操做。與 done 最大的區別在於 他接受一個不普通的回調函數做爲參數,該函數無論怎樣都必須執行。
實現
Promise.prototype.finally = function(callback){
let P = this.constructotr
return this.then(
value => P.resolve(callback()).then(()=>value),
reason => P.resolve(callback()).then(()=> {throw reason})
)
}
複製代碼