JavaScript Promise 學習記錄(一)
本文首先介紹了promise的三種狀態(tài)及生成不同狀態(tài)promise方法,然后介紹了promise的回調(diào)處理方法then,分析了不同情況下then函數(shù)返回的promise狀態(tài)。 最后通過promise鏈,將以上知識點進行串聯(lián)。希望能夠?qū)Υ蠹矣兴鶐椭S捎谧髡叩哪芰τ邢?,也正在處于學習階段,文中不免會有錯誤,歡迎讀者指正。
Promise的三種狀態(tài)
Promise 有三種狀態(tài): 未決議(pending)、已決議(resolved)、已拒絕(rejected)
生成Promise的方式有 new Promise() 、 Promise.resolve() 、Promise.reject()
在promise實例上調(diào)用then或catch方法也會生成promise對象,故可進行promise鏈,在下一節(jié)會有所介紹。
tip: chrome瀏覽器的控制臺,是一個快速驗證js代碼的不錯選擇,讀者可以打開chrome瀏覽器,邊閱讀邊實踐。以下代碼皆可直接復制,進行驗證。
首先我們在chrome控制臺中,實踐下生成三種不同狀態(tài)的promise的方法:
//生成pedding狀態(tài)的promise,在控制臺輸入:
new Promise ((resolve, reject) => {
// do nothing
})
//控制臺輸出
//Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
//生成已決議狀態(tài)的promise:
new Promise ((resolve, reject) => {
resolve(123)
})
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}
//或是
Promise.resolve(123)
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}
//生成拒絕狀態(tài)的promise
new Promise ((resolve, reject) => {
reject(456)
})
//Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}
//或是
Promise.reject(456)
//Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}
以上創(chuàng)建的都是立即決議的promise,在我們實際的場景中,Promise往往需要一段時間來處理任務(wù),再完成決議。我們來模擬下這個過程:
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}
以上,我們創(chuàng)建了一個Promise對象,該對象創(chuàng)建時,處于pending狀態(tài),我們模擬了一段耗時操作,在5秒后,對該promise進行決議 resolve(123).我們再次查看p時,發(fā)現(xiàn)狀態(tài)已經(jīng)變?yōu)閞esolved。
Promise的回調(diào)
在promise上可以調(diào)用then方法進行回調(diào)的注冊。promise采用了正確與錯誤回調(diào)分流的方式進行分別回調(diào)。區(qū)別于error-first方式。
首先看下error-first方式的回調(diào)處理方法:
var callback = function(error, value) {
//在回調(diào)中首先判斷是否有錯誤,如果有錯誤,進行處理
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 采用正常與錯誤結(jié)果分流回調(diào)的處理設(shè)計
var p = new Promise((resolve, reject) => {
resolve(123)
})
p.then(fullfilled => {
console.log(fullfilled) //123
}, error => {
console.log(error) //不會調(diào)用到這里
}
)
細心的讀者會看到在控制臺除了打印了 123外,還打印了 Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}。還記得我們前面提到then方法也會生成promise嗎,這里打印的就是調(diào)用then方法生成的promise,我們可以先跳過,后面詳細介紹。
以上我們創(chuàng)建了一個promise,并且通過調(diào)用then方法,在該promise注冊了兩個回調(diào)函數(shù)用于監(jiān)聽正確與失敗的處理結(jié)果。由于該promise 通過resolve(123)進行了決議,所以我們在then上注冊的第一個函數(shù),會收到結(jié)果,打印123。
不難想象如果promise做出了拒絕的處理,或者內(nèi)部有錯誤,我們在then上注冊的錯誤處理函數(shù)將被調(diào)用。來看下例子
var p = new Promise((resolve, reject) => {
reject(456)
})
p.then(fullfilled => {
console.log(fullfilled) //不會調(diào)用到這里
}, error => {
console.log(error) //456
}
)
//或者
var p = new Promise((resolve, reject) => {
foo() //由于未定義foo, 會拋出錯誤
resolve(123) // 無法執(zhí)行到
})
p.then(fullfilled => {
console.log(fullfilled) //不會調(diào)用這里
}, error => {
console.log(error) //ReferenceError: foo is not defined
}
)
我們看到在傳遞給promise的匿名函數(shù)中,我們調(diào)用了foo,由于未定義該方法,所以拋出了錯誤。我們可以把拋出錯誤也理解為promise做出了reject決議,并且拒絕的理由是拋出的錯誤信息。
我們看到foo()后面一句,調(diào)用了resolve,由于錯誤的拋出,這行實際沒有被執(zhí)行到。我們來猜測下,如果不是拋出錯誤,而是調(diào)用了reject(456),再調(diào)用resolve(123),resolve會生效嗎?我們注冊的2個回調(diào)都會被執(zhí)行嗎?
var p = new Promise((resolve, reject) => {
reject(456)
resolve(123)
})
p.then(fullfilled => {
console.log(fullfilled) //不會調(diào)用這里
}, error => {
console.log(error) //456
}
)
看來是不行,如果是多次調(diào)用resolve呢?
var p = new Promise((resolve, reject) => {
resolve(123)
resolve(456)
})
p.then(fullfilled => {
console.log(fullfilled) //123
}, error => {
console.log(error) //不會調(diào)用這里
}
)
看來resolve的結(jié)果并沒有被覆寫。
看了上面的例子,引述下書上的結(jié)論吧:promise一旦決議,即由peding狀態(tài)變?yōu)?resolve或reject狀態(tài),其狀態(tài)及值就不會再次改變。這點保證了,后續(xù)在同一個promise上調(diào)用then方法注冊的回調(diào)函數(shù)都將收到相同的結(jié)果。
Promise鏈
上面提到在promise上調(diào)用then方法,會返回新的promise。那新的promise的值和狀態(tài)是什么呢?
答案是取決于執(zhí)行then中注冊的回調(diào)方法時的返回值。
1.無return
相當于調(diào)用了return Promise.resolve(undefined),
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}
也就是我們上文中,調(diào)用then方法時,除了打印回調(diào)中的日志,還打印了promise的原因。
2.return 非promise、非thenable(后面介紹)值。
比如 return 123。相當于調(diào)用了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,我們是不是可以接著對他調(diào)用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 情況,應(yīng)該是能理解為什么會輸出這三條日志的。
3. return promise or thenable
首先說明下什么是thenable,即有then函數(shù)的對象。
var o = {
then:function(resolve) {
resolve(123)
}
}
var o = {
then: function(resolve, reject) {
reject(456)
}
}
以上兩例就是thenable對象。我們發(fā)現(xiàn)thenable和promise對象很像。都有then方法。并且then函數(shù) 和 new Promise(function(resolve, reject))中傳遞的匿名函數(shù)很像。
我們看下在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 //先忽略這條
通過以上兩個例子,我們發(fā)現(xiàn)當傳遞thenable時,promise會把thenable對象拆開,根據(jù)thenable對象內(nèi)的then方法,是調(diào)用了第一個回調(diào)函數(shù)(我們以resolve命名,當然也可以是其他),還是第二個回調(diào),來決定如何處理這個thenable對象。是不是很像調(diào)用了 return new Promise(thenable.then)的表現(xiàn),有可能內(nèi)部的實現(xiàn)就是這么處理的。
還剩下最后一種情況,return promise呢,那就直接返回啦。
then返回的 promise 情況小結(jié)
- 無return => return Promise.resolve(undefined)
- return 123 => return Promise.resolve(123)
- return thenable => return new Promise(thenable.then)
- return promise => return promise
then的默認傳遞
了解了調(diào)用then方法返回的promise的幾種情況后,我們來看下then的默認回調(diào),即少傳或不傳回調(diào),then會如何處理。
var p1 = Promise.resolve(123)
p1.then(null, null)
//傳遞null或少傳參數(shù)時,相當于調(diào)用了
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的默認行為。了解了默認行為后,我們可以簡化調(diào)用方式。當只注冊成功回調(diào)時,可以不傳遞第二個回調(diào)
p1.then(fullfilled=>{
//
})
只監(jiān)聽拒絕回調(diào)時,可以使用then的別名方法catch
p1.then(null, error=> {
//
})
等價于
p1.catch(error => {
//
})
promise鏈小結(jié)
結(jié)合以上的promise鏈,隨手寫了個涵蓋以上知識點的例子。大家在終端中檢驗下,看輸出的結(jié)果,是否都能理解。
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)
})
總結(jié)
本文首先介紹了promise的三種狀態(tài)及生成不同狀態(tài)promise方法,然后介紹了promise的回調(diào)處理方法then,分析了不同情況下then函數(shù)返回的promise狀態(tài)。最后通過promise鏈,將以上知識點進行串聯(lián)。希望能夠?qū)Υ蠹矣兴鶐椭S捎谧髡叩哪芰τ邢?,也正在處于學習階段,文中不免會有錯誤,歡迎讀者指正。