Promise對象用於清晰的處理非同步任務的完成,返回最終的結果值,本次分享主要介紹Promise的基本屬性以及Promise內部的基礎實現,能夠幫我們更明確使用場景、更快速定位問題。 ...
摘要
Promise對象用於清晰的處理非同步任務的完成,返回最終的結果值,本次分享主要介紹Promise的基本屬性以及Promise內部的基礎實現,能夠幫我們更明確使用場景、更快速定位問題。
Promise出現的原因
首先我們先來看一段代碼:非同步請求的層層嵌套
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()
或者我們可以將上面的代碼優化為下麵這樣
function fn1(params) {
console.log(`我是fn1,我在函數${params}中執行!!!`);
}
function fn2(params) {
try {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log(`我是fn2,我在函數${params}中執行!!!結果是:`,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函數呀');
} catch (error) {
console.error(error);
}
}
fn3()
由上面的兩種寫法的請求可見,在promise之前,為了進行多個非同步請求並且依賴上一個非同步請求的結果時,我們必須進行層層嵌套,大多數情況下,我們又對非同步結果進行數據處理,這樣使得我們的代碼非常難看,並且難以維護,這就形成了回調地獄,由此Promise開始出現了。
回調地獄缺點
- 代碼臃腫
- 可讀性差
- 耦合性高
- 不好進行異常處理
Promise的基本概念
含義
- ES6將其寫進了語言標準里統一了用法,是一個構造函數,用來生成Promise實例
- 參數為一個執行器函數(執行器函數是立即執行的),該函數有兩個函數作為參數,第一個參數是成功時的回調,第二個參數是失敗時的回調
- 函數的方法有resolve(可以處理成功和失敗)、reject(只處理失敗)、all等方法
- then、catch、finally方法為Promise實例上的方法
狀態
- pending --- 等待狀態
- Fulfilled --- 執行狀態 (resolve回調函數,then)
- Rejected --- 拒絕狀態 (reject回調函數,catch)
- 狀態一旦改變就不會再變,狀態只可能是兩種改變,從pending->Fulfilled,pending->Rejected
- 有兩個關鍵的屬性:PromiseState --- 狀態改變,PromiseResult --- 結果數據改變
const p1 = Promise.resolve(64)
const p2 = Promise.reject('我錯了')
const p3 = Promise.then()
const p4 = Promise.catch()
// 狀態改變PromiseState 結果改變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為實例上的方法,報錯
特點
-
錯誤信息清晰定位:可以在外層捕獲異常信息(網路錯誤、語法錯誤都可以捕獲),有“冒泡”性質,會一直向後傳遞,直到被捕獲,所以在最後寫一個catch就可以了
-
鏈式調用:每一個then和catch都會返回一個新的Promise,把結果傳遞到下一個then/catch中,因此可以進行鏈式調用 --- 代碼簡潔清晰
結果由什麼決定
resolve
- 如果傳遞的參數是非Promise類型的對象,則返回的結果是成功狀態的Promise對象,進入下一個then裡面
- 如果傳遞的參數是Promise類型的對象,則返回的結果由返回的Promise決定,如果返回的是resolve則是成功的狀態,進入下一個then里,如果返回的是reject則是失敗的狀態,進入下一個catch里
reject
- 如果傳遞的參數是非Promise類型的對象,則返回的結果是拒絕狀態的Promise對象,進入下一個catch裡面或者是下一個then的第二個參數reject回調裡面
- 如果傳遞的參數是Promise類型的對象,則返回的結果由返回的Promise決定,如果返回的是resolve則是成功的狀態,進入下一個then里,如果返回的是reject則是拒絕的狀態,進入下一個catch裡面或者是下一個then的第二個參數reject回調裡面
這在我們自己封裝的API裡面也有體現:為什麼code為1時都是then接收,其他都是catch接收,就是因為在then裡面也就是resolve函數中對code碼進行了判斷,如果是1則返回Promise.resolve(),進入then里處理,如果是非1則返回Promise.reject(),進入catch里處理。
流程圖
簡單使用
// 模擬一個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,結果為成功狀態
resolve({data:`第${count}次請求獲取數據成功`})
// 返回Promise,結果由Promise決定
// resolve(Promise.reject('resolve中返回reject'))
} else {
reject('customGet請求錯誤了')
}
}
// Promise狀態改變就不會再變
// onreadystatechange方法會被執行四次
// 當地次進來的時候,readyState不等於4,執行else邏輯,執行reject,狀態變為Rejected,所以即使再執行if,狀態之後不會再改變
// xmlHttp.onreadystatechange = function(){
// console.log(xmlHttp,'xmlHttp---onreadystatechange')
// if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
// console.log('customGet請求成功了');
// resolve({data:`第${count}次請求獲取數據成功`})
// } else {
// reject('customGet請求錯誤了')
// }
// }
xmlHttp.send();
})
}
// 使用Promise,並且進行鏈式調用
customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=').then((res)=>{
console.log(res.data);
return '第一次請求處理後的數據'
}).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)=>{
// 以類似'冒泡'的性質再外層捕獲所有的錯誤
console.error(err, '這是catch里的錯誤信息');
})
手寫實現簡單的Promise
通過上面的回顧,我們已經瞭解了Promise的關鍵屬性和特點,下麵我們一起來實現一個簡單的Promise吧
// 1、封裝一個Promise構造函數,有一個函數參數
function Promise(executor){
// 7、添加對象屬性PromiseState PromiseResult
this.PromiseState = 'pending'
this.PromiseResult = null
// 14、創建一個保存成功失敗回調函數的屬性
this.callback = null
// 8、this指向問題
const that = this
// 4、executor有兩個函數參數(resolve,reject)
function resolve(data){
// 10、Promise狀態只能修改一次(同時記得處理reject中的狀態)
if(that.PromiseState !== 'pending') return
// console.log(this, 'this');
// 5、修改對象的狀態PromiseState
that.PromiseState = 'Fulfilled'
// 6、修改對象的結果PromiseResult
that.PromiseResult = data
// 15、非同步執行then里的回調函數
if(that.callback?.onResolve){
that.callback.onResolve(that.PromiseResult)
}
}
function reject(data){
console.log(that.PromiseState, 'that.PromiseState');
if(that.PromiseState !== 'pending') return
// 9、處理失敗函數狀態
that.PromiseState = 'Rejected'
that.PromiseResult = data
console.log(that.PromiseResult, 'that.PromiseResult');
console.log(that.PromiseState, 'that.PromiseState');
// 16、非同步執行then里的回調函數
if(that.callback?.onReject){
that.callback.onReject(that.PromiseResult)
}
}
// 3、執行器函數是同步調用的,並且有兩個函數參數
executor(resolve,reject)
}
// 2、函數的實例上有方法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返回的結果傳遞出去
return new Promise((nextResolve,nextReject)=>{
// 11、處理成功或失敗
if(this.PromiseState === 'Fulfilled'){
// 12、將結果傳遞給函數
// onResolve(this.PromiseResult)
// 18、拿到上一次執行完後返回的結果,判斷是不是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、非同步任務時處理成功或失敗,想辦法等非同步任務執行完成後才去執行這兩個函數
if(this.PromiseState === 'pending'){
this.callback = {
onResolve,
onReject
}
console.log(this.callback, 'this.callback');
}
})
}
// 19、函數實例上有方法catch
Promise.prototype.catch = function(onReject) {
return this.then(null,onReject)
}
// 使用自定義封裝的Promise
const customP = new Promise((resolve,reject)=>{
// 模擬非同步執行請求
// 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();
// 同步執行
resolve('success')
// reject('error')
})
console.log(customP, 'customP');
customP.then((res)=>{
console.log(res, 'resolve回調');
return '第一次回調'
// return new Promise((resolve,reject)=>{
// reject('錯錯錯')
// })
},(err)=>{
console.error(err, 'reject回調');
return '2121'
}).then(()=>{
console.log('then裡面輸出');
}).then().catch((err)=>{
console.error(err, 'catch里的錯誤');
})
針對resolve中返回Promise對象時的內部執行順序
總結
以上就是我們常用的Promise基礎實現,在實現過程中對比了Promise和函數嵌套處理非同步請求的優缺點,Promise仍存在缺點,但是的確方便很多,同時更清晰的理解到錯誤處理如何進行異常穿透的,也能幫助我們更規範的使用Promise以及快速定位問題所在。
作者:京東物流 孫琦
來源:京東雲開發者社區 自猿其說Tech 轉載請註明來源