摘要
Promise 對象用于清晰的處理異步任務(wù)的完成,返回最終的結(jié)果值,本次分享主要介紹 Promise 的基本屬性以及 Promise 內(nèi)部的基礎(chǔ)實現(xiàn),能夠幫我們更明確使用場景、更快速定位問題。Promise 出現(xiàn)的原因
首先我們先來看一段代碼:異步請求的層層嵌套
function fn1(params) {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
const fn1Data = {name: 'fn1'}
console.log(fn1Data, 'fn1Data');
// 請求2
(function fn2() {
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
const fn2Data = {name: `${fn1Data.name}-fn2`}
console.log(fn2Data, 'fn2Data');
// 請求3
(function fn2() {
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
const fn3Data = {name: `${fn2Data.name}-fn3`}
console.log(fn3Data, 'fn3Data');
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
})()
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
})()
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
}
fn1()
或者我們可以將上面的代碼優(yōu)化為下面這樣
function fn1(params) {
console.log(`我是fn1,我在函數(shù)${params}中執(zhí)行?。?!`);
}
function fn2(params) {
try {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log(`我是fn2,我在函數(shù)${params}中執(zhí)行?。?!結(jié)果是:`,params.data);
fn1('fn2')
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
} catch (error) {
console.error(error);
}
}
function fn3() {
try {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log('fn3請求已完成');
fn2('fn3')
}
}
xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
xmlHttp.send();
console.log('我是f3函數(shù)呀');
} catch (error) {
console.error(error);
}
}
fn3()
由上面的兩種寫法的請求可見,在 promise 之前,為了進行多個異步請求并且依賴上一個異步請求的結(jié)果時,我們必須進行層層嵌套,大多數(shù)情況下,我們又對異步結(jié)果進行數(shù)據(jù)處理,這樣使得我們的代碼非常難看,并且難以維護,這就形成了回調(diào)地獄,由此 Promise 開始出現(xiàn)了。回調(diào)地獄缺點-
代碼臃腫
-
可讀性差
-
耦合性高
-
不好進行異常處理
Promise 的基本概念
含義
-
ES6 將其寫進了語言標準里統(tǒng)一了用法,是一個構(gòu)造函數(shù),用來生成 Promise 實例
-
參數(shù)為一個執(zhí)行器函數(shù) (執(zhí)行器函數(shù)是立即執(zhí)行的), 該函數(shù)有兩個函數(shù)作為參數(shù),第一個參數(shù)是成功時的回調(diào),第二個參數(shù)是失敗時的回調(diào)
-
函數(shù)的方法有 resolve (可以處理成功和失敗)、reject (只處理失敗)、all 等方法
-
then、catch、finally 方法為 Promise 實例上的方法
狀態(tài)
-
pending --- 等待狀態(tài)
-
Fulfilled --- 執(zhí)行狀態(tài) (resolve 回調(diào)函數(shù),then)
-
Rejected --- 拒絕狀態(tài) (reject 回調(diào)函數(shù),catch)
-
狀態(tài)一旦改變就不會再變,狀態(tài)只可能是兩種改變,從 pending->Fulfilled,pending->Rejected
-
有兩個關(guān)鍵的屬性:PromiseState --- 狀態(tài)改變,PromiseResult --- 結(jié)果數(shù)據(jù)改變
const p1 = Promise.resolve(64)
const p2 = Promise.reject('我錯了')
const p3 = Promise.then()
const p4 = Promise.catch()
// 狀態(tài)改變PromiseState 結(jié)果改變PromiseResult
console.log(new Promise(()=>{}), 'Promise'); // PromiseState='pending' PromiseResult=undefined
console.log(p1,'p1'); // PromiseState='Fulfilled' PromiseResult=64
console.log(p2,'p2'); // PromiseState="Rejected" PromiseResult='我錯了'
console.log(p3, 'p3'); // then為實例上的方法,報錯
console.log(p4, 'p4'); // catch為實例上的方法,報錯
?特點1.錯誤信息清晰定位:可以在外層捕獲異常信息(網(wǎng)絡(luò)錯誤、語法錯誤都可以捕獲),有 “冒泡” 性質(zhì),會一直向后傳遞,直到被捕獲,所以在最后寫一個 catch 就可以了2.鏈式調(diào)用:每一個 then 和 catch 都會返回一個新的 Promise,把結(jié)果傳遞到下一個 then/catch 中,因此可以進行鏈式調(diào)用 --- 代碼簡潔清晰結(jié)果由什么決定
resolve
-
如果傳遞的參數(shù)是非 Promise 類型的對象,則返回的結(jié)果是成功狀態(tài)的 Promise 對象,進入下一個 then 里面
-
如果傳遞的參數(shù)是 Promise 類型的對象,則返回的結(jié)果由返回的 Promise 決定,如果返回的是 resolve 則是成功的狀態(tài),進入下一個 then 里,如果返回的是 reject 則是失敗的狀態(tài),進入下一個 catch 里
reject
-
如果傳遞的參數(shù)是非 Promise 類型的對象,則返回的結(jié)果是拒絕狀態(tài)的 Promise 對象,進入下一個 catch 里面或者是下一個 then 的第二個參數(shù) reject 回調(diào)里面
-
如果傳遞的參數(shù)是 Promise 類型的對象,則返回的結(jié)果由返回的 Promise 決定,如果返回的是 resolve 則是成功的狀態(tài),進入下一個 then 里,如果返回的是 reject 則是拒絕的狀態(tài),進入下一個 catch 里面或者是下一個 then 的第二個參數(shù) reject 回調(diào)里面
流程圖
?簡單使用
// 模擬一個promise的get請求
let count = 0
function customGet(url){
count += 1
return new Promise((resolve, reject)=>{
const xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET",url, true);
xmlHttp.onload = ()=>{
console.log(xmlHttp, 'xmlHttp---onload');
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log('customGet請求成功了');
// 返回非Promise,結(jié)果為成功狀態(tài)
resolve({data:`第${count}次請求獲取數(shù)據(jù)成功`})
// 返回Promise,結(jié)果由Promise決定
// resolve(Promise.reject('resolve中返回reject'))
} else {
reject('customGet請求錯誤了')
}
}
// Promise狀態(tài)改變就不會再變
// onreadystatechange方法會被執(zhí)行四次
// 當?shù)卮芜M來的時候,readyState不等于4,執(zhí)行else邏輯,執(zhí)行reject,狀態(tài)變?yōu)镽ejected,所以即使再執(zhí)行if,狀態(tài)之后不會再改變
// xmlHttp.onreadystatechange = function(){
// console.log(xmlHttp,'xmlHttp---onreadystatechange')
// if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
// console.log('customGet請求成功了');
// resolve({data:`第${count}次請求獲取數(shù)據(jù)成功`})
// } else {
// reject('customGet請求錯誤了')
// }
// }
xmlHttp.send();
})
}
// 使用Promise,并且進行鏈式調(diào)用
customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=').then((res)=>{
console.log(res.data);
return '第一次請求處理后的數(shù)據(jù)'
}).then((data)=>{
console.log(data)
// console.log(data.toFixed());
return customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=')
}).then((res)=>{
console.log(res.data);
}).catch((err)=>{
// 以類似'冒泡'的性質(zhì)再外層捕獲所有的錯誤
console.error(err, '這是catch里的錯誤信息');
})
手寫實現(xiàn)簡單的 Promise通過上面的回顧,我們已經(jīng)了解了 Promise 的關(guān)鍵屬性和特點,下面我們一起來實現(xiàn)一個簡單的 Promise 吧
// 1、封裝一個Promise構(gòu)造函數(shù),有一個函數(shù)參數(shù)
function Promise(executor){
// 7、添加對象屬性PromiseState PromiseResult
this.PromiseState = 'pending'
this.PromiseResult = null
// 14、創(chuàng)建一個保存成功失敗回調(diào)函數(shù)的屬性
this.callback = null
// 8、this指向問題
const that = this
// 4、executor有兩個函數(shù)參數(shù)(resolve,reject)
function resolve(data){
// 10、Promise狀態(tài)只能修改一次(同時記得處理reject中的狀態(tài))
if(that.PromiseState !== 'pending') return
// console.log(this, 'this');
// 5、修改對象的狀態(tài)PromiseState
that.PromiseState = 'Fulfilled'
// 6、修改對象的結(jié)果PromiseResult
that.PromiseResult = data
// 15、異步執(zhí)行then里的回調(diào)函數(shù)
if(that.callback?.onResolve){
that.callback.onResolve(that.PromiseResult)
}
}
function reject(data){
console.log(that.PromiseState, 'that.PromiseState');
if(that.PromiseState !== 'pending') return
// 9、處理失敗函數(shù)狀態(tài)
that.PromiseState = 'Rejected'
that.PromiseResult = data
console.log(that.PromiseResult, 'that.PromiseResult');
console.log(that.PromiseState, 'that.PromiseState');
// 16、異步執(zhí)行then里的回調(diào)函數(shù)
if(that.callback?.onReject){
that.callback.onReject(that.PromiseResult)
}
}
// 3、執(zhí)行器函數(shù)是同步調(diào)用的,并且有兩個函數(shù)參數(shù)
executor(resolve,reject)
}
// 2、函數(shù)的實例上有方法then
Promise.prototype.then = function(onResolve,onReject){
// 20、處理onReject沒有的情況
if(typeof onReject !== 'function'){
onReject = reason => {
throw reason
}
}
// 21、處理onResolve沒有的情況
if(typeof onResolve !== 'function'){
onResolve = value => value
}
// 17、每一個then方法都返回一個新的Promise,并且把上一個then返回的結(jié)果傳遞出去
return new Promise((nextResolve,nextReject)=>{
// 11、處理成功或失敗
if(this.PromiseState === 'Fulfilled'){
// 12、將結(jié)果傳遞給函數(shù)
// onResolve(this.PromiseResult)
// 18、拿到上一次執(zhí)行完后返回的結(jié)果,判斷是不是Promise
const result = onResolve(this.PromiseResult)
if(result instanceof Promise){
result.then((v)=>{
nextResolve(v)
},(r)=>{
nextReject(r)
})
} else {
nextResolve(result)
}
}
// 當你一步步寫下來的時候有沒有懷疑過為什么不用else
if(this.PromiseState === 'Rejected'){
// 第12步同時處理此邏輯
// onReject(this.PromiseResult)
// 22、處理catch異常穿透捕獲錯誤
try {
const result = onReject(this.PromiseResult)
if(result instanceof Promise){
result.then((v)=>{
nextResolve(v)
}).catch((r)=>{
nextReject(r)
})
} else {
nextReject(result)
}
} catch (error) {
nextReject(this.PromiseResult)
}
}
// 13、異步任務(wù)時處理成功或失敗,想辦法等異步任務(wù)執(zhí)行完成后才去執(zhí)行這兩個函數(shù)
if(this.PromiseState === 'pending'){
this.callback = {
onResolve,
onReject
}
console.log(this.callback, 'this.callback');
}
})
}
// 19、函數(shù)實例上有方法catch
Promise.prototype.catch = function(onReject) {
return this.then(null,onReject)
}
// 使用自定義封裝的Promise
const customP = new Promise((resolve,reject)=>{
// 模擬異步執(zhí)行請求
// const xmlHttp = new XMLHttpRequest();
// xmlHttp.open("GET",'https://v0.yiketianqi.com/api/cityall?appid=&appsecret=', true);
// xmlHttp.onload = ()=>{
// if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
// resolve('success')
// } else {
// reject('error')
// }
// }
// xmlHttp.send();
// 同步執(zhí)行
resolve('success')
// reject('error')
})
console.log(customP, 'customP');
customP.then((res)=>{
console.log(res, 'resolve回調(diào)');
return '第一次回調(diào)'
// return new Promise((resolve,reject)=>{
// reject('錯錯錯')
// })
},(err)=>{
console.error(err, 'reject回調(diào)');
return '2121'
}).then(()=>{
console.log('then里面輸出');
}).then().catch((err)=>{
console.error(err, 'catch里的錯誤');
})
針對 resolve 中返回 Promise 對象時的內(nèi)部執(zhí)行順序??總結(jié)以上就是我們常用的 Promise 基礎(chǔ)實現(xiàn),在實現(xiàn)過程中對比了 Promise 和函數(shù)嵌套處理異步請求的優(yōu)缺點,Promise 仍存在缺點,但是的確方便很多,同時更清晰的理解到錯誤處理如何進行異常穿透的,也能幫助我們更規(guī)范的使用 Promise 以及快速定位問題所在。
-
耦合
+關(guān)注
關(guān)注
13文章
578瀏覽量
100770 -
代碼
+關(guān)注
關(guān)注
30文章
4728瀏覽量
68253 -
執(zhí)行器
+關(guān)注
關(guān)注
5文章
375瀏覽量
19295
原文標題:Promise規(guī)范與原理解析
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論