JavaScript Promise 學習記錄(一)
本文首先介紹了promise的三種狀態及生成不同狀態promise方法,然後介紹了promise的回撥處理方法then,分析了不同情況下then函式返回的promise狀態。最後通過promise鏈,將以上知識點進行串聯。希望能夠對大家有所幫助。由於作者的能力有限,也正在處於學習階段,文中不免會有錯誤,歡迎讀者指正。
Promise的三種狀態
Promise 有三種狀態: 未決議(pending)、已決議(resolved)、已拒絕(rejected)
生成Promise的方式有 new Promise() 、 Promise.resolve() 、Promise.reject()
在promise例項上呼叫then或catch方法也會生成promise物件,故可進行promise鏈,在下一節會有所介紹。
tip: chrome瀏覽器的控制檯,是一個快速驗證js程式碼的不錯選擇,讀者可以開啟chrome瀏覽器,邊閱讀邊實踐。以下程式碼皆可直接複製,進行驗證。
首先我們在chrome控制檯中,實踐下生成三種不同狀態的promise的方法:
//生成pedding狀態的promise,在控制檯輸入:
new Promise ((resolve, reject) => {
// do nothing
})
//控制檯輸出
//Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
//生成已決議狀態的promise:
new Promise ((resolve, reject) => {
resolve(123)
})
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}
//或是
Promise.resolve(123)
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}
//生成拒絕狀態的promise
new Promise ((resolve, reject) => {
reject(456)
})
//Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}
//或是
Promise.reject(456)
//Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}複製程式碼
以上建立的都是立即決議的promise,在我們實際的場景中,Promise往往需要一段時間來處理任務,再完成決議。我們來模擬下這個過程:
var p = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(123)
}, 5000)
})複製程式碼
輸入完以上程式碼後,我們立即在控制檯輸入p,檢視p物件
p
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}複製程式碼
5秒後,我們再次輸入p,
p
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}複製程式碼
以上,我們建立了一個Promise物件,該物件建立時,處於pending狀態,我們模擬了一段耗時操作,在5秒後,對該promise進行決議 resolve(123).我們再次檢視p時,發現狀態已經變為resolved。
Promise的回撥
在promise上可以呼叫then方法進行回撥的註冊。promise採用了正確與錯誤回撥分流的方式進行分別回撥。區別於error-first方式。
首先看下error-first方式的回撥處理方法:
var callback = function(error, value) {
//在回撥中首先判斷是否有錯誤,如果有錯誤,進行處理
if (error) {
console.log(error)
return
}
//無錯誤,正常處理
console.log(`get value :` + value)
}
function foo (key, callback) {
// do something
if (key == 1) {
callback(`key not exit`, null) //存在錯誤的情況
} else if (key == 2) {
callback(null, 3) // 正常處理的情況
}
}
foo(1, callback) // key not exit
foo(2, callback) // get value :3複製程式碼
- promise 採用正常與錯誤結果分流回撥的處理設計
var p = new Promise((resolve, reject) => {
resolve(123)
})
p.then(fullfilled => {
console.log(fullfilled) //123
}, error => {
console.log(error) //不會呼叫到這裡
}
)複製程式碼
細心的讀者會看到在控制檯除了列印了 123外,還列印了 Promise {[[PromiseStatus]]: “resolved”, [[PromiseValue]]: undefined}。還記得我們前面提到then方法也會生成promise嗎,這裡列印的就是呼叫then方法生成的promise,我們可以先跳過,後面詳細介紹。
以上我們建立了一個promise,並且通過呼叫then方法,在該promise註冊了兩個回撥函式用於監聽正確與失敗的處理結果。由於該promise 通過resolve(123)進行了決議,所以我們在then上註冊的第一個函式,會收到結果,列印123。
不難想象如果promise做出了拒絕的處理,或者內部有錯誤,我們在then上註冊的錯誤處理函式將被呼叫。來看下例子
var p = new Promise((resolve, reject) => {
reject(456)
})
p.then(fullfilled => {
console.log(fullfilled) //不會呼叫到這裡
}, error => {
console.log(error) //456
}
)
//或者
var p = new Promise((resolve, reject) => {
foo() //由於未定義foo, 會丟擲錯誤
resolve(123) // 無法執行到
})
p.then(fullfilled => {
console.log(fullfilled) //不會呼叫這裡
}, error => {
console.log(error) //ReferenceError: foo is not defined
}
)複製程式碼
我們看到在傳遞給promise的匿名函式中,我們呼叫了foo,由於未定義該方法,所以丟擲了錯誤。我們可以把丟擲錯誤也理解為promise做出了reject決議,並且拒絕的理由是丟擲的錯誤資訊。
我們看到foo()後面一句,呼叫了resolve,由於錯誤的丟擲,這行實際沒有被執行到。我們來猜測下,如果不是丟擲錯誤,而是呼叫了reject(456),再呼叫resolve(123),resolve會生效嗎?我們註冊的2個回撥都會被執行嗎?
var p = new Promise((resolve, reject) => {
reject(456)
resolve(123)
})
p.then(fullfilled => {
console.log(fullfilled) //不會呼叫這裡
}, error => {
console.log(error) //456
}
)複製程式碼
看來是不行,如果是多次呼叫resolve呢?
var p = new Promise((resolve, reject) => {
resolve(123)
resolve(456)
})
p.then(fullfilled => {
console.log(fullfilled) //123
}, error => {
console.log(error) //不會呼叫這裡
}
)複製程式碼
看來resolve的結果並沒有被覆寫。
看了上面的例子,引述下書上的結論吧:promise一旦決議,即由peding狀態變為 resolve或reject狀態,其狀態及值就不會再次改變。這點保證了,後續在同一個promise上呼叫then方法註冊的回撥函式都將收到相同的結果。
Promise鏈
上面提到在promise上呼叫then方法,會返回新的promise。那新的promise的值和狀態是什麼呢?
答案是取決於執行then中註冊的回撥方法時的返回值。
1.無return
相當於呼叫了return Promise.resolve(undefined),
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}複製程式碼
也就是我們上文中,呼叫then方法時,除了列印回撥中的日誌,還列印了promise的原因。
2.return 非promise、非thenable(後面介紹)值。
比如 return 123。相當於呼叫了return Promise.resolve(123), 即返回包裝了決議值為123的promise。
var p1 = Promise.resolve(`p1Value`)
p1.then(fullfilled => {
console.log(fullfilled) //p1Value
return 123
}, error => {
console.log(error)
})複製程式碼
我們看到控制檯列印了
p1Value
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}複製程式碼
第二行列印的,就是then方法返回的promise例項。既然他返回的是promise,我們是不是可以接著對他呼叫then方法?我們試下
var p1 = Promise.resolve(`p1Value`)
p1.then(fullfilled => {
console.log(fullfilled) //p1Value
return 123
}, error => {
console.log(error)
}).then(fullfilled => {
console.log(fullfilled) //123
}, error => {
console.log(error)
})複製程式碼
我們在控制檯會看到3行輸出
p1Value
123
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}複製程式碼
如果理解了以上兩種return 情況,應該是能理解為什麼會輸出這三條日誌的。
3. return promise or thenable
首先說明下什麼是thenable,即有then函式的物件。
var o = {
then:function(resolve) {
resolve(123)
}
}
var o = {
then: function(resolve, reject) {
reject(456)
}
}複製程式碼
以上兩例就是thenable物件。我們發現thenable和promise物件很像。都有then方法。並且then函式 和 new Promise(function(resolve, reject))中傳遞的匿名函式很像。
我們看下在promise中返回thenabled物件,會是如何?
var o = {
then:function(resolve) {
resolve(123)
}
}
var p1 = Promise.resolve(`p1Value`)
p1.then(fullfilled => {
console.log(fullfilled) //p1Value
return o
}, error => {
console.log(error)
})複製程式碼
列印的日誌為:
p1Value
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}複製程式碼
再來看個例子
var o = {
then:function(resolve, reject) {
reject(456)
}
}
var p1 = Promise.resolve(`p1Value`)
p1.then(fullfilled => {
console.log(fullfilled) //p1Value
return o
}, error => {
console.log(error)
})複製程式碼
列印的日誌為:
p1Value
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}
Uncaught (in promise) 456 //先忽略這條複製程式碼
通過以上兩個例子,我們發現當傳遞thenable時,promise會把thenable物件拆開,根據thenable物件內的then方法,是呼叫了第一個回撥函式(我們以resolve命名,當然也可以是其他),還是第二個回撥,來決定如何處理這個thenable物件。是不是很像呼叫了 return new Promise(thenable.then)的表現,有可能內部的實現就是這麼處理的。
還剩下最後一種情況,return promise呢,那就直接返回啦。
then返回的 promise 情況小結
- 無return => return Promise.resolve(undefined)
- return 123 => return Promise.resolve(123)
- return thenable => return new Promise(thenable.then)
- return promise => return promise
then的預設值
瞭解了呼叫then方法返回的promise的幾種情況後,我們來看下then的預設回撥,即少傳或不傳回撥,then會如何處理。
var p1 = Promise.resolve(123)
p1.then(null, null)
//傳遞null或少傳引數時,相當於呼叫了
p1.then(fullfilled => {return Promise.resolve(fullfilled)}, error => {return Promise.reject(error) })複製程式碼
即把then所作用的promise的決議值,作為新生成的promise的決議值,再返回這個新的promise。我們來驗證下
var p1 = Promise.resolve(123)
p1.then(null, null)
.then(value => {
console.log(`onResolve`, value)
}, error => {
console.log(`onReject`, error)
})
//onResolve 123
p1.then(fullfilled => {
return Promise.resolve(fullfilled)
},error => {
return Promise.reject(error)
})
.then(value => {
console.log(`onResolve`, value)
}, error => {
console.log(`onReject`, error)
})
//onResolve 123
var p2 = Promise.reject(456)
p2.then(null, null)
.then(value => {
console.log(`onResolve`, value)
}, error => {
console.log(`onReject`, error)
})
//onReject 456
p2.then(fullfilled => {
return Promise.resolve(fullfilled)
},error => {
return Promise.reject(error)
})
.then(value => {
console.log(`onResolve`, value)
}, error => {
console.log(`onReject`, error)
})
//onReject 456複製程式碼
以上驗證了當傳遞null時,then的預設行為。瞭解了預設行為後,我們可以簡化呼叫方式。當只註冊成功回撥時,可以不傳遞第二個回撥
p1.then(fullfilled=>{
//
})複製程式碼
只監聽拒絕回撥時,可以使用then的別名方法catch
p1.then(null, error=> {
//
})
等價於
p1.catch(error => {
//
})複製程式碼
promise鏈小結
結合以上的promise鏈,隨手寫了個涵蓋以上知識點的例子。大家在終端中檢驗下,看輸出的結果,是否都能理解。
var p1 = Promise.resolve(123)
p1.then(null, null)
.then(fullfiled=>{
console.log(`onfullfiled`, fullfiled)
return Promise.resolve(456)
})
.then(fullfiled=> {
console.log(`onfullfiled`, fullfiled)
return {
then: (resolve, reject) => {
reject(789)
}
}
})
.then(null, null)
.then(fullfiled =>{
console.log(`onfullfiled`, fullfiled)
}, error => {
console.log(`onerror`, error)
return Promise.resolve(10) //將錯誤處理後,又進入了正常處理流
})
.then(fullfiled=> {
console.log(`onfullfiled`, fullfiled)
return Promise.reject(`duang duang duang error`)
})
.catch(error => {
console.log(`onCatch`, error)
})複製程式碼
總結
本文首先介紹了promise的三種狀態及生成不同狀態promise方法,然後介紹了promise的回撥處理方法then,分析了不同情況下then函式返回的promise狀態。最後通過promise鏈,將以上知識點進行串聯。希望能夠對大家有所幫助。由於作者的能力有限,也正在處於學習階段,文中不免會有錯誤,歡迎讀者指正。